Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
16 KiB

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