|
|
;;; spinner.el --- Add spinners and progress-bars to the mode-line for ongoing operations -*- lexical-binding: t; -*- |
|
|
|
|
|
;; Copyright (C) 2015 Free Software Foundation, Inc. |
|
|
|
|
|
;; Author: Artur Malabarba <emacs@endlessparentheses.com> |
|
|
;; Version: 1.4 |
|
|
;; URL: https://github.com/Malabarba/spinner.el |
|
|
;; Keywords: processes mode-line |
|
|
|
|
|
;; 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
;;; Commentary: |
|
|
;; |
|
|
;; 1 Usage |
|
|
;; ═══════ |
|
|
;; |
|
|
;; First of all, don’t forget to add `(spinner "VERSION")' to your |
|
|
;; package’s dependencies. |
|
|
;; |
|
|
;; |
|
|
;; 1.1 Major-modes |
|
|
;; ─────────────── |
|
|
;; |
|
|
;; 1. Just call `(spinner-start)' and a spinner will be added to the |
|
|
;; mode-line. |
|
|
;; 2. Call `(spinner-stop)' on the same buffer when you want to remove |
|
|
;; it. |
|
|
;; |
|
|
;; The default spinner is a line drawing that rotates. You can pass an |
|
|
;; argument to `spinner-start' to specify which spinner you want. All |
|
|
;; possibilities are listed in the `spinner-types' variable, but here are |
|
|
;; a few examples for you to try: |
|
|
;; |
|
|
;; • `(spinner-start 'vertical-breathing 10)' |
|
|
;; • `(spinner-start 'minibox)' |
|
|
;; • `(spinner-start 'moon)' |
|
|
;; • `(spinner-start 'triangle)' |
|
|
;; |
|
|
;; You can also define your own as a vector of strings (see the examples |
|
|
;; in `spinner-types'). |
|
|
;; |
|
|
;; |
|
|
;; 1.2 Minor-modes |
|
|
;; ─────────────── |
|
|
;; |
|
|
;; 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. |
|
|
;; |
|
|
;; 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 (spinner-create '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 `spinner-create' function. The snippet below is an |
|
|
;; example. |
|
|
;; |
|
|
;; ┌──── |
|
|
;; │ (defvar-local foo--spinner nil) |
|
|
;; │ (defconst foo--lighter |
|
|
;; │ '(" foo" (:eval (spinner-print foo--spinner)))) |
|
|
;; │ (defun foo--start-spinner () |
|
|
;; │ "Create and start a spinner on this buffer." |
|
|
;; │ (unless foo--spinner |
|
|
;; │ (setq foo--spinner (spinner-create 'moon t))) |
|
|
;; │ (spinner-start foo--spinner)) |
|
|
;; └──── |
|
|
;; |
|
|
;; 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: |
|
|
(eval-when-compile |
|
|
(require 'cl)) |
|
|
|
|
|
(defconst spinner-types |
|
|
'((3-line-clock . ["┤" "┘" "┴" "└" "├" "┌" "┬" "┐"]) |
|
|
(2-line-clock . ["┘" "└" "┌" "┐"]) |
|
|
(flipping-line . ["_" "\\" "|" "/"]) |
|
|
(rotating-line . ["-" "\\" "|" "/"]) |
|
|
(progress-bar . ["[ ]" "[= ]" "[== ]" "[=== ]" "[====]" "[ ===]" "[ ==]" "[ =]"]) |
|
|
(progress-bar-filled . ["| |" "|█ |" "|██ |" "|███ |" "|████|" "| ███|" "| ██|" "| █|"]) |
|
|
(vertical-breathing . ["▁" "▂" "▃" "▄" "▅" "▆" "▇" "█" "▇" "▆" "▅" "▄" "▃" "▂" "▁" " "]) |
|
|
(vertical-rising . ["▁" "▄" "█" "▀" "▔"]) |
|
|
(horizontal-breathing . [" " "▏" "▎" "▍" "▌" "▋" "▊" "▉" "▉" "▊" "▋" "▌" "▍" "▎" "▏"]) |
|
|
(horizontal-breathing-long |
|
|
. [" " "▎ " "▌ " "▊ " "█ " "█▎" "█▌" "█▊" "██" "█▊" "█▌" "█▎" "█ " "▊ " "▋ " "▌ " "▍ " "▎ " "▏ "]) |
|
|
(horizontal-moving . [" " "▌ " "█ " "▐▌" " █" " ▐"]) |
|
|
(minibox . ["▖" "▘" "▝" "▗"]) |
|
|
(triangle . ["◢" "◣" "◤" "◥"]) |
|
|
(box-in-box . ["◰" "◳" "◲" "◱"]) |
|
|
(box-in-circle . ["◴" "◷" "◶" "◵"]) |
|
|
(half-circle . ["◐" "◓" "◑" "◒"]) |
|
|
(moon . ["🌑" "🌘" "🌖" "🌕" "🌔" "🌒"])) |
|
|
"Predefined alist of spinners. |
|
|
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-process'.") |
|
|
(make-variable-buffer-local 'spinner-current) |
|
|
|
|
|
(defconst spinner--mode-line-construct |
|
|
'(: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-frames-per-second 10 |
|
|
"Default speed at which spinners spin, in frames per second. |
|
|
Each spinner can override this value.") |
|
|
|
|
|
|
|
|
;;; 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)))) |
|
|
|
|
|
(defstruct (spinner |
|
|
(:copier nil) |
|
|
(:conc-name spinner--) |
|
|
(: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. |
|
|
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. |
|
|
|
|
|
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. |
|
|
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)) |
|
|
(let ((frame (spinner--counter spinner))) |
|
|
(when (>= frame 0) |
|
|
(elt (spinner--frames spinner) frame))))) |
|
|
|
|
|
(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) (if (< x 0) |
|
|
(1+ 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." |
|
|
(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) (- (* (or (spinner--delay spinner) 0) |
|
|
(spinner--fps spinner)))) |
|
|
;; Create timer. |
|
|
(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))) |
|
|
(timer-set-time timer time repeat) |
|
|
(timer-set-function timer #'spinner--timer-function (list spinner)) |
|
|
(timer-activate timer) |
|
|
;; Return a stopping function. |
|
|
(lambda () (spinner-stop spinner))))) |
|
|
|
|
|
|
|
|
;;; The main functions |
|
|
;;;###autoload |
|
|
(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)) |
|
|
To stop this spinner, call `spinner-stop' on it. |
|
|
|
|
|
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. |
|
|
|
|
|
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. |
|
|
|
|
|
FPS, if given, is the number of desired 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 delay))) |
|
|
(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. |
|
|
(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) |
|
|
"Like `spinner-print', but also start SPINNER if it's not active." |
|
|
(unless (spinner--active-p spinner) |
|
|
(spinner-start 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))) |
|
|
|
|
|
;;;; ChangeLog: |
|
|
|
|
|
;; 2015-08-11 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; Merge commit '8d8c459d7757cf5774f11be9147d7a54f5f9bbd7' |
|
|
;; |
|
|
;; 2015-05-02 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; * spinner: Rename constructor. |
|
|
;; |
|
|
;; 2015-04-30 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; * spinner/spinner.el: Rewrite spinners as structures |
|
|
;; |
|
|
;; 2015-04-09 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; spinner: Fix readme |
|
|
;; |
|
|
;; 2015-04-09 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; spinner: Fix leftover mode-line-format code |
|
|
;; |
|
|
;; 2015-04-09 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; Merge commit 'c44ef65515f50bd38304a6f50adebc984fb8e431' |
|
|
;; |
|
|
;; 2015-03-07 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; Merge commit '7eca7d023c95bc21c7838467b3a58d549afaf68d' |
|
|
;; |
|
|
;; 2015-03-07 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; Merge commit 'a7b4e52766977b58c6b9899305e962a2b5235bda' |
|
|
;; |
|
|
;; 2015-03-07 Artur Malabarba <bruce.connor.am@gmail.com> |
|
|
;; |
|
|
;; Add 'packages/spinner/' from commit |
|
|
;; '9477ee899d62259d4b946f243cdcdd9cdeb1e910' |
|
|
;; |
|
|
;; git-subtree-dir: packages/spinner git-subtree-mainline: |
|
|
;; 5736e852fd48a0f1ba1c328dd4d03e3fa008a406 git-subtree-split: |
|
|
;; 9477ee899d62259d4b946f243cdcdd9cdeb1e910 |
|
|
;; |
|
|
|
|
|
|
|
|
(provide 'spinner) |
|
|
|
|
|
;;; spinner.el ends here
|
|
|
|