Functions to print different types of progress spinners for use in Elvish scripts.
This file is written in literate programming style, to make it easy to explain. See spinners.elv for the generated file.
Install the elvish-modules
package using epm (you can put these statements in your rc.elv
file as well, to automatically install the package upon startup if needed):
use epm
epm:install &silent-if-installed github.com/zzamboni/elvish-modules
In your rc.elv
, load this module:
use github.com/zzamboni/elvish-modules/spinners
use github.com/zzamboni/elvish-modules/spinners
The spinner definitions are taken from the sindresorhus/cli-spinners project. You can view a demo of all the spinners with the spinners:demo
command:
You can get the list of available spinners using the spinners:list
function.
spinners:list | take 5
▶ arc ▶ arrow ▶ arrow2 ▶ arrow3 ▶ balloon
The easiest way to create a spinner is to use spinners:run
to execute a lambda while displaying a spinner. The spinner will stop automatically when the lambda finishes:
spinners:run { sleep 3 }
By default, the spinner specified by $spinners:default-spinner
will be used. You can change this by passing the &spinner
option. You can also specify a title to show next to the spinner by passing the &title
option:
spinners:run &spinner=arrow &title="Loading modules" { sleep 3 }
The input and output of spinners:run
are connected to the lambda, so you can use it as part of a pipeline. E.g.:
spinners:run &title=(styled "Counting files" blue) &style=green { fd . . } | count
fd . . | spinners:run &title="Counting characters in filenames" { each [f]{ all $f } } | count
The following options are available:
&spinner
- specify the spinner to use, by name. Default:$spinners:default-spinner
.&style
- string or list of style transformers that will be applied to the spinner (only to the spinner itself). Default: empty list.&title
- string or styled object that will be shown after the spinner. Default: empty string.&prefix
- string or styled object that will be shown before the spinner. Default: empty string.&indent
- integer indicating an indentation level. The indent will appear beforeprefix
if both are given. Default: 0&cursor
- boolean specifying whether the cursor should be shown while the spinner is running. Default:$false
.&persist
- if and how the spinner should persist at the end. See Spinner persistence and exception handling below. Default:$false
&hide-exception
- boolean specifying whether an exception generated by the lambda should be displayed as usual or hidden (useful with&persist=status
). Default:$false
.
A spinner definition consists of frames and an interval. If you don’t want to use one of the built-in spinners by specifying &spinner
, you can use the following options to create a custom spinner:
&frames
- a string or list containing the different steps for the spinner.&interval
- interval between the frames, in milliseconds.
If the lambda accepts an argument, the spinner ID will be passed to it. You can use it to manipulate the spinner while it runs using spinners:attr
to modify its attributes and behavior (the same as the option names above). For example:
spinners:run &title="Starting title" &persist=$true [s]{
sleep 3
spinners:attr $s title "New title!"
sleep 3
spinners:attr $s spinner shark
spinners:attr $s style [ red ]
sleep 3
spinners:attr $s persist success
}
By default, the spinner disappears when it stops. Any exceptions thrown by the lambda are displayed as usual.
This behavior can be changed by the following options to spinners:run
:
&hide-exception
: if set, exceptions thrown by the lamda are caught and hidden. Most useful with&persist=status
(see below).&persist
: if set, can have one of the following values:
You can define custom persistence symbols by adding elements to $spinners:persist-symbols
, indexed by the symbol name, and containing two keys symbol
and color
. For example:
spinners:persist-symbols[unicorn] = [ &symbol="🦄" &color=default ]
spinners:run &title="Getting a unicorn" &persist=unicorn { sleep 3 }
If you want more flexibility in producing spinners, you can create, start and stop spinners by hand. The general sequence should be as follows:
- Create a spinner using the
spinners:new
function. This function returns the ID of the new spinner, which you must store to use in spinner operations. Thespinners:new
function accepts the same options asspinners:run
to specify the parameters of the spinner. For example:s = (spinners:new &title="Test spinner" &persist=status &hide-exception)
- Start the spinner with the
spinners:start
function. This displays and starts the spinner running with a background function, so it returns immediately. Note: if you do this from the terminal, the spinner will display on top of the command line as you type other commands.spinners:start $s
- While the spinner runs, you can use the
spinners:attr
function to set any of its attributes (the same as the option names in thespinners:new
function) to modify the spinner on the fly. You can even change the spinner type by changing thespinner
attribute. You can also persist and continue the current spinner and continue in a new line withspinners:persist
, and also persist the current one and continue with a new one with thespinners:persist-and-new
function, which takes the same arguments as thespinners:run
function.sleep 3 spinners:attr $s title "New title!" sleep 2 spinners:persist $s sleep 1 spinners:attr $s spinner shark sleep 3 spinners:attr $s status ?(fail error) spinners:persist-and-new $s &indent=2 &title=(styled "Next step" blue) sleep 3
- Stop the spinner with the
spinners:stop
function. You can use the&status
option to pass an exception object or$ok
which can be used to determine the final state of the spinner (e.g. if you created it with&persist=status
):spinners:stop $s
Note: if you call spinners:start
and spinners:stop
by hand, be aware that when the spinner stops Elvish will produce a notification about the background process. To prevent this you have to set $notify-bg-job-success
to $false
before calling $spinners:stop
. This is not necessary if you use spinners:run
.
During the execution of a spinner, the spinners:persist
function can be used to persist the current spinner (according to its settings), move the cursor to the next line and continue running it. The spinners:persist-and-new
function can be used to persist the current spinner (according to its settings), move the cursor to the next line, and then continue with a new spinner. This function accepts the same arguments as spinners:run
, settings from the old spinner are not inherited.
use str
use path
use github.com/zzamboni/elvish-modules/tty
When the module is loaded, we read the spinner definitions from spinners.json
.
var spinners = (from-json < (path:dir (src)[name])/spinners.json)
By default the dots
spinner is used.
var default-spinner = 'dots'
We store spinners in the $spinners:-sr
variable, indexed by ID.
var -sr = [&]
All output is produced through this function.
fn -output {|@s|
print $@s >/dev/tty
}
A spinner object is a definition of a spinner, and is a map containing the following keys:
spinner
- the name of one of the built-in spinners. This field implicitly definesframes
andinterval
:frames
- a string or list containing the different steps for the spinner.interval
- interval between the frames, in milliseconds.
style
- an optional list of style transformers that will be applied to the spinner characters.title
- an optional string or styled object that will be shown after the spinner.prefix
- an optional string or styled object that will be shown before the spinner.indent
- an optional integer indicating an indentation level. The indent will appear beforeprefix
if both are given.cursor
- an optional boolean specifying whether the cursor should be shown while the spinner is running. Default is to hide it.persist
- an optional boolean specifying whether the spinner should be left in place and the cursor moved to the next line when the spinner finishes running. By default the spinner is cleared when it finishes running, and the cursor stays at the beginning of the line.current
- the current step of the spinner as it runs.id
- unique identifier for the spinner. By default generated as a UUID (if theuuidgen
command is present) or a random number, but can be specified using the&id
option if desired.
spinners:new
creates a new spinner object containing the keys above, stores it in the registry, and returns its ID. The &spinner
option can be used to initialize &frames
and &interval
from the default spinner definitions. If not given, $spinners:default-spinner
is used. If &frames
and &interval
are given, they are used to override the default values. &title
, &prefix
and &style
default to empty.
fn new {|&spinner=$nil &frames=$nil &interval=$nil &title="" &style=[] &prefix="" &indent=0 &cursor=$false &persist=$false &hide-exception=$false &id=$nil|
# Determine ID to use
set id = (or $id (var e = ?(uuidgen)) (randint 0 9999999))
# Use default spinner if none is specified
if (not $spinner) { set spinner = $default-spinner }
# Automatically convert non-list styles, so you can do e.g. &style=red
if (not-eq (kind-of $style) list) { set style = [$style] }
# Create and store the new spinner object
set -sr[$id] = [
&id= $id
&spinner= $spinner
&frames= (or $frames $spinners[$spinner][frames])
&interval= (or $interval $spinners[$spinner][interval])
&title= $title
&prefix= $prefix
&indent= $indent
&style= $style
&cursor= $cursor
&persist= $persist
&hide-exception= $hide-exception
¤t= 0
&status= $ok
&stop= $false
]
# Return ID of the new spinner
put $id
}
Once a spinner object is created, spinners:step
can be used to display and advance the spinner. This function returns an updated spinner object, which needs to be stored by the caller (Elvish does not support modifying arguments by reference).
fn step {|spinner|
var steps = $-sr[$spinner][frames]
var indentation = (str:join '' [(repeat $-sr[$spinner][indent] ' ')])
var pre-string = (if (not-eq $-sr[$spinner][prefix] '') { put $-sr[$spinner][prefix]' ' } else { put '' })
var post-string = (if (not-eq $-sr[$spinner][title] '') { put ' '$-sr[$spinner][title] } else { put '' })
tty:set-cursor-pos (all $-sr[$spinner][initial-pos])
-output $indentation$pre-string(styled $steps[$-sr[$spinner][current]] (all $-sr[$spinner][style]))$post-string
tty:clear-line
var inc = 1
if (eq (kind-of $steps string)) {
set inc = (count $steps[$-sr[$spinner][current]])
}
set -sr[$spinner][current] = (% (+ $-sr[$spinner][current] $inc) (count $steps))
}
Set the status of the spinner to different outcomes, will be displayed the next time the step
function is called. The definition of the symbols to display can be customized by adding or changing elements in $spinners:persist-symbols
.
var persist-symbols = [
&success= [ &symbol="✔" &color=green ]
&error= [ &symbol="✖" &color=red ]
&warning= [ &symbol="⚠" &color=yellow ]
&info= [ &symbol="ℹ" &color=blue ]
]
fn set-symbol {|spinner symbol|
set -sr[$spinner][frames] = [ $persist-symbols[$symbol][symbol] ]
set -sr[$spinner][style] = [ $persist-symbols[$symbol][color] ]
set -sr[$spinner][current] = 0
}
Wait an amount of time as defined by the spinner’s interval
field.
fn spinner-sleep {|s|
sleep (to-string (/ $-sr[$s][interval] 1000))
}
Persist a spinner by setting its symbol to the one corresponding to its persist
field (if any, otherwise just leave it as-is), and then moving the cursor to the next line and storing the new position as the spinner’s initial-pos
field.
fn persist {|spinner|
if (eq $-sr[$spinner][persist] status) {
if $-sr[$spinner][status] {
set-symbol $spinner success
} else {
set-symbol $spinner error
}
} elif (eq (kind-of $-sr[$spinner][persist]) string) {
set-symbol $spinner $-sr[$spinner][persist]
}
step $spinner
-output "\n"
set -sr[$spinner][initial-pos] = [(tty:cursor-pos)]
}
Individual fields of a spinner can be queried or modified using the spinners:attr
function. If no value is given, returns the value of the attribute, otherwise just sets it (no return value). The spinner
field is treated specially by fetching the corresponding frames
and interval
attributes from the default spinner definitions, instead of just storing its value. It also resets the current
counter to zero, since different spinners have different number of frames.
fn attr {|id attr @val|
if (has-key $-sr $id) {
if (eq $val []) {
put $-sr[$id][$attr]
} else {
if (eq $attr spinner) {
# Automatically populate frames and interval based on spinner
var name = $val[0]
set -sr[$id][spinner] = $name
set -sr[$id][frames] = $spinners[$name][frames]
set -sr[$id][interval] = $spinners[$name][interval]
set -sr[$id][current] = 0
} elif (eq $attr style) {
# Automatically convert non-list styles, so you can do e.g. &style=red
var style = $val[0]
if (not-eq (kind-of $style) list) { set style = [$style] }
set -sr[$id][style] = $style
} else {
set -sr[$id][$attr] = $val[0]
}
}
} else {
fail "Nonexisting spinner with ID "$id
}
}
Start a and stop a background spinner. The spinner is shown by a background function which will run until the spinner’s stop
flag is set.
Note: if you call spinners:start
and spinners:stop
by hand, be aware that when the spinner stops Elvish will produce a notification about the background process. To prevent this you have to set $notify-bg-job-success
to $false
before calling $spinners:stop
. This is not necessary if you use spinners:run
.
The spinners:do-spinner
function is the one that actually does the work of:
- Hiding the cursor if necessary;
- Cycling the spinner until its
stop
field is set (this requires a parallel process that sets this flag eventually); - Persisting the spinner with the appropriate symbol or clearing it, according to its configuration;
- Reissuing any exceptions, if necessary;
- Deleting the spinner definition from the internal registry.
fn do-spinner {|spinner|
if (not $-sr[$spinner][cursor]) {
tty:hide-cursor
}
set -sr[$spinner][initial-pos] = [(tty:cursor-pos)]
while (not $-sr[$spinner][stop]) {
step $spinner
spinner-sleep $spinner
if (has-key $-sr[$spinner] next-spinner-id) {
var next-spinner-id = $-sr[$spinner][next-spinner-id]
# Indicator to persist the current spinner and continue with a new definition
persist $spinner
set -sr[$spinner] = $-sr[$next-spinner-id]
set -sr[$spinner][id] = $spinner
set -sr[$spinner][initial-pos] = [(tty:cursor-pos)]
del -sr[$next-spinner-id]
}
}
if $-sr[$spinner][persist] {
persist $spinner
} else {
tty:set-cursor-pos (all $-sr[$spinner][initial-pos])
tty:clear-line
}
if (not $-sr[$spinner][cursor]) { tty:show-cursor }
if (and (not $-sr[$spinner][status]) (not $-sr[$spinner][hide-exception])) {
show $-sr[$spinner][status]
}
del -sr[$spinner]
}
The spinners:start
function simply calls do-spinner
in the background.
fn start {|spinner|
do-spinner $spinner &
}
Stop execution of a spinner by setting its stop
flag. This will be caught by the spinner process in the background, which does the work of stopping, persisting and removing the spinner. The &status
option should be used to pass an exception object used to set the status of the spinner.
fn stop {|spinner &status=$ok|
set -sr[$spinner][status] = $status
set -sr[$spinner][stop] = $true
}
Simplest point of entry for displaying a spinner while a function is running. Takes a lambda as the only mandatory argument. A spinner will be automatically created and displayed until the lambda finishes. It takes the same options as spinners:new
, which can be used to specify the details of the spinner to use.
fn run {|&spinner=$nil &frames=$nil &interval=$nil &title="" &style=[] &prefix="" &indent=0 &cursor=$false &persist=$false &hide-exception=$false f|
# Create spinner
var s = (new &spinner=$spinner &frames=$frames &interval=$interval &title=$title &style=$style &prefix=$prefix &indent=$indent &cursor=$cursor &persist=$persist &hide-exception=$hide-exception)
# Determine whether to pass the spinner ID to the function
var f-args = [$s]
if (eq $f[arg-names] []) { set f-args = [] }
# Run spinner in parallel with the function
var status = $ok
run-parallel {
do-spinner $s
} {
set status = $ok
try {
$f $@f-args
} catch e {
set status = $e
} finally {
# Short pause to avoid a potential race condition when the
# function finishes too quickly
sleep 0.05
stop &status=$status $s
}
}
}
During the execution of a spinner, the spinners:persist-and-new
function can be used to persist the current spinner (according to its settings), move the cursor to the next line, and then continue with a new spinner. This function accepts the same arguments as spinners:run
, settings from the old spinner are not inherited.
fn persist-and-new {|old-spinner &spinner=$nil &frames=$nil &interval=$nil &title="" &style=[] &prefix="" &indent=0 &cursor=$false &persist=$false &hide-exception=$false|
var new-spinner = (new &spinner=$spinner &frames=$frames &interval=$interval &title=$title &style=$style &prefix=$prefix &indent=$indent &cursor=$cursor &persist=$persist &hide-exception=$hide-exception)
set -sr[$old-spinner][next-spinner-id] = $new-spinner
}
Return the list of available spinners
fn list {
keys $spinners | order
}
Produce all the spinners in sequence.
fn demo {|&time=2 &style=blue &persist=$false|
list | each {|s|
run &spinner=$s &title=$s &style=$style &persist=$persist { sleep $time }
}
}
The spinner definitions are taken from the sindresorhus/cli-spinners project, released under the following license:
MIT License Copyright (c) Sindre Sorhus <[email protected]> (https://sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{
"dots": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏"
]
},
"dots2": {
"interval": 80,
"frames": [
"⣾",
"⣽",
"⣻",
"⢿",
"⡿",
"⣟",
"⣯",
"⣷"
]
},
"dots3": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠚",
"⠞",
"⠖",
"⠦",
"⠴",
"⠲",
"⠳",
"⠓"
]
},
"dots4": {
"interval": 80,
"frames": [
"⠄",
"⠆",
"⠇",
"⠋",
"⠙",
"⠸",
"⠰",
"⠠",
"⠰",
"⠸",
"⠙",
"⠋",
"⠇",
"⠆"
]
},
"dots5": {
"interval": 80,
"frames": [
"⠋",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋"
]
},
"dots6": {
"interval": 80,
"frames": [
"⠁",
"⠉",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠤",
"⠄",
"⠄",
"⠤",
"⠴",
"⠲",
"⠒",
"⠂",
"⠂",
"⠒",
"⠚",
"⠙",
"⠉",
"⠁"
]
},
"dots7": {
"interval": 80,
"frames": [
"⠈",
"⠉",
"⠋",
"⠓",
"⠒",
"⠐",
"⠐",
"⠒",
"⠖",
"⠦",
"⠤",
"⠠",
"⠠",
"⠤",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋",
"⠉",
"⠈"
]
},
"dots8": {
"interval": 80,
"frames": [
"⠁",
"⠁",
"⠉",
"⠙",
"⠚",
"⠒",
"⠂",
"⠂",
"⠒",
"⠲",
"⠴",
"⠤",
"⠄",
"⠄",
"⠤",
"⠠",
"⠠",
"⠤",
"⠦",
"⠖",
"⠒",
"⠐",
"⠐",
"⠒",
"⠓",
"⠋",
"⠉",
"⠈",
"⠈"
]
},
"dots9": {
"interval": 80,
"frames": [
"⢹",
"⢺",
"⢼",
"⣸",
"⣇",
"⡧",
"⡗",
"⡏"
]
},
"dots10": {
"interval": 80,
"frames": [
"⢄",
"⢂",
"⢁",
"⡁",
"⡈",
"⡐",
"⡠"
]
},
"dots11": {
"interval": 100,
"frames": [
"⠁",
"⠂",
"⠄",
"⡀",
"⢀",
"⠠",
"⠐",
"⠈"
]
},
"dots12": {
"interval": 80,
"frames": [
"⢀⠀",
"⡀⠀",
"⠄⠀",
"⢂⠀",
"⡂⠀",
"⠅⠀",
"⢃⠀",
"⡃⠀",
"⠍⠀",
"⢋⠀",
"⡋⠀",
"⠍⠁",
"⢋⠁",
"⡋⠁",
"⠍⠉",
"⠋⠉",
"⠋⠉",
"⠉⠙",
"⠉⠙",
"⠉⠩",
"⠈⢙",
"⠈⡙",
"⢈⠩",
"⡀⢙",
"⠄⡙",
"⢂⠩",
"⡂⢘",
"⠅⡘",
"⢃⠨",
"⡃⢐",
"⠍⡐",
"⢋⠠",
"⡋⢀",
"⠍⡁",
"⢋⠁",
"⡋⠁",
"⠍⠉",
"⠋⠉",
"⠋⠉",
"⠉⠙",
"⠉⠙",
"⠉⠩",
"⠈⢙",
"⠈⡙",
"⠈⠩",
"⠀⢙",
"⠀⡙",
"⠀⠩",
"⠀⢘",
"⠀⡘",
"⠀⠨",
"⠀⢐",
"⠀⡐",
"⠀⠠",
"⠀⢀",
"⠀⡀"
]
},
"dots8Bit": {
"interval": 80,
"frames": [
"⠀",
"⠁",
"⠂",
"⠃",
"⠄",
"⠅",
"⠆",
"⠇",
"⡀",
"⡁",
"⡂",
"⡃",
"⡄",
"⡅",
"⡆",
"⡇",
"⠈",
"⠉",
"⠊",
"⠋",
"⠌",
"⠍",
"⠎",
"⠏",
"⡈",
"⡉",
"⡊",
"⡋",
"⡌",
"⡍",
"⡎",
"⡏",
"⠐",
"⠑",
"⠒",
"⠓",
"⠔",
"⠕",
"⠖",
"⠗",
"⡐",
"⡑",
"⡒",
"⡓",
"⡔",
"⡕",
"⡖",
"⡗",
"⠘",
"⠙",
"⠚",
"⠛",
"⠜",
"⠝",
"⠞",
"⠟",
"⡘",
"⡙",
"⡚",
"⡛",
"⡜",
"⡝",
"⡞",
"⡟",
"⠠",
"⠡",
"⠢",
"⠣",
"⠤",
"⠥",
"⠦",
"⠧",
"⡠",
"⡡",
"⡢",
"⡣",
"⡤",
"⡥",
"⡦",
"⡧",
"⠨",
"⠩",
"⠪",
"⠫",
"⠬",
"⠭",
"⠮",
"⠯",
"⡨",
"⡩",
"⡪",
"⡫",
"⡬",
"⡭",
"⡮",
"⡯",
"⠰",
"⠱",
"⠲",
"⠳",
"⠴",
"⠵",
"⠶",
"⠷",
"⡰",
"⡱",
"⡲",
"⡳",
"⡴",
"⡵",
"⡶",
"⡷",
"⠸",
"⠹",
"⠺",
"⠻",
"⠼",
"⠽",
"⠾",
"⠿",
"⡸",
"⡹",
"⡺",
"⡻",
"⡼",
"⡽",
"⡾",
"⡿",
"⢀",
"⢁",
"⢂",
"⢃",
"⢄",
"⢅",
"⢆",
"⢇",
"⣀",
"⣁",
"⣂",
"⣃",
"⣄",
"⣅",
"⣆",
"⣇",
"⢈",
"⢉",
"⢊",
"⢋",
"⢌",
"⢍",
"⢎",
"⢏",
"⣈",
"⣉",
"⣊",
"⣋",
"⣌",
"⣍",
"⣎",
"⣏",
"⢐",
"⢑",
"⢒",
"⢓",
"⢔",
"⢕",
"⢖",
"⢗",
"⣐",
"⣑",
"⣒",
"⣓",
"⣔",
"⣕",
"⣖",
"⣗",
"⢘",
"⢙",
"⢚",
"⢛",
"⢜",
"⢝",
"⢞",
"⢟",
"⣘",
"⣙",
"⣚",
"⣛",
"⣜",
"⣝",
"⣞",
"⣟",
"⢠",
"⢡",
"⢢",
"⢣",
"⢤",
"⢥",
"⢦",
"⢧",
"⣠",
"⣡",
"⣢",
"⣣",
"⣤",
"⣥",
"⣦",
"⣧",
"⢨",
"⢩",
"⢪",
"⢫",
"⢬",
"⢭",
"⢮",
"⢯",
"⣨",
"⣩",
"⣪",
"⣫",
"⣬",
"⣭",
"⣮",
"⣯",
"⢰",
"⢱",
"⢲",
"⢳",
"⢴",
"⢵",
"⢶",
"⢷",
"⣰",
"⣱",
"⣲",
"⣳",
"⣴",
"⣵",
"⣶",
"⣷",
"⢸",
"⢹",
"⢺",
"⢻",
"⢼",
"⢽",
"⢾",
"⢿",
"⣸",
"⣹",
"⣺",
"⣻",
"⣼",
"⣽",
"⣾",
"⣿"
]
},
"line": {
"interval": 130,
"frames": [
"-",
"\\",
"|",
"/"
]
},
"line2": {
"interval": 100,
"frames": [
"⠂",
"-",
"–",
"—",
"–",
"-"
]
},
"pipe": {
"interval": 100,
"frames": [
"┤",
"┘",
"┴",
"└",
"├",
"┌",
"┬",
"┐"
]
},
"simpleDots": {
"interval": 400,
"frames": [
". ",
".. ",
"...",
" "
]
},
"simpleDotsScrolling": {
"interval": 200,
"frames": [
". ",
".. ",
"...",
" ..",
" .",
" "
]
},
"star": {
"interval": 70,
"frames": [
"✶",
"✸",
"✹",
"✺",
"✹",
"✷"
]
},
"star2": {
"interval": 80,
"frames": [
"+",
"x",
"*"
]
},
"flip": {
"interval": 70,
"frames": [
"_",
"_",
"_",
"-",
"`",
"`",
"'",
"´",
"-",
"_",
"_",
"_"
]
},
"hamburger": {
"interval": 100,
"frames": [
"☱",
"☲",
"☴"
]
},
"growVertical": {
"interval": 120,
"frames": [
"▁",
"▃",
"▄",
"▅",
"▆",
"▇",
"▆",
"▅",
"▄",
"▃"
]
},
"growHorizontal": {
"interval": 120,
"frames": [
"▏",
"▎",
"▍",
"▌",
"▋",
"▊",
"▉",
"▊",
"▋",
"▌",
"▍",
"▎"
]
},
"balloon": {
"interval": 140,
"frames": [
" ",
".",
"o",
"O",
"@",
"*",
" "
]
},
"balloon2": {
"interval": 120,
"frames": [
".",
"o",
"O",
"°",
"O",
"o",
"."
]
},
"noise": {
"interval": 100,
"frames": [
"▓",
"▒",
"░"
]
},
"bounce": {
"interval": 120,
"frames": [
"⠁",
"⠂",
"⠄",
"⠂"
]
},
"boxBounce": {
"interval": 120,
"frames": [
"▖",
"▘",
"▝",
"▗"
]
},
"boxBounce2": {
"interval": 100,
"frames": [
"▌",
"▀",
"▐",
"▄"
]
},
"triangle": {
"interval": 50,
"frames": [
"◢",
"◣",
"◤",
"◥"
]
},
"arc": {
"interval": 100,
"frames": [
"◜",
"◠",
"◝",
"◞",
"◡",
"◟"
]
},
"circle": {
"interval": 120,
"frames": [
"◡",
"⊙",
"◠"
]
},
"squareCorners": {
"interval": 180,
"frames": [
"◰",
"◳",
"◲",
"◱"
]
},
"circleQuarters": {
"interval": 120,
"frames": [
"◴",
"◷",
"◶",
"◵"
]
},
"circleHalves": {
"interval": 50,
"frames": [
"◐",
"◓",
"◑",
"◒"
]
},
"squish": {
"interval": 100,
"frames": [
"╫",
"╪"
]
},
"toggle": {
"interval": 250,
"frames": [
"⊶",
"⊷"
]
},
"toggle2": {
"interval": 80,
"frames": [
"▫",
"▪"
]
},
"toggle3": {
"interval": 120,
"frames": [
"□",
"■"
]
},
"toggle4": {
"interval": 100,
"frames": [
"■",
"□",
"▪",
"▫"
]
},
"toggle5": {
"interval": 100,
"frames": [
"▮",
"▯"
]
},
"toggle6": {
"interval": 300,
"frames": [
"ဝ",
"၀"
]
},
"toggle7": {
"interval": 80,
"frames": [
"⦾",
"⦿"
]
},
"toggle8": {
"interval": 100,
"frames": [
"◍",
"◌"
]
},
"toggle9": {
"interval": 100,
"frames": [
"◉",
"◎"
]
},
"toggle10": {
"interval": 100,
"frames": [
"㊂",
"㊀",
"㊁"
]
},
"toggle11": {
"interval": 50,
"frames": [
"⧇",
"⧆"
]
},
"toggle12": {
"interval": 120,
"frames": [
"☗",
"☖"
]
},
"toggle13": {
"interval": 80,
"frames": [
"=",
"*",
"-"
]
},
"arrow": {
"interval": 100,
"frames": [
"←",
"↖",
"↑",
"↗",
"→",
"↘",
"↓",
"↙"
]
},
"arrow2": {
"interval": 80,
"frames": [
"⬆️ ",
"↗️ ",
"➡️ ",
"↘️ ",
"⬇️ ",
"↙️ ",
"⬅️ ",
"↖️ "
]
},
"arrow3": {
"interval": 120,
"frames": [
"▹▹▹▹▹",
"▸▹▹▹▹",
"▹▸▹▹▹",
"▹▹▸▹▹",
"▹▹▹▸▹",
"▹▹▹▹▸"
]
},
"bouncingBar": {
"interval": 80,
"frames": [
"[ ]",
"[= ]",
"[== ]",
"[=== ]",
"[ ===]",
"[ ==]",
"[ =]",
"[ ]",
"[ =]",
"[ ==]",
"[ ===]",
"[====]",
"[=== ]",
"[== ]",
"[= ]"
]
},
"bouncingBall": {
"interval": 80,
"frames": [
"( ● )",
"( ● )",
"( ● )",
"( ● )",
"( ●)",
"( ● )",
"( ● )",
"( ● )",
"( ● )",
"(● )"
]
},
"smiley": {
"interval": 200,
"frames": [
"😄 ",
"😝 "
]
},
"monkey": {
"interval": 300,
"frames": [
"🙈 ",
"🙈 ",
"🙉 ",
"🙊 "
]
},
"hearts": {
"interval": 100,
"frames": [
"💛 ",
"💙 ",
"💜 ",
"💚 ",
"❤️ "
]
},
"clock": {
"interval": 100,
"frames": [
"🕛 ",
"🕐 ",
"🕑 ",
"🕒 ",
"🕓 ",
"🕔 ",
"🕕 ",
"🕖 ",
"🕗 ",
"🕘 ",
"🕙 ",
"🕚 "
]
},
"earth": {
"interval": 180,
"frames": [
"🌍 ",
"🌎 ",
"🌏 "
]
},
"material": {
"interval": 17,
"frames": [
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"███████▁▁▁▁▁▁▁▁▁▁▁▁▁",
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"██████████▁▁▁▁▁▁▁▁▁▁",
"███████████▁▁▁▁▁▁▁▁▁",
"█████████████▁▁▁▁▁▁▁",
"██████████████▁▁▁▁▁▁",
"██████████████▁▁▁▁▁▁",
"▁██████████████▁▁▁▁▁",
"▁██████████████▁▁▁▁▁",
"▁██████████████▁▁▁▁▁",
"▁▁██████████████▁▁▁▁",
"▁▁▁██████████████▁▁▁",
"▁▁▁▁█████████████▁▁▁",
"▁▁▁▁██████████████▁▁",
"▁▁▁▁██████████████▁▁",
"▁▁▁▁▁██████████████▁",
"▁▁▁▁▁██████████████▁",
"▁▁▁▁▁██████████████▁",
"▁▁▁▁▁▁██████████████",
"▁▁▁▁▁▁██████████████",
"▁▁▁▁▁▁▁█████████████",
"▁▁▁▁▁▁▁█████████████",
"▁▁▁▁▁▁▁▁████████████",
"▁▁▁▁▁▁▁▁████████████",
"▁▁▁▁▁▁▁▁▁███████████",
"▁▁▁▁▁▁▁▁▁███████████",
"▁▁▁▁▁▁▁▁▁▁██████████",
"▁▁▁▁▁▁▁▁▁▁██████████",
"▁▁▁▁▁▁▁▁▁▁▁▁████████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"█████████▁▁▁▁▁▁▁▁▁▁▁",
"███████████▁▁▁▁▁▁▁▁▁",
"████████████▁▁▁▁▁▁▁▁",
"████████████▁▁▁▁▁▁▁▁",
"██████████████▁▁▁▁▁▁",
"██████████████▁▁▁▁▁▁",
"▁██████████████▁▁▁▁▁",
"▁██████████████▁▁▁▁▁",
"▁▁▁█████████████▁▁▁▁",
"▁▁▁▁▁████████████▁▁▁",
"▁▁▁▁▁████████████▁▁▁",
"▁▁▁▁▁▁███████████▁▁▁",
"▁▁▁▁▁▁▁▁█████████▁▁▁",
"▁▁▁▁▁▁▁▁█████████▁▁▁",
"▁▁▁▁▁▁▁▁▁█████████▁▁",
"▁▁▁▁▁▁▁▁▁█████████▁▁",
"▁▁▁▁▁▁▁▁▁▁█████████▁",
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁"
]
},
"moon": {
"interval": 80,
"frames": [
"🌑 ",
"🌒 ",
"🌓 ",
"🌔 ",
"🌕 ",
"🌖 ",
"🌗 ",
"🌘 "
]
},
"runner": {
"interval": 140,
"frames": [
"🚶 ",
"🏃 "
]
},
"pong": {
"interval": 80,
"frames": [
"▐⠂ ▌",
"▐⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂▌",
"▐ ⠠▌",
"▐ ⡀▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐ ⠠ ▌",
"▐ ⠂ ▌",
"▐ ⠈ ▌",
"▐ ⠂ ▌",
"▐ ⠠ ▌",
"▐ ⡀ ▌",
"▐⠠ ▌"
]
},
"shark": {
"interval": 120,
"frames": [
"▐|\\____________▌",
"▐_|\\___________▌",
"▐__|\\__________▌",
"▐___|\\_________▌",
"▐____|\\________▌",
"▐_____|\\_______▌",
"▐______|\\______▌",
"▐_______|\\_____▌",
"▐________|\\____▌",
"▐_________|\\___▌",
"▐__________|\\__▌",
"▐___________|\\_▌",
"▐____________|\\▌",
"▐____________/|▌",
"▐___________/|_▌",
"▐__________/|__▌",
"▐_________/|___▌",
"▐________/|____▌",
"▐_______/|_____▌",
"▐______/|______▌",
"▐_____/|_______▌",
"▐____/|________▌",
"▐___/|_________▌",
"▐__/|__________▌",
"▐_/|___________▌",
"▐/|____________▌"
]
},
"dqpb": {
"interval": 100,
"frames": [
"d",
"q",
"p",
"b"
]
},
"weather": {
"interval": 100,
"frames": [
"☀️ ",
"☀️ ",
"☀️ ",
"🌤 ",
"⛅️ ",
"🌥 ",
"☁️ ",
"🌧 ",
"🌨 ",
"🌧 ",
"🌨 ",
"🌧 ",
"🌨 ",
"⛈ ",
"🌨 ",
"🌧 ",
"🌨 ",
"☁️ ",
"🌥 ",
"⛅️ ",
"🌤 ",
"☀️ ",
"☀️ "
]
},
"christmas": {
"interval": 400,
"frames": [
"🌲",
"🎄"
]
},
"grenade": {
"interval": 80,
"frames": [
"، ",
"′ ",
" ´ ",
" ‾ ",
" ⸌",
" ⸊",
" |",
" ⁎",
" ⁕",
" ෴ ",
" ⁓",
" ",
" ",
" "
]
},
"point": {
"interval": 125,
"frames": [
"∙∙∙",
"●∙∙",
"∙●∙",
"∙∙●",
"∙∙∙"
]
},
"layer": {
"interval": 150,
"frames": [
"-",
"=",
"≡"
]
},
"betaWave": {
"interval": 80,
"frames": [
"ρββββββ",
"βρβββββ",
"ββρββββ",
"βββρβββ",
"ββββρββ",
"βββββρβ",
"ββββββρ"
]
}
}