config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

spinner.el (13862B)


      1 ;;; spinner.el --- Add spinners and progress-bars to the mode-line for ongoing operations -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
      4 
      5 ;; Author: Artur Malabarba <emacs@endlessparentheses.com>
      6 ;; Version: 1.7.4
      7 ;; Package-Requires: ((emacs "24.3"))
      8 ;; URL: https://github.com/Malabarba/spinner.el
      9 ;; Keywords: processes mode-line
     10 
     11 ;; This program is free software; you can redistribute it and/or modify
     12 ;; it under the terms of the GNU General Public License as published by
     13 ;; the Free Software Foundation, either version 3 of the License, or
     14 ;; (at your option) any later version.
     15 
     16 ;; This program is distributed in the hope that it will be useful,
     17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 ;; GNU General Public License for more details.
     20 
     21 ;; You should have received a copy of the GNU General Public License
     22 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     23 
     24 ;;; Commentary:
     25 ;;
     26 ;; 1 Usage
     27 ;; ═══════
     28 ;;
     29 ;;   First of all, don’t forget to add `(spinner "VERSION")' to your
     30 ;;   package’s dependencies.
     31 ;;
     32 ;;
     33 ;; 1.1 Major-modes
     34 ;; ───────────────
     35 ;;
     36 ;;   1. Just call `(spinner-start)' and a spinner will be added to the
     37 ;;      mode-line.
     38 ;;   2. Call `(spinner-stop)' on the same buffer when you want to remove
     39 ;;      it.
     40 ;;
     41 ;;   The default spinner is a line drawing that rotates. You can pass an
     42 ;;   argument to `spinner-start' to specify which spinner you want. All
     43 ;;   possibilities are listed in the `spinner-types' variable, but here are
     44 ;;   a few examples for you to try:
     45 ;;
     46 ;;   • `(spinner-start 'vertical-breathing 10)'
     47 ;;   • `(spinner-start 'minibox)'
     48 ;;   • `(spinner-start 'moon)'
     49 ;;   • `(spinner-start 'triangle)'
     50 ;;
     51 ;;   You can also define your own as a vector of strings (see the examples
     52 ;;   in `spinner-types').
     53 ;;
     54 ;;
     55 ;; 1.2 Minor-modes
     56 ;; ───────────────
     57 ;;
     58 ;;   Minor-modes can create a spinner with `spinner-create' and then add it
     59 ;;   to their mode-line lighter. They can then start the spinner by setting
     60 ;;   a variable and calling `spinner-start-timer'. Finally, they can stop
     61 ;;   the spinner (and the timer) by just setting the same variable to nil.
     62 ;;
     63 ;;   Here’s an example for a minor-mode named `foo'. Assuming that
     64 ;;   `foo--lighter' is used as the mode-line lighter, the following code
     65 ;;   will add an *inactive* global spinner to the mode-line.
     66 ;;   ┌────
     67 ;;   │ (defvar foo--spinner (spinner-create 'rotating-line))
     68 ;;   │ (defconst foo--lighter
     69 ;;   │   '(" foo" (:eval (spinner-print foo--spinner))))
     70 ;;   └────
     71 ;;
     72 ;;   1. To activate the spinner, just call `(spinner-start foo--spinner)'.
     73 ;;      It will show up on the mode-line and start animating.
     74 ;;   2. To get rid of it, call `(spinner-stop foo--spinner)'. It will then
     75 ;;      disappear again.
     76 ;;
     77 ;;   Some minor-modes will need spinners to be buffer-local. To achieve
     78 ;;   that, just make the `foo--spinner' variable buffer-local and use the
     79 ;;   third argument of the `spinner-create' function. The snippet below is an
     80 ;;   example.
     81 ;;
     82 ;;   ┌────
     83 ;;   │ (defvar-local foo--spinner nil)
     84 ;;   │ (defconst foo--lighter
     85 ;;   │   '(" foo" (:eval (spinner-print foo--spinner))))
     86 ;;   │ (defun foo--start-spinner ()
     87 ;;   │   "Create and start a spinner on this buffer."
     88 ;;   │   (unless foo--spinner
     89 ;;   │     (setq foo--spinner (spinner-create 'moon t)))
     90 ;;   │   (spinner-start foo--spinner))
     91 ;;   └────
     92 ;;
     93 ;;   1. To activate the spinner, just call `(foo--start-spinner)'.
     94 ;;   2. To get rid of it, call `(spinner-stop foo--spinner)'.
     95 ;;
     96 ;;   This will use the `moon' spinner, but you can use any of the names
     97 ;;   defined in the `spinner-types' variable or even define your own.
     98 
     99 
    100 ;;; Code:
    101 (eval-when-compile
    102   (require 'cl-lib))
    103 
    104 (defconst spinner-types
    105   '((3-line-clock . ["┤" "┘" "┴" "└" "├" "┌" "┬" "┐"])
    106     (2-line-clock . ["┘" "└" "┌" "┐"])
    107     (flipping-line . ["_" "\\" "|" "/"])
    108     (rotating-line . ["-" "\\" "|" "/"])
    109     (progress-bar . ["[    ]" "[=   ]" "[==  ]" "[=== ]" "[====]" "[ ===]" "[  ==]" "[   =]"])
    110     (progress-bar-filled . ["|    |" "|█   |" "|██  |" "|███ |" "|████|" "| ███|" "|  ██|" "|   █|"])
    111     (vertical-breathing . ["▁" "▂" "▃" "▄" "▅" "▆" "▇" "█" "▇" "▆" "▅" "▄" "▃" "▂" "▁" " "])
    112     (vertical-rising . ["▁" "▄" "█" "▀" "▔"])
    113     (horizontal-breathing . [" " "▏" "▎" "▍" "▌" "▋" "▊" "▉" "▉" "▊" "▋" "▌" "▍" "▎" "▏"])
    114     (horizontal-breathing-long
    115      . ["  " "▎ " "▌ " "▊ " "█ " "█▎" "█▌" "█▊" "██" "█▊" "█▌" "█▎" "█ " "▊ " "▋ " "▌ " "▍ " "▎ " "▏ "])
    116     (horizontal-moving . ["  " "▌ " "█ " "▐▌" " █" " ▐"])
    117     (minibox . ["▖" "▘" "▝" "▗"])
    118     (triangle . ["◢" "◣" "◤" "◥"])
    119     (box-in-box . ["◰" "◳" "◲" "◱"])
    120     (box-in-circle . ["◴" "◷" "◶" "◵"])
    121     (half-circle . ["◐" "◓" "◑" "◒"])
    122     (moon . ["🌑" "🌘" "🌗" "🌖" "🌕" "🌔" "🌓" "🌒"]))
    123   "Predefined alist of spinners.
    124 Each car is a symbol identifying the spinner, and each cdr is a
    125 vector, the spinner itself.")
    126 
    127 (defun spinner-make-progress-bar (width &optional char)
    128   "Return a vector of strings of the given WIDTH.
    129 The vector is a valid spinner type and is similar to the
    130 `progress-bar' spinner, except without the surrounding brackets.
    131 CHAR is the character to use for the moving bar (defaults to =)."
    132   (let ((whole-string (concat (make-string (1- width) ?\s)
    133                               (make-string 4 (or char ?=))
    134                               (make-string width ?\s))))
    135     (apply #'vector (mapcar (lambda (n) (substring whole-string n (+ n width)))
    136                             (number-sequence (+ width 3) 0 -1)))))
    137 
    138 (defvar spinner-current nil
    139   "Spinner currently being displayed on the `mode-line-process'.")
    140 (make-variable-buffer-local 'spinner-current)
    141 
    142 (defconst spinner--mode-line-construct
    143   '(:eval (spinner-print spinner-current))
    144   "Construct used to display a spinner in `mode-line-process'.")
    145 (put 'spinner--mode-line-construct 'risky-local-variable t)
    146 
    147 (defvar spinner-frames-per-second 10
    148   "Default speed at which spinners spin, in frames per second.
    149 Each spinner can override this value.")
    150 
    151 
    152 ;;; The spinner object.
    153 (defun spinner--type-to-frames (type)
    154   "Return a vector of frames corresponding to TYPE.
    155 The list of possible built-in spinner types is given by the
    156 `spinner-types' variable, but you can also use your own (see
    157 below).
    158 
    159 If TYPE is nil, the frames of this spinner are given by the first
    160 element of `spinner-types'.
    161 If TYPE is a symbol, it specifies an element of `spinner-types'.
    162 If TYPE is 'random, use a random element of `spinner-types'.
    163 If TYPE is a list, it should be a list of symbols, and a random
    164 one is chosen as the spinner type.
    165 If TYPE is a vector, it should be a vector of strings and these
    166 are used as the spinner's frames.  This allows you to make your
    167 own spinner animations."
    168   (cond
    169    ((vectorp type) type)
    170    ((not type) (cdr (car spinner-types)))
    171    ((eq type 'random)
    172     (cdr (elt spinner-types
    173               (random (length spinner-types)))))
    174    ((listp type)
    175     (cdr (assq (elt type (random (length type)))
    176                spinner-types)))
    177    ((symbolp type) (cdr (assq type spinner-types)))
    178    (t (error "Unknown spinner type: %s" type))))
    179 
    180 (cl-defstruct (spinner
    181                (:copier nil)
    182                (:conc-name spinner--)
    183                (:constructor make-spinner (&optional type buffer-local frames-per-second delay-before-start)))
    184   (frames (spinner--type-to-frames type))
    185   (counter 0)
    186   (fps (or frames-per-second spinner-frames-per-second))
    187   (timer (timer-create))
    188   (active-p nil)
    189   (buffer (when buffer-local
    190             (if (bufferp buffer-local)
    191                 buffer-local
    192               (current-buffer))))
    193   (delay (or delay-before-start 0)))
    194 
    195 ;;;###autoload
    196 (defun spinner-create (&optional type buffer-local fps delay)
    197   "Create a spinner of the given TYPE.
    198 The possible TYPEs are described in `spinner--type-to-frames'.
    199 
    200 FPS, if given, is the number of desired frames per second.
    201 Default is `spinner-frames-per-second'.
    202 
    203 If BUFFER-LOCAL is non-nil, the spinner will be automatically
    204 deactivated if the buffer is killed.  If BUFFER-LOCAL is a
    205 buffer, use that instead of current buffer.
    206 
    207 When started, in order to function properly, the spinner runs a
    208 timer which periodically calls `force-mode-line-update' in the
    209 current buffer.  If BUFFER-LOCAL was set at creation time, then
    210 `force-mode-line-update' is called in that buffer instead.  When
    211 the spinner is stopped, the timer is deactivated.
    212 
    213 DELAY, if given, is the number of seconds to wait after starting
    214 the spinner before actually displaying it. It is safe to cancel
    215 the spinner before this time, in which case it won't display at
    216 all."
    217   (make-spinner type buffer-local fps delay))
    218 
    219 (defun spinner-print (spinner)
    220   "Return a string of the current frame of SPINNER.
    221 If SPINNER is nil, just return nil.
    222 Designed to be used in the mode-line with:
    223     (:eval (spinner-print some-spinner))"
    224   (when (and spinner (spinner--active-p spinner))
    225     (let ((frame (spinner--counter spinner)))
    226       (when (>= frame 0)
    227         (elt (spinner--frames spinner) frame)))))
    228 
    229 (defun spinner--timer-function (spinner)
    230   "Function called to update SPINNER.
    231 If SPINNER is no longer active, or if its buffer has been killed,
    232 stop the SPINNER's timer."
    233   (let ((buffer (spinner--buffer spinner)))
    234     (if (or (not (spinner--active-p spinner))
    235             (and buffer (not (buffer-live-p buffer))))
    236         (spinner-stop spinner)
    237       ;; Increment
    238       (cl-callf (lambda (x) (if (< x 0)
    239                            (1+ x)
    240                          (% (1+ x) (length (spinner--frames spinner)))))
    241           (spinner--counter spinner))
    242       ;; Update mode-line.
    243       (if (buffer-live-p buffer)
    244           (with-current-buffer buffer
    245             (force-mode-line-update))
    246         (force-mode-line-update)))))
    247 
    248 (defun spinner--start-timer (spinner)
    249   "Start a SPINNER's timer."
    250   (let ((old-timer (spinner--timer spinner)))
    251     (when (timerp old-timer)
    252       (cancel-timer old-timer))
    253 
    254     (setf (spinner--active-p spinner) t)
    255 
    256     (unless (ignore-errors (> (spinner--fps spinner) 0))
    257       (error "A spinner's FPS must be a positive number"))
    258     (setf (spinner--counter spinner)
    259           (round (- (* (or (spinner--delay spinner) 0)
    260                        (spinner--fps spinner)))))
    261     ;; Create timer.
    262     (let* ((repeat (/ 1.0 (spinner--fps spinner)))
    263            (time (timer-next-integral-multiple-of-time (current-time) repeat))
    264            ;; Create the timer as a lex variable so it can cancel itself.
    265            (timer (spinner--timer spinner)))
    266       (timer-set-time timer time repeat)
    267       (timer-set-function timer #'spinner--timer-function (list spinner))
    268       (timer-activate timer)
    269       ;; Return a stopping function.
    270       (lambda () (spinner-stop spinner)))))
    271 
    272 
    273 ;;; The main functions
    274 ;;;###autoload
    275 (defun spinner-start (&optional type-or-object fps delay)
    276   "Start a mode-line spinner of given TYPE-OR-OBJECT.
    277 If TYPE-OR-OBJECT is an object created with `make-spinner',
    278 simply activate it.  This method is designed for minor modes, so
    279 they can use the spinner as part of their lighter by doing:
    280     '(:eval (spinner-print THE-SPINNER))
    281 To stop this spinner, call `spinner-stop' on it.
    282 
    283 If TYPE-OR-OBJECT is anything else, a buffer-local spinner is
    284 created with this type, and it is displayed in the
    285 `mode-line-process' of the buffer it was created it.  Both
    286 TYPE-OR-OBJECT and FPS are passed to `make-spinner' (which see).
    287 To stop this spinner, call `spinner-stop' in the same buffer.
    288 
    289 Either way, the return value is a function which can be called
    290 anywhere to stop this spinner.  You can also call `spinner-stop'
    291 in the same buffer where the spinner was created.
    292 
    293 FPS, if given, is the number of desired frames per second.
    294 Default is `spinner-frames-per-second'.
    295 
    296 DELAY, if given, is the number of seconds to wait until actually
    297 displaying the spinner. It is safe to cancel the spinner before
    298 this time, in which case it won't display at all."
    299   (unless (spinner-p type-or-object)
    300     ;; Choose type.
    301     (if (spinner-p spinner-current)
    302         (setf (spinner--frames spinner-current) (spinner--type-to-frames type-or-object))
    303       (setq spinner-current (make-spinner type-or-object (current-buffer) fps delay)))
    304     (setq type-or-object spinner-current)
    305     ;; Maybe add to mode-line.
    306     (unless (and (listp mode-line-process)
    307                  (memq 'spinner--mode-line-construct mode-line-process))
    308       (setq mode-line-process
    309             (list (or mode-line-process "")
    310                   'spinner--mode-line-construct))))
    311 
    312   ;; Create timer.
    313   (when fps (setf (spinner--fps type-or-object) fps))
    314   (when delay (setf (spinner--delay type-or-object) delay))
    315   (spinner--start-timer type-or-object))
    316 
    317 (defun spinner-start-print (spinner)
    318   "Like `spinner-print', but also start SPINNER if it's not active."
    319   (unless (spinner--active-p spinner)
    320     (spinner-start spinner))
    321   (spinner-print spinner))
    322 
    323 (defun spinner-stop (&optional spinner)
    324   "Stop SPINNER, defaulting to the current buffer's spinner.
    325 It is always safe to call this function, even if there is no
    326 active spinner."
    327   (let ((spinner (or spinner spinner-current)))
    328     (when (spinner-p spinner)
    329       (let ((timer (spinner--timer spinner)))
    330         (when (timerp timer)
    331           (cancel-timer timer)))
    332       (setf (spinner--active-p spinner) nil)
    333       (force-mode-line-update))))
    334 
    335 (provide 'spinner)
    336 
    337 ;; Local Variables:
    338 ;; indent-tabs-mode: nil
    339 ;; End:
    340 ;;; spinner.el ends here