aboutsummaryrefslogtreecommitdiff
path: root/stow
diff options
context:
space:
mode:
Diffstat (limited to 'stow')
-rw-r--r--stow/XserverDotfiles/dot-xinitrcbspwm1
-rw-r--r--stow/XserverDotfiles/dot-xinitrcdwm2
-rw-r--r--stow/XserverDotfiles/dot-xinitrcgnome3
-rw-r--r--stow/XserverDotfiles/dot-xinitrci31
-rw-r--r--stow/XserverDotfiles/dot-xinitrcpostintel0
-rw-r--r--stow/XserverDotfiles/dot-xinitrcpostnvidia2
-rw-r--r--stow/XserverDotfiles/dot-xinitrcprevintel0
-rw-r--r--stow/XserverDotfiles/dot-xinitrcprevnvidia4
-rw-r--r--stow/XserverDotfiles/dot-xinitrcxfce41
-rwxr-xr-xstow/bspwm/dot-config/bspwm/bspwmrc28
-rw-r--r--stow/cmus/dot-config/cmus/autosave217
-rw-r--r--stow/colorschemes/dot-config/colorschemes/customBlue29
-rw-r--r--stow/colorschemes/dot-config/colorschemes/gruvbox31
-rw-r--r--stow/colorschemes/dot-config/colorschemes/nord47
-rw-r--r--stow/dunst/dot-config/dunst/dunstifyIDs12
-rw-r--r--stow/dunst/dot-config/dunst/dunstrc94
-rw-r--r--stow/emoji/dot-config/emoji4267
-rw-r--r--stow/i3/dot-config/i3/config279
-rw-r--r--stow/i3blocks/dot-config/i3blocks/config101
-rw-r--r--stow/mpv/dot-config/mpv/input.conf213
-rw-r--r--stow/mpv/dot-config/mpv/mpv.conf21
-rw-r--r--stow/mpv/dot-config/mpv/scripts-opts/mpv_thumbnail_script.conf70
-rw-r--r--stow/mpv/dot-config/mpv/scripts-opts/youtube-quality.conf41
-rw-r--r--stow/mpv/dot-config/mpv/scripts/mpv-splice.lua328
-rw-r--r--stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_client_osc.lua3886
-rw-r--r--stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_server.lua736
-rw-r--r--stow/mpv/dot-config/mpv/scripts/youtube-quality.lua275
-rw-r--r--stow/neofetch/dot-config/neofetch/config.conf764
-rw-r--r--stow/neofetch/dot-config/neofetch/neofetchLogin.conf63
-rw-r--r--stow/newsboat/dot-config/newsboat/config11
-rw-r--r--stow/nvim/dot-config/nvim/init.vim3
-rw-r--r--stow/pandoc/dot-config/pandoc/luascripts/diagram-generator.lua299
-rw-r--r--stow/picom/dot-config/picom/picom.conf502
-rw-r--r--stow/picom/dot-config/picom/picom.jonaburg.sample.conf512
-rw-r--r--stow/picom/dot-config/picom/picom.upstream.conf235
-rw-r--r--stow/qutebrowser/dot-config/qutebrowser/config.py1729
-rw-r--r--stow/ranger/dot-config/ranger/rc.conf640
-rw-r--r--stow/ranger/dot-config/ranger/rifle.conf226
-rwxr-xr-xstow/ranger/dot-config/ranger/scope.sh350
-rw-r--r--stow/rcs/dot-Xresources40
-rw-r--r--stow/rcs/dot-bash_aliases44
-rw-r--r--stow/rcs/dot-bash_vars87
-rw-r--r--stow/rcs/dot-bashrc66
-rw-r--r--stow/rcs/dot-config/ttymaps.kmap2
-rw-r--r--stow/rcs/dot-inputrc16
-rw-r--r--stow/rcs/dot-profile46
-rw-r--r--stow/sxhkd/dot-config/sxhkd/sxhkdrc129
-rw-r--r--stow/sxhkd/dot-config/sxhkd/sxhkdrcbspc92
-rwxr-xr-xstow/sxiv/dot-config/sxiv/exec/image-info18
-rwxr-xr-xstow/sxiv/dot-config/sxiv/exec/key-handler128
-rw-r--r--stow/tmux/dot-tmux.conf70
-rw-r--r--stow/vim/dot-vim/autoload/plug.vim2788
-rw-r--r--stow/vim/dot-vim/autoload/plug.vim.old2788
-rw-r--r--stow/vim/dot-vim/colors/burnttoast256.vim116
-rw-r--r--stow/vim/dot-vim/colors/nord.vim764
-rw-r--r--stow/vim/dot-vim/ftplugin/antlr/antlr3.vim91
-rw-r--r--stow/vim/dot-vim/ftplugin/antlr/antlr4.vim76
-rw-r--r--stow/vim/dot-vim/ftplugin/asciidoc/asciidoc.vim19
-rw-r--r--stow/vim/dot-vim/ftplugin/cpp/cpp.vim28
-rw-r--r--stow/vim/dot-vim/ftplugin/cpp/snips/template.txt6
-rw-r--r--stow/vim/dot-vim/ftplugin/css/css.vim20
-rw-r--r--stow/vim/dot-vim/ftplugin/html/html.vim78
-rw-r--r--stow/vim/dot-vim/ftplugin/html/snips/template.txt12
-rw-r--r--stow/vim/dot-vim/ftplugin/java/java.vim14
-rw-r--r--stow/vim/dot-vim/ftplugin/javascript/javascript.vim38
-rw-r--r--stow/vim/dot-vim/ftplugin/plantuml/plantuml.vim20
-rw-r--r--stow/vim/dot-vim/ftplugin/plantuml/snips/template.txt9
-rw-r--r--stow/vim/dot-vim/ftplugin/python/python.vim34
-rw-r--r--stow/vim/dot-vim/ftplugin/python/snips/class3
-rw-r--r--stow/vim/dot-vim/ftplugin/python/snips/template5
-rw-r--r--stow/vim/dot-vim/ftplugin/sent/sent.vim19
-rw-r--r--stow/vim/dot-vim/ftplugin/sh/sh.vim16
-rw-r--r--stow/vim/dot-vim/ftplugin/sh/snippets/if.txt3
-rw-r--r--stow/vim/dot-vim/ftplugin/tex/snips/template.txt30
-rw-r--r--stow/vim/dot-vim/ftplugin/tex/tex.vim45
-rw-r--r--stow/vim/dot-vim/ftplugin/text/text.vim20
-rw-r--r--stow/vim/dot-vim/ftplugin/typescript/typescript.vim33
-rw-r--r--stow/vim/dot-vim/ftplugin/vimwiki/snips/plantUml.txt5
-rw-r--r--stow/vim/dot-vim/ftplugin/vimwiki/snips/template.txt15
-rw-r--r--stow/vim/dot-vim/ftplugin/vimwiki/vimwiki.vim39
-rw-r--r--stow/vim/dot-vim/plugin/SWTC.vim339
-rw-r--r--stow/vim/dot-vim/plugin/dragvisuals.vim345
-rw-r--r--stow/vim/dot-vim/plugin/rng.vim83
-rw-r--r--stow/vim/dot-vimrc280
-rw-r--r--stow/vis/dot-config/vis/colors/basic_colors15
-rw-r--r--stow/vis/dot-config/vis/colors/rainbow30
-rw-r--r--stow/vis/dot-config/vis/config91
-rw-r--r--stow/zathura/dot-config/zathura/zathurarc58
88 files changed, 25136 insertions, 0 deletions
diff --git a/stow/XserverDotfiles/dot-xinitrcbspwm b/stow/XserverDotfiles/dot-xinitrcbspwm
new file mode 100644
index 0000000..ac7f120
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcbspwm
@@ -0,0 +1 @@
+exec bspwm
diff --git a/stow/XserverDotfiles/dot-xinitrcdwm b/stow/XserverDotfiles/dot-xinitrcdwm
new file mode 100644
index 0000000..e59268d
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcdwm
@@ -0,0 +1,2 @@
+dwmblocks &
+xinit.sh; exec dwm
diff --git a/stow/XserverDotfiles/dot-xinitrcgnome b/stow/XserverDotfiles/dot-xinitrcgnome
new file mode 100644
index 0000000..74f16d6
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcgnome
@@ -0,0 +1,3 @@
+export XDG_SESSION_TYPE=x11
+export GDK_BACKEND=x11
+xinit.sh; exec gnome-session
diff --git a/stow/XserverDotfiles/dot-xinitrci3 b/stow/XserverDotfiles/dot-xinitrci3
new file mode 100644
index 0000000..ba0c6ef
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrci3
@@ -0,0 +1 @@
+exec i3
diff --git a/stow/XserverDotfiles/dot-xinitrcpostintel b/stow/XserverDotfiles/dot-xinitrcpostintel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcpostintel
diff --git a/stow/XserverDotfiles/dot-xinitrcpostnvidia b/stow/XserverDotfiles/dot-xinitrcpostnvidia
new file mode 100644
index 0000000..7412099
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcpostnvidia
@@ -0,0 +1,2 @@
+sudo /usr/bin/prime-switch
+
diff --git a/stow/XserverDotfiles/dot-xinitrcprevintel b/stow/XserverDotfiles/dot-xinitrcprevintel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcprevintel
diff --git a/stow/XserverDotfiles/dot-xinitrcprevnvidia b/stow/XserverDotfiles/dot-xinitrcprevnvidia
new file mode 100644
index 0000000..18e2dfc
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcprevnvidia
@@ -0,0 +1,4 @@
+xrandr --setprovideroutputsource modesetting NVIDIA-0
+xrandr --auto
+xrandr --dpi 96
+/usr/bin/prime-offload
diff --git a/stow/XserverDotfiles/dot-xinitrcxfce4 b/stow/XserverDotfiles/dot-xinitrcxfce4
new file mode 100644
index 0000000..d6c8552
--- /dev/null
+++ b/stow/XserverDotfiles/dot-xinitrcxfce4
@@ -0,0 +1 @@
+exec xfce4-session
diff --git a/stow/bspwm/dot-config/bspwm/bspwmrc b/stow/bspwm/dot-config/bspwm/bspwmrc
new file mode 100755
index 0000000..5dd20e5
--- /dev/null
+++ b/stow/bspwm/dot-config/bspwm/bspwmrc
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+desktops="I II III IV V"
+
+if xrandr | grep -q 'HDMI-1-1 connected'; then
+ bspc monitor '^1' -d $desktops
+ bspc monitor '^2' -d $desktops
+else
+ bspc monitor -d $desktops
+fi
+
+bspc config border_width 4
+bspc config window_gap 10
+
+bspc config split_ratio 0.50
+bspc config borderless_monocle true
+bspc config gapless_monocle true
+
+#bspc rule -a Gimp desktop='^8' state=floating follow=on
+#bspc rule -a Chromium desktop='^2'
+#bspc rule -a mplayer2 state=floating
+#bspc rule -a mpv state=floating
+#bspc rule -a Kupfer.py focus=on
+bspc rule -a Screenkey manage=off
+
+pgrep -x sxhkd > /dev/null || sxhkd "$XDG_CONFIG_HOME/sxhkd/sxhkdrcbspc" >~/logs/sxhkd.log &
+
+xinit.sh
diff --git a/stow/cmus/dot-config/cmus/autosave b/stow/cmus/dot-config/cmus/autosave
new file mode 100644
index 0000000..86d1436
--- /dev/null
+++ b/stow/cmus/dot-config/cmus/autosave
@@ -0,0 +1,217 @@
+set aaa_mode=all
+set altformat_current= %F
+set altformat_playlist= %f%= %d
+set altformat_title=%f
+set altformat_trackwin= %f%= %d
+set auto_expand_albums_follow=true
+set auto_expand_albums_search=true
+set auto_expand_albums_selcur=true
+set auto_reshuffle=true
+set buffer_seconds=10
+set color_cmdline_attr=default
+set color_cmdline_bg=default
+set color_cmdline_fg=default
+set color_cur_sel_attr=default
+set color_error=lightred
+set color_info=lightcyan
+set color_separator=blue
+set color_statusline_attr=default
+set color_statusline_bg=gray
+set color_statusline_fg=black
+set color_titleline_attr=default
+set color_titleline_bg=blue
+set color_titleline_fg=white
+set color_trackwin_album_attr=bold
+set color_trackwin_album_bg=default
+set color_trackwin_album_fg=default
+set color_win_attr=default
+set color_win_bg=default
+set color_win_cur=lightgreen
+set color_win_cur_attr=default
+set color_win_cur_sel_attr=default
+set color_win_cur_sel_bg=blue
+set color_win_cur_sel_fg=lightgreen
+set color_win_dir=lightblue
+set color_win_fg=default
+set color_win_inactive_cur_sel_attr=default
+set color_win_inactive_cur_sel_bg=lightblue
+set color_win_inactive_cur_sel_fg=lightgreen
+set color_win_inactive_sel_attr=default
+set color_win_inactive_sel_bg=lightblue
+set color_win_inactive_sel_fg=black
+set color_win_sel_attr=default
+set color_win_sel_bg=blue
+set color_win_sel_fg=white
+set color_win_title_attr=default
+set color_win_title_bg=blue
+set color_win_title_fg=white
+set confirm_run=true
+set continue=true
+set continue_album=true
+set device=/dev/cdrom
+set display_artist_sort_name=false
+set dsp.alsa.device=
+set dsp.ao.buffer_size=16384
+set dsp.ao.device_interface=
+set dsp.ao.driver=
+set dsp.ao.wav_counter=1
+set dsp.ao.wav_dir=/home/taamas
+set dsp.jack.resampling_quality=2
+set dsp.jack.server_name=
+set dsp.oss.device=
+set follow=true
+set format_current= %a - %l -%3n. %t%= %y
+set format_playlist= %-21%a %3n. %t%= %y %d %{?X!=0?%3X ? }
+set format_playlist_va= %-21%A %3n. %t (%a)%= %y %d %{?X!=0?%3X ? }
+set format_statusline= %{status} %{?show_playback_position?%{position} %{?duration?/ %{duration} }?%{?duration?%{duration} }}- %{total} %{?bpm>0?at %{bpm} BPM }%{?volume>=0?vol: %{?lvolume!=rvolume?%{lvolume},%{rvolume} ?%{volume} }}%{?stream?buf: %{buffer} }%{?show_current_bitrate & bitrate>=0? %{bitrate} kbps }%=%{?repeat_current?repeat current?%{?play_library?%{playlist_mode} from %{?play_sorted?sorted }library?playlist}} | %1{continue}%1{follow}%1{repeat}%1{shuffle}
+set format_title=%a - %l - %t (%y)
+set format_trackwin=%3n. %t%= %y %d
+set format_trackwin_album= %l %= %{albumduration}
+set format_trackwin_va=%3n. %t (%a)%= %y %d
+set format_treewin= %l
+set format_treewin_artist=%a
+set icecast_default_charset=ISO-8859-1
+set id3_default_charset=ISO-8859-1
+set input.aac.priority=50
+set input.cdio.cddb_url=freedb.freedb.org:8880
+set input.cdio.priority=50
+set input.cue.priority=50
+set input.ffmpeg.priority=30
+set input.flac.priority=50
+set input.mad.priority=55
+set input.modplug.priority=50
+set input.mpc.priority=50
+set input.opus.priority=50
+set input.vorbis.priority=50
+set input.wav.priority=50
+set input.wavpack.priority=50
+set lib_add_filter=
+set lib_sort=albumartist date album discnumber tracknumber title filename play_count
+set mixer.alsa.channel=
+set mixer.alsa.device=
+set mixer.oss.channel=PCM
+set mixer.oss.device=
+set mixer.pulse.restore_volume=1
+set mouse=false
+set mpris=true
+set output_plugin=pulse
+set passwd=
+set pl_sort=
+set play_library=true
+set play_sorted=false
+set repeat=true
+set repeat_current=false
+set replaygain=disabled
+set replaygain_limit=true
+set replaygain_preamp=0.000000
+set resume=false
+set rewind_offset=5
+set scroll_offset=2
+set set_term_title=true
+set show_all_tracks=true
+set show_current_bitrate=false
+set show_hidden=false
+set show_playback_position=true
+set show_remaining_time=false
+set shuffle=true
+set skip_track_info=false
+set smart_artist_sort=true
+set softvol=false
+set softvol_state=0 0
+set start_view=tree
+set status_display_program=
+set stop_after_queue=false
+set time_show_leading_zero=false
+set tree_width_max=0
+set tree_width_percent=33
+set wrap_search=true
+bind browser backspace browser-up
+bind browser i toggle show_hidden
+bind browser space win-activate
+bind browser u win-update
+bind common ! push shell
+bind common + vol +10%
+bind common , seek -1m
+bind common - vol -10%
+bind common . seek +1m
+bind common / search-start
+bind common 1 view tree
+bind common 2 view sorted
+bind common 3 view playlist
+bind common 4 view queue
+bind common 5 view browser
+bind common 6 view filters
+bind common 7 view settings
+bind common = vol +10%
+bind common ? search-b-start
+bind common C toggle continue
+bind common D win-remove
+bind common E win-add-Q
+bind common F push filter
+bind common G win-bottom
+bind common I echo {}
+bind common L push live-filter
+bind common M toggle play_library
+bind common N search-prev
+bind common P win-mv-before
+bind common U win-update-cache
+bind common [ vol +1% +0
+bind common ] vol +0 +1%
+bind common ^B win-page-up
+bind common ^C echo Type :quit<enter> to exit cmus.
+bind common ^D win-half-page-down
+bind common ^E win-scroll-down
+bind common ^F win-page-down
+bind common ^L refresh
+bind common ^R toggle repeat_current
+bind common ^U win-half-page-up
+bind common ^Y win-scroll-up
+bind common a win-add-l
+bind common b player-next
+bind common c player-pause
+bind common delete win-remove
+bind common down win-down
+bind common e win-add-q
+bind common end win-bottom
+bind common enter win-activate
+bind common f toggle follow
+bind common g win-top
+bind common h seek -5
+bind common home win-top
+bind common i win-sel-cur
+bind common j win-down
+bind common k win-up
+bind common l seek +5
+bind common left seek -5
+bind common m toggle aaa_mode
+bind common mlb_click_selected win-activate
+bind common mouse_scroll_down win-down
+bind common mouse_scroll_up win-up
+bind common n search-next
+bind common o toggle play_sorted
+bind common p win-mv-after
+bind common page_down win-page-down
+bind common page_up win-page-up
+bind common q quit -i
+bind common r toggle repeat
+bind common right seek +5
+bind common s toggle shuffle
+bind common space win-toggle
+bind common t toggle show_remaining_time
+bind common tab win-next
+bind common u update-cache
+bind common up win-up
+bind common v player-stop
+bind common x player-play
+bind common y win-add-p
+bind common z player-prev
+bind common { vol -1% -0
+bind common } vol -0 -1%
+fset 90s=date>=1990&date<2000
+fset classical=genre="Classical"
+fset missing-tag=!stream&(artist=""|album=""|title=""|tracknumber=-1|date=-1)
+fset mp3=filename="*.mp3"
+fset ogg=filename="*.ogg"
+fset ogg-or-mp3=ogg|mp3
+fset unheard=play_count=0
+factivate
diff --git a/stow/colorschemes/dot-config/colorschemes/customBlue b/stow/colorschemes/dot-config/colorschemes/customBlue
new file mode 100644
index 0000000..a96475f
--- /dev/null
+++ b/stow/colorschemes/dot-config/colorschemes/customBlue
@@ -0,0 +1,29 @@
+!Custom blue
+!*background: #181820
+*background: #232f40
+!*foreground: #ebdbb2
+*foreground: #ffffff
+! Black + DarkGrey
+*color0: #000000
+*color8: #181820
+! DarkRed + Red
+*color1: #ff3333
+*color9: #ff6666
+! DarkGreen + Green
+*color2: #66ff66
+*color10: #b2ff66
+! DarkYellow + Yellow
+*color3: #fabd2f
+*color11: #ffff66
+! DarkBlue + Blue
+*color4: #6666ff
+*color12: #66b2ff
+! DarkMagenta + Magenta
+*color5: #ff3366
+*color13: #ff66bb
+! DarkCyan + Cyan
+*color6: #33ddff
+*color14: #66ffff
+! LightGrey + White
+*color7: #ebdbb2
+*color15: #ffffff
diff --git a/stow/colorschemes/dot-config/colorschemes/gruvbox b/stow/colorschemes/dot-config/colorschemes/gruvbox
new file mode 100644
index 0000000..5c83041
--- /dev/null
+++ b/stow/colorschemes/dot-config/colorschemes/gruvbox
@@ -0,0 +1,31 @@
+!Gruvbox
+*background: #1d2021
+!*background: #32302f
+!*background: #282828
+!*foreground: #d5c4a1
+*foreground: #fbf1c7
+!*foreground: #ebdbb2
+! Black + DarkGrey
+*color0: #282828
+*color8: #928374
+! DarkRed + Red
+*color1: #cc241d
+*color9: #fb4934
+! DarkGreen + Green
+*color2: #98971a
+*color10: #b8bb26
+! DarkYellow + Yellow
+*color3: #d79921
+*color11: #fabd2f
+! DarkBlue + Blue
+*color4: #358588
+*color12: #83a598
+! DarkMagenta + Magenta
+*color5: #b16286
+*color13: #d3869b
+! DarkCyan + Cyan
+*color6: #689d6a
+*color14: #8ec07c
+! LightGrey + White
+*color7: #a89984
+*color15: #ebdbb2
diff --git a/stow/colorschemes/dot-config/colorschemes/nord b/stow/colorschemes/dot-config/colorschemes/nord
new file mode 100644
index 0000000..916a0ff
--- /dev/null
+++ b/stow/colorschemes/dot-config/colorschemes/nord
@@ -0,0 +1,47 @@
+! Copyright (c) 2016-present Arctic Ice Studio <development@arcticicestudio.com>
+! Copyright (c) 2016-present Sven Greb <code@svengreb.de>
+
+! Project: Nord XResources
+! Version: 0.1.0
+! Repository: https://github.com/arcticicestudio/nord-xresources
+! License: MIT
+
+#define nord0 #2E3440
+#define nord1 #3B4252
+#define nord2 #434C5E
+#define nord3 #4C566A
+#define nord4 #D8DEE9
+#define nord5 #E5E9F0
+#define nord6 #ECEFF4
+#define nord7 #8FBCBB
+#define nord8 #88C0D0
+#define nord9 #81A1C1
+#define nord10 #5E81AC
+#define nord11 #BF616A
+#define nord12 #D08770
+#define nord13 #EBCB8B
+#define nord14 #A3BE8C
+#define nord15 #B48EAD
+
+*.foreground: nord4
+*.background: nord0
+*.cursorColor: nord4
+*fading: 35
+*fadeColor: nord3
+
+*.color0: nord1
+*.color1: nord11
+*.color2: nord14
+*.color3: nord13
+*.color4: nord9
+*.color5: nord15
+*.color6: nord8
+*.color7: nord5
+*.color8: nord3
+*.color9: nord11
+*.color10: nord14
+*.color11: nord13
+*.color12: nord9
+*.color13: nord15
+*.color14: nord7
+*.color15: nord6
diff --git a/stow/dunst/dot-config/dunst/dunstifyIDs b/stow/dunst/dot-config/dunst/dunstifyIDs
new file mode 100644
index 0000000..2bccbf4
--- /dev/null
+++ b/stow/dunst/dot-config/dunst/dunstifyIDs
@@ -0,0 +1,12 @@
+vol 1001
+musicMode 1002
+brightness 1003
+cpu 1004
+memory 1005
+disk 1006
+batteryLow 1007
+moon 1008
+calcurse 1009
+bspwmDesktop 1010
+toggleKBLayouts 1011
+bspwmRunningStuff 1012
diff --git a/stow/dunst/dot-config/dunst/dunstrc b/stow/dunst/dot-config/dunst/dunstrc
new file mode 100644
index 0000000..80004aa
--- /dev/null
+++ b/stow/dunst/dot-config/dunst/dunstrc
@@ -0,0 +1,94 @@
+[global]
+ monitor = 0
+ follow = keyboard
+ geometry = "500x5-25+20"
+ indicate_hidden = yes
+ shrink = yes
+ transparency = 0
+ notification_height = 0
+ separator_height = 2
+ padding = 8
+ horizontal_padding = 8
+ frame_width = 3
+ frame_color = "#000000"
+ separator_color = frame
+ sort = yes
+ idle_threshold = 120
+
+ ### Text ###
+ font = Inconsolata 10
+ line_height = 0
+ markup = full
+ format = "<b>%s</b>\n%b"
+ alignment = center
+ show_age_threshold = -1
+ word_wrap = yes
+ ellipsize = middle
+ ignore_newline = no
+ stack_duplicates = true
+ hide_duplicate_count = false
+ show_indicators = yes
+
+ ### Icons ###
+ icon_position = off
+ #max_icon_size = 128
+ #icon_path = /usr/share/icons/hicolor/16x16/status/:/usr/share/icons/hicolor/16x16/devices/
+
+ ### History ###
+ sticky_history = yes
+ history_length = 20
+
+ ### Misc/Advanced ###
+
+ dmenu = /usr/bin/dmenu -p dunst:
+ browser = /usr/bin/firefox -new-tab
+ always_run_script = true
+ title = Dunst
+ class = Dunst
+ startup_notification = false
+
+ ### Legacy
+ force_xinerama = false
+
+[experimental]
+ per_monitor_dpi = false
+
+[shortcuts]
+ close = ctrl+space
+ close_all = ctrl+shift+space
+ #history = ctrl+grave
+ context = ctrl+shift+period
+
+[urgency_low]
+ background = "#222222"
+ foreground = "#888888"
+ timeout = 5
+
+[urgency_normal]
+ #background = "#285577" # default
+ #foreground = "#ffffff" # default
+ background = "#2e3440"
+ #foreground = "#81a1c1"
+ foreground = "#88c0d0"
+ timeout = 5
+
+[urgency_critical]
+ background = "#900000"
+ foreground = "#ffffff"
+ frame_color = "#ff0000"
+ timeout = 0
+
+[moon]
+ summary = "moon"
+ #urgency = normal
+ foreground = "#00ffff"
+ alignment = left
+ markup = no
+ timeout = 5
+
+#[musicMode]
+# summary = "testsummaryforshowingtime" # If empty, ALL notifications match!
+# urgency = normal
+# timeout = 0
+
+# vim: ft=cfg
diff --git a/stow/emoji/dot-config/emoji b/stow/emoji/dot-config/emoji
new file mode 100644
index 0000000..2441237
--- /dev/null
+++ b/stow/emoji/dot-config/emoji
@@ -0,0 +1,4267 @@
+~ Obtained from https://emojipedia.org/emoji/ on Mon 15 Jun 2020
+😀 Grinning Face U+1F600
+😃 Grinning Face with Big Eyes U+1F603
+😄 Grinning Face with Smiling Eyes U+1F604
+😠Beaming Face with Smiling Eyes U+1F601
+😆 Grinning Squinting Face U+1F606
+😅 Grinning Face with Sweat U+1F605
+🤣 Rolling on the Floor Laughing U+1F923
+😂 Face with Tears of Joy U+1F602
+🙂 Slightly Smiling Face U+1F642
+🙃 Upside-Down Face U+1F643
+😉 Winking Face U+1F609
+😊 Smiling Face with Smiling Eyes U+1F60A
+😇 Smiling Face with Halo U+1F607
+🥰 Smiling Face with Hearts U+1F970
+😠Smiling Face with Heart-Eyes U+1F60D
+🤩 Star-Struck U+1F929
+😘 Face Blowing a Kiss U+1F618
+😗 Kissing Face U+1F617
+â˜ºï¸ Smiling Face U+263A, U+FE0F
+😚 Kissing Face with Closed Eyes U+1F61A
+😙 Kissing Face with Smiling Eyes U+1F619
+🥲 Smiling Face with Tear U+1F972
+😋 Face Savoring Food U+1F60B
+😛 Face with Tongue U+1F61B
+😜 Winking Face with Tongue U+1F61C
+🤪 Zany Face U+1F92A
+😠Squinting Face with Tongue U+1F61D
+🤑 Money-Mouth Face U+1F911
+🤗 Hugging Face U+1F917
+🤭 Face with Hand Over Mouth U+1F92D
+🤫 Shushing Face U+1F92B
+🤔 Thinking Face U+1F914
+🤠Zipper-Mouth Face U+1F910
+🤨 Face with Raised Eyebrow U+1F928
+😠Neutral Face U+1F610
+😑 Expressionless Face U+1F611
+😶 Face Without Mouth U+1F636
+😠Smirking Face U+1F60F
+😒 Unamused Face U+1F612
+🙄 Face with Rolling Eyes U+1F644
+😬 Grimacing Face U+1F62C
+🤥 Lying Face U+1F925
+😌 Relieved Face U+1F60C
+😔 Pensive Face U+1F614
+😪 Sleepy Face U+1F62A
+🤤 Drooling Face U+1F924
+😴 Sleeping Face U+1F634
+😷 Face with Medical Mask U+1F637
+🤒 Face with Thermometer U+1F912
+🤕 Face with Head-Bandage U+1F915
+🤢 Nauseated Face U+1F922
+🤮 Face Vomiting U+1F92E
+🤧 Sneezing Face U+1F927
+🥵 Hot Face U+1F975
+🥶 Cold Face U+1F976
+🥴 Woozy Face U+1F974
+😵 Dizzy Face U+1F635
+🤯 Exploding Head U+1F92F
+🤠 Cowboy Hat Face U+1F920
+🥳 Partying Face U+1F973
+🥸 Disguised Face U+1F978
+😎 Smiling Face with Sunglasses U+1F60E
+🤓 Nerd Face U+1F913
+🧠Face with Monocle U+1F9D0
+😕 Confused Face U+1F615
+😟 Worried Face U+1F61F
+🙠Slightly Frowning Face U+1F641
+â˜¹ï¸ Frowning Face U+2639, U+FE0F
+😮 Face with Open Mouth U+1F62E
+😯 Hushed Face U+1F62F
+😲 Astonished Face U+1F632
+😳 Flushed Face U+1F633
+🥺 Pleading Face U+1F97A
+😦 Frowning Face with Open Mouth U+1F626
+😧 Anguished Face U+1F627
+😨 Fearful Face U+1F628
+😰 Anxious Face with Sweat U+1F630
+😥 Sad but Relieved Face U+1F625
+😢 Crying Face U+1F622
+😭 Loudly Crying Face U+1F62D
+😱 Face Screaming in Fear U+1F631
+😖 Confounded Face U+1F616
+😣 Persevering Face U+1F623
+😞 Disappointed Face U+1F61E
+😓 Downcast Face with Sweat U+1F613
+😩 Weary Face U+1F629
+😫 Tired Face U+1F62B
+🥱 Yawning Face U+1F971
+😤 Face with Steam From Nose U+1F624
+😡 Pouting Face U+1F621
+😠 Angry Face U+1F620
+🤬 Face with Symbols on Mouth U+1F92C
+😈 Smiling Face with Horns U+1F608
+👿 Angry Face with Horns U+1F47F
+💀 Skull U+1F480
+â˜ ï¸ Skull and Crossbones U+2620, U+FE0F
+💩 Pile of Poo U+1F4A9
+🤡 Clown Face U+1F921
+👹 Ogre U+1F479
+👺 Goblin U+1F47A
+👻 Ghost U+1F47B
+👽 Alien U+1F47D
+👾 Alien Monster U+1F47E
+🤖 Robot U+1F916
+😺 Grinning Cat U+1F63A
+😸 Grinning Cat with Smiling Eyes U+1F638
+😹 Cat with Tears of Joy U+1F639
+😻 Smiling Cat with Heart-Eyes U+1F63B
+😼 Cat with Wry Smile U+1F63C
+😽 Kissing Cat U+1F63D
+🙀 Weary Cat U+1F640
+😿 Crying Cat U+1F63F
+😾 Pouting Cat U+1F63E
+🙈 See-No-Evil Monkey U+1F648
+🙉 Hear-No-Evil Monkey U+1F649
+🙊 Speak-No-Evil Monkey U+1F64A
+💋 Kiss Mark U+1F48B
+💌 Love Letter U+1F48C
+💘 Heart with Arrow U+1F498
+💠Heart with Ribbon U+1F49D
+💖 Sparkling Heart U+1F496
+💗 Growing Heart U+1F497
+💓 Beating Heart U+1F493
+💞 Revolving Hearts U+1F49E
+💕 Two Hearts U+1F495
+💟 Heart Decoration U+1F49F
+â£ï¸ Heart Exclamation U+2763, U+FE0F
+💔 Broken Heart U+1F494
+â¤ï¸ Red Heart U+2764, U+FE0F
+🧡 Orange Heart U+1F9E1
+💛 Yellow Heart U+1F49B
+💚 Green Heart U+1F49A
+💙 Blue Heart U+1F499
+💜 Purple Heart U+1F49C
+🤎 Brown Heart U+1F90E
+🖤 Black Heart U+1F5A4
+🤠White Heart U+1F90D
+💯 Hundred Points U+1F4AF
+💢 Anger Symbol U+1F4A2
+💥 Collision U+1F4A5
+💫 Dizzy U+1F4AB
+💦 Sweat Droplets U+1F4A6
+💨 Dashing Away U+1F4A8
+ðŸ•³ï¸ Hole U+1F573, U+FE0F
+💣 Bomb U+1F4A3
+💬 Speech Balloon U+1F4AC
+ðŸ‘ï¸â€ðŸ—¨ï¸ Eye in Speech Bubble U+1F441, U+FE0F, U+200D, U+1F5E8,
+ U+FE0F
+ðŸ—¨ï¸ Left Speech Bubble U+1F5E8, U+FE0F
+ðŸ—¯ï¸ Right Anger Bubble U+1F5EF, U+FE0F
+💭 Thought Balloon U+1F4AD
+💤 Zzz U+1F4A4
+👋 Waving Hand U+1F44B
+👋🻠Waving Hand: Light Skin Tone U+1F44B, U+1F3FB
+👋🼠Waving Hand: Medium-Light Skin Tone U+1F44B, U+1F3FC
+👋🽠Waving Hand: Medium Skin Tone U+1F44B, U+1F3FD
+👋🾠Waving Hand: Medium-Dark Skin Tone U+1F44B, U+1F3FE
+👋🿠Waving Hand: Dark Skin Tone U+1F44B, U+1F3FF
+🤚 Raised Back of Hand U+1F91A
+🤚🻠Raised Back of Hand: Light Skin Tone U+1F91A, U+1F3FB
+🤚🼠Raised Back of Hand: Medium-Light Skin U+1F91A, U+1F3FC
+Tone
+🤚🽠Raised Back of Hand: Medium Skin Tone U+1F91A, U+1F3FD
+🤚🾠Raised Back of Hand: Medium-Dark Skin U+1F91A, U+1F3FE
+Tone
+🤚🿠Raised Back of Hand: Dark Skin Tone U+1F91A, U+1F3FF
+ðŸ–ï¸ Hand with Fingers Splayed U+1F590, U+FE0F
+ðŸ–🻠Hand with Fingers Splayed: Light Skin U+1F590, U+1F3FB
+Tone
+ðŸ–🼠Hand with Fingers Splayed: Medium-Light U+1F590, U+1F3FC
+Skin Tone
+ðŸ–🽠Hand with Fingers Splayed: Medium Skin U+1F590, U+1F3FD
+Tone
+ðŸ–🾠Hand with Fingers Splayed: Medium-Dark U+1F590, U+1F3FE
+Skin Tone
+ðŸ–🿠Hand with Fingers Splayed: Dark Skin Tone U+1F590, U+1F3FF
+✋ Raised Hand U+270B
+✋🻠Raised Hand: Light Skin Tone U+270B, U+1F3FB
+✋🼠Raised Hand: Medium-Light Skin Tone U+270B, U+1F3FC
+✋🽠Raised Hand: Medium Skin Tone U+270B, U+1F3FD
+✋🾠Raised Hand: Medium-Dark Skin Tone U+270B, U+1F3FE
+✋🿠Raised Hand: Dark Skin Tone U+270B, U+1F3FF
+🖖 Vulcan Salute U+1F596
+🖖🻠Vulcan Salute: Light Skin Tone U+1F596, U+1F3FB
+🖖🼠Vulcan Salute: Medium-Light Skin Tone U+1F596, U+1F3FC
+🖖🽠Vulcan Salute: Medium Skin Tone U+1F596, U+1F3FD
+🖖🾠Vulcan Salute: Medium-Dark Skin Tone U+1F596, U+1F3FE
+🖖🿠Vulcan Salute: Dark Skin Tone U+1F596, U+1F3FF
+👌 OK Hand U+1F44C
+👌🻠OK Hand: Light Skin Tone U+1F44C, U+1F3FB
+👌🼠OK Hand: Medium-Light Skin Tone U+1F44C, U+1F3FC
+👌🽠OK Hand: Medium Skin Tone U+1F44C, U+1F3FD
+👌🾠OK Hand: Medium-Dark Skin Tone U+1F44C, U+1F3FE
+👌🿠OK Hand: Dark Skin Tone U+1F44C, U+1F3FF
+🤌 Pinched Fingers U+1F90C
+🤌🻠Pinched Fingers: Light Skin Tone U+1F90C, U+1F3FB
+🤌🼠Pinched Fingers: Medium-Light Skin Tone U+1F90C, U+1F3FC
+🤌🽠Pinched Fingers: Medium Skin Tone U+1F90C, U+1F3FD
+🤌🾠Pinched Fingers: Medium-Dark Skin Tone U+1F90C, U+1F3FE
+🤌🿠Pinched Fingers: Dark Skin Tone U+1F90C, U+1F3FF
+🤠Pinching Hand U+1F90F
+ðŸ¤ðŸ» Pinching Hand: Light Skin Tone U+1F90F, U+1F3FB
+ðŸ¤ðŸ¼ Pinching Hand: Medium-Light Skin Tone U+1F90F, U+1F3FC
+ðŸ¤ðŸ½ Pinching Hand: Medium Skin Tone U+1F90F, U+1F3FD
+ðŸ¤ðŸ¾ Pinching Hand: Medium-Dark Skin Tone U+1F90F, U+1F3FE
+ðŸ¤ðŸ¿ Pinching Hand: Dark Skin Tone U+1F90F, U+1F3FF
+âœŒï¸ Victory Hand U+270C, U+FE0F
+✌🻠Victory Hand: Light Skin Tone U+270C, U+1F3FB
+✌🼠Victory Hand: Medium-Light Skin Tone U+270C, U+1F3FC
+✌🽠Victory Hand: Medium Skin Tone U+270C, U+1F3FD
+✌🾠Victory Hand: Medium-Dark Skin Tone U+270C, U+1F3FE
+✌🿠Victory Hand: Dark Skin Tone U+270C, U+1F3FF
+🤞 Crossed Fingers U+1F91E
+🤞🻠Crossed Fingers: Light Skin Tone U+1F91E, U+1F3FB
+🤞🼠Crossed Fingers: Medium-Light Skin Tone U+1F91E, U+1F3FC
+🤞🽠Crossed Fingers: Medium Skin Tone U+1F91E, U+1F3FD
+🤞🾠Crossed Fingers: Medium-Dark Skin Tone U+1F91E, U+1F3FE
+🤞🿠Crossed Fingers: Dark Skin Tone U+1F91E, U+1F3FF
+🤟 Love-You Gesture U+1F91F
+🤟🻠Love-You Gesture: Light Skin Tone U+1F91F, U+1F3FB
+🤟🼠Love-You Gesture: Medium-Light Skin Tone U+1F91F, U+1F3FC
+🤟🽠Love-You Gesture: Medium Skin Tone U+1F91F, U+1F3FD
+🤟🾠Love-You Gesture: Medium-Dark Skin Tone U+1F91F, U+1F3FE
+🤟🿠Love-You Gesture: Dark Skin Tone U+1F91F, U+1F3FF
+🤘 Sign of the Horns U+1F918
+🤘🻠Sign of the Horns: Light Skin Tone U+1F918, U+1F3FB
+🤘🼠Sign of the Horns: Medium-Light Skin Tone U+1F918, U+1F3FC
+🤘🽠Sign of the Horns: Medium Skin Tone U+1F918, U+1F3FD
+🤘🾠Sign of the Horns: Medium-Dark Skin Tone U+1F918, U+1F3FE
+🤘🿠Sign of the Horns: Dark Skin Tone U+1F918, U+1F3FF
+🤙 Call Me Hand U+1F919
+🤙🻠Call Me Hand: Light Skin Tone U+1F919, U+1F3FB
+🤙🼠Call Me Hand: Medium-Light Skin Tone U+1F919, U+1F3FC
+🤙🽠Call Me Hand: Medium Skin Tone U+1F919, U+1F3FD
+🤙🾠Call Me Hand: Medium-Dark Skin Tone U+1F919, U+1F3FE
+🤙🿠Call Me Hand: Dark Skin Tone U+1F919, U+1F3FF
+👈 Backhand Index Pointing Left U+1F448
+👈🻠Backhand Index Pointing Left: Light Skin U+1F448, U+1F3FB
+Tone
+👈🼠Backhand Index Pointing Left: U+1F448, U+1F3FC
+Medium-Light Skin Tone
+👈🽠Backhand Index Pointing Left: Medium Skin U+1F448, U+1F3FD
+Tone
+👈🾠Backhand Index Pointing Left: Medium-Dark U+1F448, U+1F3FE
+Skin Tone
+👈🿠Backhand Index Pointing Left: Dark Skin U+1F448, U+1F3FF
+Tone
+👉 Backhand Index Pointing Right U+1F449
+👉🻠Backhand Index Pointing Right: Light Skin U+1F449, U+1F3FB
+Tone
+👉🼠Backhand Index Pointing Right: U+1F449, U+1F3FC
+Medium-Light Skin Tone
+👉🽠Backhand Index Pointing Right: Medium U+1F449, U+1F3FD
+Skin Tone
+👉🾠Backhand Index Pointing Right: U+1F449, U+1F3FE
+Medium-Dark Skin Tone
+👉🿠Backhand Index Pointing Right: Dark Skin U+1F449, U+1F3FF
+Tone
+👆 Backhand Index Pointing Up U+1F446
+👆🻠Backhand Index Pointing Up: Light Skin U+1F446, U+1F3FB
+Tone
+👆🼠Backhand Index Pointing Up: Medium-Light U+1F446, U+1F3FC
+Skin Tone
+👆🽠Backhand Index Pointing Up: Medium Skin U+1F446, U+1F3FD
+Tone
+👆🾠Backhand Index Pointing Up: Medium-Dark U+1F446, U+1F3FE
+Skin Tone
+👆🿠Backhand Index Pointing Up: Dark Skin U+1F446, U+1F3FF
+Tone
+🖕 Middle Finger U+1F595
+🖕🻠Middle Finger: Light Skin Tone U+1F595, U+1F3FB
+🖕🼠Middle Finger: Medium-Light Skin Tone U+1F595, U+1F3FC
+🖕🽠Middle Finger: Medium Skin Tone U+1F595, U+1F3FD
+🖕🾠Middle Finger: Medium-Dark Skin Tone U+1F595, U+1F3FE
+🖕🿠Middle Finger: Dark Skin Tone U+1F595, U+1F3FF
+👇 Backhand Index Pointing Down U+1F447
+👇🻠Backhand Index Pointing Down: Light Skin U+1F447, U+1F3FB
+Tone
+👇🼠Backhand Index Pointing Down: U+1F447, U+1F3FC
+Medium-Light Skin Tone
+👇🽠Backhand Index Pointing Down: Medium Skin U+1F447, U+1F3FD
+Tone
+👇🾠Backhand Index Pointing Down: Medium-Dark U+1F447, U+1F3FE
+Skin Tone
+👇🿠Backhand Index Pointing Down: Dark Skin U+1F447, U+1F3FF
+Tone
+â˜ï¸ Index Pointing Up U+261D, U+FE0F
+â˜ðŸ» Index Pointing Up: Light Skin Tone U+261D, U+1F3FB
+â˜ðŸ¼ Index Pointing Up: Medium-Light Skin Tone U+261D, U+1F3FC
+â˜ðŸ½ Index Pointing Up: Medium Skin Tone U+261D, U+1F3FD
+â˜ðŸ¾ Index Pointing Up: Medium-Dark Skin Tone U+261D, U+1F3FE
+â˜ðŸ¿ Index Pointing Up: Dark Skin Tone U+261D, U+1F3FF
+👠Thumbs Up U+1F44D
+ðŸ‘🻠Thumbs Up: Light Skin Tone U+1F44D, U+1F3FB
+ðŸ‘🼠Thumbs Up: Medium-Light Skin Tone U+1F44D, U+1F3FC
+ðŸ‘🽠Thumbs Up: Medium Skin Tone U+1F44D, U+1F3FD
+ðŸ‘🾠Thumbs Up: Medium-Dark Skin Tone U+1F44D, U+1F3FE
+ðŸ‘🿠Thumbs Up: Dark Skin Tone U+1F44D, U+1F3FF
+👎 Thumbs Down U+1F44E
+👎🻠Thumbs Down: Light Skin Tone U+1F44E, U+1F3FB
+👎🼠Thumbs Down: Medium-Light Skin Tone U+1F44E, U+1F3FC
+👎🽠Thumbs Down: Medium Skin Tone U+1F44E, U+1F3FD
+👎🾠Thumbs Down: Medium-Dark Skin Tone U+1F44E, U+1F3FE
+👎🿠Thumbs Down: Dark Skin Tone U+1F44E, U+1F3FF
+✊ Raised Fist U+270A
+✊🻠Raised Fist: Light Skin Tone U+270A, U+1F3FB
+✊🼠Raised Fist: Medium-Light Skin Tone U+270A, U+1F3FC
+✊🽠Raised Fist: Medium Skin Tone U+270A, U+1F3FD
+✊🾠Raised Fist: Medium-Dark Skin Tone U+270A, U+1F3FE
+✊🿠Raised Fist: Dark Skin Tone U+270A, U+1F3FF
+👊 Oncoming Fist U+1F44A
+👊🻠Oncoming Fist: Light Skin Tone U+1F44A, U+1F3FB
+👊🼠Oncoming Fist: Medium-Light Skin Tone U+1F44A, U+1F3FC
+👊🽠Oncoming Fist: Medium Skin Tone U+1F44A, U+1F3FD
+👊🾠Oncoming Fist: Medium-Dark Skin Tone U+1F44A, U+1F3FE
+👊🿠Oncoming Fist: Dark Skin Tone U+1F44A, U+1F3FF
+🤛 Left-Facing Fist U+1F91B
+🤛🻠Left-Facing Fist: Light Skin Tone U+1F91B, U+1F3FB
+🤛🼠Left-Facing Fist: Medium-Light Skin Tone U+1F91B, U+1F3FC
+🤛🽠Left-Facing Fist: Medium Skin Tone U+1F91B, U+1F3FD
+🤛🾠Left-Facing Fist: Medium-Dark Skin Tone U+1F91B, U+1F3FE
+🤛🿠Left-Facing Fist: Dark Skin Tone U+1F91B, U+1F3FF
+🤜 Right-Facing Fist U+1F91C
+🤜🻠Right-Facing Fist: Light Skin Tone U+1F91C, U+1F3FB
+🤜🼠Right-Facing Fist: Medium-Light Skin Tone U+1F91C, U+1F3FC
+🤜🽠Right-Facing Fist: Medium Skin Tone U+1F91C, U+1F3FD
+🤜🾠Right-Facing Fist: Medium-Dark Skin Tone U+1F91C, U+1F3FE
+🤜🿠Right-Facing Fist: Dark Skin Tone U+1F91C, U+1F3FF
+👠Clapping Hands U+1F44F
+ðŸ‘🻠Clapping Hands: Light Skin Tone U+1F44F, U+1F3FB
+ðŸ‘🼠Clapping Hands: Medium-Light Skin Tone U+1F44F, U+1F3FC
+ðŸ‘🽠Clapping Hands: Medium Skin Tone U+1F44F, U+1F3FD
+ðŸ‘🾠Clapping Hands: Medium-Dark Skin Tone U+1F44F, U+1F3FE
+ðŸ‘🿠Clapping Hands: Dark Skin Tone U+1F44F, U+1F3FF
+🙌 Raising Hands U+1F64C
+🙌🻠Raising Hands: Light Skin Tone U+1F64C, U+1F3FB
+🙌🼠Raising Hands: Medium-Light Skin Tone U+1F64C, U+1F3FC
+🙌🽠Raising Hands: Medium Skin Tone U+1F64C, U+1F3FD
+🙌🾠Raising Hands: Medium-Dark Skin Tone U+1F64C, U+1F3FE
+🙌🿠Raising Hands: Dark Skin Tone U+1F64C, U+1F3FF
+👠Open Hands U+1F450
+ðŸ‘🻠Open Hands: Light Skin Tone U+1F450, U+1F3FB
+ðŸ‘🼠Open Hands: Medium-Light Skin Tone U+1F450, U+1F3FC
+ðŸ‘🽠Open Hands: Medium Skin Tone U+1F450, U+1F3FD
+ðŸ‘🾠Open Hands: Medium-Dark Skin Tone U+1F450, U+1F3FE
+ðŸ‘🿠Open Hands: Dark Skin Tone U+1F450, U+1F3FF
+🤲 Palms Up Together U+1F932
+🤲🻠Palms Up Together: Light Skin Tone U+1F932, U+1F3FB
+🤲🼠Palms Up Together: Medium-Light Skin Tone U+1F932, U+1F3FC
+🤲🽠Palms Up Together: Medium Skin Tone U+1F932, U+1F3FD
+🤲🾠Palms Up Together: Medium-Dark Skin Tone U+1F932, U+1F3FE
+🤲🿠Palms Up Together: Dark Skin Tone U+1F932, U+1F3FF
+🤠Handshake U+1F91D
+🙠Folded Hands U+1F64F
+ðŸ™ðŸ» Folded Hands: Light Skin Tone U+1F64F, U+1F3FB
+ðŸ™ðŸ¼ Folded Hands: Medium-Light Skin Tone U+1F64F, U+1F3FC
+ðŸ™ðŸ½ Folded Hands: Medium Skin Tone U+1F64F, U+1F3FD
+ðŸ™ðŸ¾ Folded Hands: Medium-Dark Skin Tone U+1F64F, U+1F3FE
+ðŸ™ðŸ¿ Folded Hands: Dark Skin Tone U+1F64F, U+1F3FF
+âœï¸ Writing Hand U+270D, U+FE0F
+âœðŸ» Writing Hand: Light Skin Tone U+270D, U+1F3FB
+âœðŸ¼ Writing Hand: Medium-Light Skin Tone U+270D, U+1F3FC
+âœðŸ½ Writing Hand: Medium Skin Tone U+270D, U+1F3FD
+âœðŸ¾ Writing Hand: Medium-Dark Skin Tone U+270D, U+1F3FE
+âœðŸ¿ Writing Hand: Dark Skin Tone U+270D, U+1F3FF
+💅 Nail Polish U+1F485
+💅🻠Nail Polish: Light Skin Tone U+1F485, U+1F3FB
+💅🼠Nail Polish: Medium-Light Skin Tone U+1F485, U+1F3FC
+💅🽠Nail Polish: Medium Skin Tone U+1F485, U+1F3FD
+💅🾠Nail Polish: Medium-Dark Skin Tone U+1F485, U+1F3FE
+💅🿠Nail Polish: Dark Skin Tone U+1F485, U+1F3FF
+🤳 Selfie U+1F933
+🤳🻠Selfie: Light Skin Tone U+1F933, U+1F3FB
+🤳🼠Selfie: Medium-Light Skin Tone U+1F933, U+1F3FC
+🤳🽠Selfie: Medium Skin Tone U+1F933, U+1F3FD
+🤳🾠Selfie: Medium-Dark Skin Tone U+1F933, U+1F3FE
+🤳🿠Selfie: Dark Skin Tone U+1F933, U+1F3FF
+💪 Flexed Biceps U+1F4AA
+💪🻠Flexed Biceps: Light Skin Tone U+1F4AA, U+1F3FB
+💪🼠Flexed Biceps: Medium-Light Skin Tone U+1F4AA, U+1F3FC
+💪🽠Flexed Biceps: Medium Skin Tone U+1F4AA, U+1F3FD
+💪🾠Flexed Biceps: Medium-Dark Skin Tone U+1F4AA, U+1F3FE
+💪🿠Flexed Biceps: Dark Skin Tone U+1F4AA, U+1F3FF
+🦾 Mechanical Arm U+1F9BE
+🦿 Mechanical Leg U+1F9BF
+🦵 Leg U+1F9B5
+🦵🻠Leg: Light Skin Tone U+1F9B5, U+1F3FB
+🦵🼠Leg: Medium-Light Skin Tone U+1F9B5, U+1F3FC
+🦵🽠Leg: Medium Skin Tone U+1F9B5, U+1F3FD
+🦵🾠Leg: Medium-Dark Skin Tone U+1F9B5, U+1F3FE
+🦵🿠Leg: Dark Skin Tone U+1F9B5, U+1F3FF
+🦶 Foot U+1F9B6
+🦶🻠Foot: Light Skin Tone U+1F9B6, U+1F3FB
+🦶🼠Foot: Medium-Light Skin Tone U+1F9B6, U+1F3FC
+🦶🽠Foot: Medium Skin Tone U+1F9B6, U+1F3FD
+🦶🾠Foot: Medium-Dark Skin Tone U+1F9B6, U+1F3FE
+🦶🿠Foot: Dark Skin Tone U+1F9B6, U+1F3FF
+👂 Ear U+1F442
+👂🻠Ear: Light Skin Tone U+1F442, U+1F3FB
+👂🼠Ear: Medium-Light Skin Tone U+1F442, U+1F3FC
+👂🽠Ear: Medium Skin Tone U+1F442, U+1F3FD
+👂🾠Ear: Medium-Dark Skin Tone U+1F442, U+1F3FE
+👂🿠Ear: Dark Skin Tone U+1F442, U+1F3FF
+🦻 Ear with Hearing Aid U+1F9BB
+🦻🻠Ear with Hearing Aid: Light Skin Tone U+1F9BB, U+1F3FB
+🦻🼠Ear with Hearing Aid: Medium-Light Skin U+1F9BB, U+1F3FC
+Tone
+🦻🽠Ear with Hearing Aid: Medium Skin Tone U+1F9BB, U+1F3FD
+🦻🾠Ear with Hearing Aid: Medium-Dark Skin U+1F9BB, U+1F3FE
+Tone
+🦻🿠Ear with Hearing Aid: Dark Skin Tone U+1F9BB, U+1F3FF
+👃 Nose U+1F443
+👃🻠Nose: Light Skin Tone U+1F443, U+1F3FB
+👃🼠Nose: Medium-Light Skin Tone U+1F443, U+1F3FC
+👃🽠Nose: Medium Skin Tone U+1F443, U+1F3FD
+👃🾠Nose: Medium-Dark Skin Tone U+1F443, U+1F3FE
+👃🿠Nose: Dark Skin Tone U+1F443, U+1F3FF
+🧠 Brain U+1F9E0
+🫀 Anatomical Heart U+1FAC0
+🫠Lungs U+1FAC1
+🦷 Tooth U+1F9B7
+🦴 Bone U+1F9B4
+👀 Eyes U+1F440
+ðŸ‘ï¸ Eye U+1F441, U+FE0F
+👅 Tongue U+1F445
+👄 Mouth U+1F444
+👶 Baby U+1F476
+👶🻠Baby: Light Skin Tone U+1F476, U+1F3FB
+👶🼠Baby: Medium-Light Skin Tone U+1F476, U+1F3FC
+👶🽠Baby: Medium Skin Tone U+1F476, U+1F3FD
+👶🾠Baby: Medium-Dark Skin Tone U+1F476, U+1F3FE
+👶🿠Baby: Dark Skin Tone U+1F476, U+1F3FF
+🧒 Child U+1F9D2
+🧒🻠Child: Light Skin Tone U+1F9D2, U+1F3FB
+🧒🼠Child: Medium-Light Skin Tone U+1F9D2, U+1F3FC
+🧒🽠Child: Medium Skin Tone U+1F9D2, U+1F3FD
+🧒🾠Child: Medium-Dark Skin Tone U+1F9D2, U+1F3FE
+🧒🿠Child: Dark Skin Tone U+1F9D2, U+1F3FF
+👦 Boy U+1F466
+👦🻠Boy: Light Skin Tone U+1F466, U+1F3FB
+👦🼠Boy: Medium-Light Skin Tone U+1F466, U+1F3FC
+👦🽠Boy: Medium Skin Tone U+1F466, U+1F3FD
+👦🾠Boy: Medium-Dark Skin Tone U+1F466, U+1F3FE
+👦🿠Boy: Dark Skin Tone U+1F466, U+1F3FF
+👧 Girl U+1F467
+👧🻠Girl: Light Skin Tone U+1F467, U+1F3FB
+👧🼠Girl: Medium-Light Skin Tone U+1F467, U+1F3FC
+👧🽠Girl: Medium Skin Tone U+1F467, U+1F3FD
+👧🾠Girl: Medium-Dark Skin Tone U+1F467, U+1F3FE
+👧🿠Girl: Dark Skin Tone U+1F467, U+1F3FF
+🧑 Person U+1F9D1
+🧑🻠Person: Light Skin Tone U+1F9D1, U+1F3FB
+🧑🼠Person: Medium-Light Skin Tone U+1F9D1, U+1F3FC
+🧑🽠Person: Medium Skin Tone U+1F9D1, U+1F3FD
+🧑🾠Person: Medium-Dark Skin Tone U+1F9D1, U+1F3FE
+🧑🿠Person: Dark Skin Tone U+1F9D1, U+1F3FF
+👱 Person: Blond Hair U+1F471
+👱🻠Person: Light Skin Tone, Blond Hair U+1F471, U+1F3FB
+👱🼠Person: Medium-Light Skin Tone, Blond U+1F471, U+1F3FC
+Hair
+👱🽠Person: Medium Skin Tone, Blond Hair U+1F471, U+1F3FD
+👱🾠Person: Medium-Dark Skin Tone, Blond Hair U+1F471, U+1F3FE
+👱🿠Person: Dark Skin Tone, Blond Hair U+1F471, U+1F3FF
+👨 Man U+1F468
+👨🻠Man: Light Skin Tone U+1F468, U+1F3FB
+👨🼠Man: Medium-Light Skin Tone U+1F468, U+1F3FC
+👨🽠Man: Medium Skin Tone U+1F468, U+1F3FD
+👨🾠Man: Medium-Dark Skin Tone U+1F468, U+1F3FE
+👨🿠Man: Dark Skin Tone U+1F468, U+1F3FF
+🧔 Man: Beard U+1F9D4
+🧔🻠Man: Light Skin Tone, Beard U+1F9D4, U+1F3FB
+🧔🼠Man: Medium-Light Skin Tone, Beard U+1F9D4, U+1F3FC
+🧔🽠Man: Medium Skin Tone, Beard U+1F9D4, U+1F3FD
+🧔🾠Man: Medium-Dark Skin Tone, Beard U+1F9D4, U+1F3FE
+🧔🿠Man: Dark Skin Tone, Beard U+1F9D4, U+1F3FF
+👨â€ðŸ¦° Man: Red Hair U+1F468, U+200D, U+1F9B0
+👨ðŸ»â€ðŸ¦° Man: Light Skin Tone, Red Hair U+1F468, U+1F3FB, U+200D, U+1F9B0
+👨ðŸ¼â€ðŸ¦° Man: Medium-Light Skin Tone, Red Hair U+1F468, U+1F3FC, U+200D, U+1F9B0
+👨ðŸ½â€ðŸ¦° Man: Medium Skin Tone, Red Hair U+1F468, U+1F3FD, U+200D, U+1F9B0
+👨ðŸ¾â€ðŸ¦° Man: Medium-Dark Skin Tone, Red Hair U+1F468, U+1F3FE, U+200D, U+1F9B0
+👨ðŸ¿â€ðŸ¦° Man: Dark Skin Tone, Red Hair U+1F468, U+1F3FF, U+200D, U+1F9B0
+👨â€ðŸ¦± Man: Curly Hair U+1F468, U+200D, U+1F9B1
+👨ðŸ»â€ðŸ¦± Man: Light Skin Tone, Curly Hair U+1F468, U+1F3FB, U+200D, U+1F9B1
+👨ðŸ¼â€ðŸ¦± Man: Medium-Light Skin Tone, Curly Hair U+1F468, U+1F3FC, U+200D, U+1F9B1
+👨ðŸ½â€ðŸ¦± Man: Medium Skin Tone, Curly Hair U+1F468, U+1F3FD, U+200D, U+1F9B1
+👨ðŸ¾â€ðŸ¦± Man: Medium-Dark Skin Tone, Curly Hair U+1F468, U+1F3FE, U+200D, U+1F9B1
+👨ðŸ¿â€ðŸ¦± Man: Dark Skin Tone, Curly Hair U+1F468, U+1F3FF, U+200D, U+1F9B1
+👨â€ðŸ¦³ Man: White Hair U+1F468, U+200D, U+1F9B3
+👨ðŸ»â€ðŸ¦³ Man: Light Skin Tone, White Hair U+1F468, U+1F3FB, U+200D, U+1F9B3
+👨ðŸ¼â€ðŸ¦³ Man: Medium-Light Skin Tone, White Hair U+1F468, U+1F3FC, U+200D, U+1F9B3
+👨ðŸ½â€ðŸ¦³ Man: Medium Skin Tone, White Hair U+1F468, U+1F3FD, U+200D, U+1F9B3
+👨ðŸ¾â€ðŸ¦³ Man: Medium-Dark Skin Tone, White Hair U+1F468, U+1F3FE, U+200D, U+1F9B3
+👨ðŸ¿â€ðŸ¦³ Man: Dark Skin Tone, White Hair U+1F468, U+1F3FF, U+200D, U+1F9B3
+👨â€ðŸ¦² Man: Bald U+1F468, U+200D, U+1F9B2
+👨ðŸ»â€ðŸ¦² Man: Light Skin Tone, Bald U+1F468, U+1F3FB, U+200D, U+1F9B2
+👨ðŸ¼â€ðŸ¦² Man: Medium-Light Skin Tone, Bald U+1F468, U+1F3FC, U+200D, U+1F9B2
+👨ðŸ½â€ðŸ¦² Man: Medium Skin Tone, Bald U+1F468, U+1F3FD, U+200D, U+1F9B2
+👨ðŸ¾â€ðŸ¦² Man: Medium-Dark Skin Tone, Bald U+1F468, U+1F3FE, U+200D, U+1F9B2
+👨ðŸ¿â€ðŸ¦² Man: Dark Skin Tone, Bald U+1F468, U+1F3FF, U+200D, U+1F9B2
+👩 Woman U+1F469
+👩🻠Woman: Light Skin Tone U+1F469, U+1F3FB
+👩🼠Woman: Medium-Light Skin Tone U+1F469, U+1F3FC
+👩🽠Woman: Medium Skin Tone U+1F469, U+1F3FD
+👩🾠Woman: Medium-Dark Skin Tone U+1F469, U+1F3FE
+👩🿠Woman: Dark Skin Tone U+1F469, U+1F3FF
+👩â€ðŸ¦° Woman: Red Hair U+1F469, U+200D, U+1F9B0
+👩ðŸ»â€ðŸ¦° Woman: Light Skin Tone, Red Hair U+1F469, U+1F3FB, U+200D, U+1F9B0
+👩ðŸ¼â€ðŸ¦° Woman: Medium-Light Skin Tone, Red Hair U+1F469, U+1F3FC, U+200D, U+1F9B0
+👩ðŸ½â€ðŸ¦° Woman: Medium Skin Tone, Red Hair U+1F469, U+1F3FD, U+200D, U+1F9B0
+👩ðŸ¾â€ðŸ¦° Woman: Medium-Dark Skin Tone, Red Hair U+1F469, U+1F3FE, U+200D, U+1F9B0
+👩ðŸ¿â€ðŸ¦° Woman: Dark Skin Tone, Red Hair U+1F469, U+1F3FF, U+200D, U+1F9B0
+🧑â€ðŸ¦° Person: Red Hair U+1F9D1, U+200D, U+1F9B0
+🧑ðŸ»â€ðŸ¦° Person: Light Skin Tone, Red Hair U+1F9D1, U+1F3FB, U+200D, U+1F9B0
+🧑ðŸ¼â€ðŸ¦° Person: Medium-Light Skin Tone, Red U+1F9D1, U+1F3FC, U+200D, U+1F9B0
+Hair
+🧑ðŸ½â€ðŸ¦° Person: Medium Skin Tone, Red Hair U+1F9D1, U+1F3FD, U+200D, U+1F9B0
+🧑ðŸ¾â€ðŸ¦° Person: Medium-Dark Skin Tone, Red Hair U+1F9D1, U+1F3FE, U+200D, U+1F9B0
+🧑ðŸ¿â€ðŸ¦° Person: Dark Skin Tone, Red Hair U+1F9D1, U+1F3FF, U+200D, U+1F9B0
+👩â€ðŸ¦± Woman: Curly Hair U+1F469, U+200D, U+1F9B1
+👩ðŸ»â€ðŸ¦± Woman: Light Skin Tone, Curly Hair U+1F469, U+1F3FB, U+200D, U+1F9B1
+👩ðŸ¼â€ðŸ¦± Woman: Medium-Light Skin Tone, Curly U+1F469, U+1F3FC, U+200D, U+1F9B1
+Hair
+👩ðŸ½â€ðŸ¦± Woman: Medium Skin Tone, Curly Hair U+1F469, U+1F3FD, U+200D, U+1F9B1
+👩ðŸ¾â€ðŸ¦± Woman: Medium-Dark Skin Tone, Curly U+1F469, U+1F3FE, U+200D, U+1F9B1
+Hair
+👩ðŸ¿â€ðŸ¦± Woman: Dark Skin Tone, Curly Hair U+1F469, U+1F3FF, U+200D, U+1F9B1
+🧑â€ðŸ¦± Person: Curly Hair U+1F9D1, U+200D, U+1F9B1
+🧑ðŸ»â€ðŸ¦± Person: Light Skin Tone, Curly Hair U+1F9D1, U+1F3FB, U+200D, U+1F9B1
+🧑ðŸ¼â€ðŸ¦± Person: Medium-Light Skin Tone, Curly U+1F9D1, U+1F3FC, U+200D, U+1F9B1
+Hair
+🧑ðŸ½â€ðŸ¦± Person: Medium Skin Tone, Curly Hair U+1F9D1, U+1F3FD, U+200D, U+1F9B1
+🧑ðŸ¾â€ðŸ¦± Person: Medium-Dark Skin Tone, Curly U+1F9D1, U+1F3FE, U+200D, U+1F9B1
+Hair
+🧑ðŸ¿â€ðŸ¦± Person: Dark Skin Tone, Curly Hair U+1F9D1, U+1F3FF, U+200D, U+1F9B1
+👩â€ðŸ¦³ Woman: White Hair U+1F469, U+200D, U+1F9B3
+👩ðŸ»â€ðŸ¦³ Woman: Light Skin Tone, White Hair U+1F469, U+1F3FB, U+200D, U+1F9B3
+👩ðŸ¼â€ðŸ¦³ Woman: Medium-Light Skin Tone, White U+1F469, U+1F3FC, U+200D, U+1F9B3
+Hair
+👩ðŸ½â€ðŸ¦³ Woman: Medium Skin Tone, White Hair U+1F469, U+1F3FD, U+200D, U+1F9B3
+👩ðŸ¾â€ðŸ¦³ Woman: Medium-Dark Skin Tone, White U+1F469, U+1F3FE, U+200D, U+1F9B3
+Hair
+👩ðŸ¿â€ðŸ¦³ Woman: Dark Skin Tone, White Hair U+1F469, U+1F3FF, U+200D, U+1F9B3
+🧑â€ðŸ¦³ Person: White Hair U+1F9D1, U+200D, U+1F9B3
+🧑ðŸ»â€ðŸ¦³ Person: Light Skin Tone, White Hair U+1F9D1, U+1F3FB, U+200D, U+1F9B3
+🧑ðŸ¼â€ðŸ¦³ Person: Medium-Light Skin Tone, White U+1F9D1, U+1F3FC, U+200D, U+1F9B3
+Hair
+🧑ðŸ½â€ðŸ¦³ Person: Medium Skin Tone, White Hair U+1F9D1, U+1F3FD, U+200D, U+1F9B3
+🧑ðŸ¾â€ðŸ¦³ Person: Medium-Dark Skin Tone, White U+1F9D1, U+1F3FE, U+200D, U+1F9B3
+Hair
+🧑ðŸ¿â€ðŸ¦³ Person: Dark Skin Tone, White Hair U+1F9D1, U+1F3FF, U+200D, U+1F9B3
+👩â€ðŸ¦² Woman: Bald U+1F469, U+200D, U+1F9B2
+👩ðŸ»â€ðŸ¦² Woman: Light Skin Tone, Bald U+1F469, U+1F3FB, U+200D, U+1F9B2
+👩ðŸ¼â€ðŸ¦² Woman: Medium-Light Skin Tone, Bald U+1F469, U+1F3FC, U+200D, U+1F9B2
+👩ðŸ½â€ðŸ¦² Woman: Medium Skin Tone, Bald U+1F469, U+1F3FD, U+200D, U+1F9B2
+👩ðŸ¾â€ðŸ¦² Woman: Medium-Dark Skin Tone, Bald U+1F469, U+1F3FE, U+200D, U+1F9B2
+👩ðŸ¿â€ðŸ¦² Woman: Dark Skin Tone, Bald U+1F469, U+1F3FF, U+200D, U+1F9B2
+🧑â€ðŸ¦² Person: Bald U+1F9D1, U+200D, U+1F9B2
+🧑ðŸ»â€ðŸ¦² Person: Light Skin Tone, Bald U+1F9D1, U+1F3FB, U+200D, U+1F9B2
+🧑ðŸ¼â€ðŸ¦² Person: Medium-Light Skin Tone, Bald U+1F9D1, U+1F3FC, U+200D, U+1F9B2
+🧑ðŸ½â€ðŸ¦² Person: Medium Skin Tone, Bald U+1F9D1, U+1F3FD, U+200D, U+1F9B2
+🧑ðŸ¾â€ðŸ¦² Person: Medium-Dark Skin Tone, Bald U+1F9D1, U+1F3FE, U+200D, U+1F9B2
+🧑ðŸ¿â€ðŸ¦² Person: Dark Skin Tone, Bald U+1F9D1, U+1F3FF, U+200D, U+1F9B2
+👱â€â™€ï¸ Woman: Blond Hair U+1F471, U+200D, U+2640, U+FE0F
+👱ðŸ»â€â™€ï¸ Woman: Light Skin Tone, Blond Hair U+1F471, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+👱ðŸ¼â€â™€ï¸ Woman: Medium-Light Skin Tone, Blond U+1F471, U+1F3FC, U+200D, U+2640,
+Hair U+FE0F
+👱ðŸ½â€â™€ï¸ Woman: Medium Skin Tone, Blond Hair U+1F471, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+👱ðŸ¾â€â™€ï¸ Woman: Medium-Dark Skin Tone, Blond U+1F471, U+1F3FE, U+200D, U+2640,
+Hair U+FE0F
+👱ðŸ¿â€â™€ï¸ Woman: Dark Skin Tone, Blond Hair U+1F471, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+👱â€â™‚ï¸ Man: Blond Hair U+1F471, U+200D, U+2642, U+FE0F
+👱ðŸ»â€â™‚ï¸ Man: Light Skin Tone, Blond Hair U+1F471, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+👱ðŸ¼â€â™‚ï¸ Man: Medium-Light Skin Tone, Blond U+1F471, U+1F3FC, U+200D, U+2642,
+Hair U+FE0F
+👱ðŸ½â€â™‚ï¸ Man: Medium Skin Tone, Blond Hair U+1F471, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+👱ðŸ¾â€â™‚ï¸ Man: Medium-Dark Skin Tone, Blond Hair U+1F471, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+👱ðŸ¿â€â™‚ï¸ Man: Dark Skin Tone, Blond Hair U+1F471, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧓 Older Person U+1F9D3
+🧓🻠Older Person: Light Skin Tone U+1F9D3, U+1F3FB
+🧓🼠Older Person: Medium-Light Skin Tone U+1F9D3, U+1F3FC
+🧓🽠Older Person: Medium Skin Tone U+1F9D3, U+1F3FD
+🧓🾠Older Person: Medium-Dark Skin Tone U+1F9D3, U+1F3FE
+🧓🿠Older Person: Dark Skin Tone U+1F9D3, U+1F3FF
+👴 Old Man U+1F474
+👴🻠Old Man: Light Skin Tone U+1F474, U+1F3FB
+👴🼠Old Man: Medium-Light Skin Tone U+1F474, U+1F3FC
+👴🽠Old Man: Medium Skin Tone U+1F474, U+1F3FD
+👴🾠Old Man: Medium-Dark Skin Tone U+1F474, U+1F3FE
+👴🿠Old Man: Dark Skin Tone U+1F474, U+1F3FF
+👵 Old Woman U+1F475
+👵🻠Old Woman: Light Skin Tone U+1F475, U+1F3FB
+👵🼠Old Woman: Medium-Light Skin Tone U+1F475, U+1F3FC
+👵🽠Old Woman: Medium Skin Tone U+1F475, U+1F3FD
+👵🾠Old Woman: Medium-Dark Skin Tone U+1F475, U+1F3FE
+👵🿠Old Woman: Dark Skin Tone U+1F475, U+1F3FF
+🙠Person Frowning U+1F64D
+ðŸ™ðŸ» Person Frowning: Light Skin Tone U+1F64D, U+1F3FB
+ðŸ™ðŸ¼ Person Frowning: Medium-Light Skin Tone U+1F64D, U+1F3FC
+ðŸ™ðŸ½ Person Frowning: Medium Skin Tone U+1F64D, U+1F3FD
+ðŸ™ðŸ¾ Person Frowning: Medium-Dark Skin Tone U+1F64D, U+1F3FE
+ðŸ™ðŸ¿ Person Frowning: Dark Skin Tone U+1F64D, U+1F3FF
+ðŸ™â€â™‚ï¸ Man Frowning U+1F64D, U+200D, U+2642, U+FE0F
+ðŸ™ðŸ»â€â™‚ï¸ Man Frowning: Light Skin Tone U+1F64D, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ™ðŸ¼â€â™‚ï¸ Man Frowning: Medium-Light Skin Tone U+1F64D, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸ™ðŸ½â€â™‚ï¸ Man Frowning: Medium Skin Tone U+1F64D, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ™ðŸ¾â€â™‚ï¸ Man Frowning: Medium-Dark Skin Tone U+1F64D, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸ™ðŸ¿â€â™‚ï¸ Man Frowning: Dark Skin Tone U+1F64D, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ™â€â™€ï¸ Woman Frowning U+1F64D, U+200D, U+2640, U+FE0F
+ðŸ™ðŸ»â€â™€ï¸ Woman Frowning: Light Skin Tone U+1F64D, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ™ðŸ¼â€â™€ï¸ Woman Frowning: Medium-Light Skin Tone U+1F64D, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸ™ðŸ½â€â™€ï¸ Woman Frowning: Medium Skin Tone U+1F64D, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ™ðŸ¾â€â™€ï¸ Woman Frowning: Medium-Dark Skin Tone U+1F64D, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸ™ðŸ¿â€â™€ï¸ Woman Frowning: Dark Skin Tone U+1F64D, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🙎 Person Pouting U+1F64E
+🙎🻠Person Pouting: Light Skin Tone U+1F64E, U+1F3FB
+🙎🼠Person Pouting: Medium-Light Skin Tone U+1F64E, U+1F3FC
+🙎🽠Person Pouting: Medium Skin Tone U+1F64E, U+1F3FD
+🙎🾠Person Pouting: Medium-Dark Skin Tone U+1F64E, U+1F3FE
+🙎🿠Person Pouting: Dark Skin Tone U+1F64E, U+1F3FF
+🙎â€â™‚ï¸ Man Pouting U+1F64E, U+200D, U+2642, U+FE0F
+🙎ðŸ»â€â™‚ï¸ Man Pouting: Light Skin Tone U+1F64E, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🙎ðŸ¼â€â™‚ï¸ Man Pouting: Medium-Light Skin Tone U+1F64E, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🙎ðŸ½â€â™‚ï¸ Man Pouting: Medium Skin Tone U+1F64E, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🙎ðŸ¾â€â™‚ï¸ Man Pouting: Medium-Dark Skin Tone U+1F64E, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🙎ðŸ¿â€â™‚ï¸ Man Pouting: Dark Skin Tone U+1F64E, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🙎â€â™€ï¸ Woman Pouting U+1F64E, U+200D, U+2640, U+FE0F
+🙎ðŸ»â€â™€ï¸ Woman Pouting: Light Skin Tone U+1F64E, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🙎ðŸ¼â€â™€ï¸ Woman Pouting: Medium-Light Skin Tone U+1F64E, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🙎ðŸ½â€â™€ï¸ Woman Pouting: Medium Skin Tone U+1F64E, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🙎ðŸ¾â€â™€ï¸ Woman Pouting: Medium-Dark Skin Tone U+1F64E, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🙎ðŸ¿â€â™€ï¸ Woman Pouting: Dark Skin Tone U+1F64E, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🙅 Person Gesturing No U+1F645
+🙅🻠Person Gesturing No: Light Skin Tone U+1F645, U+1F3FB
+🙅🼠Person Gesturing No: Medium-Light Skin U+1F645, U+1F3FC
+Tone
+🙅🽠Person Gesturing No: Medium Skin Tone U+1F645, U+1F3FD
+🙅🾠Person Gesturing No: Medium-Dark Skin U+1F645, U+1F3FE
+Tone
+🙅🿠Person Gesturing No: Dark Skin Tone U+1F645, U+1F3FF
+🙅â€â™‚ï¸ Man Gesturing No U+1F645, U+200D, U+2642, U+FE0F
+🙅ðŸ»â€â™‚ï¸ Man Gesturing No: Light Skin Tone U+1F645, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🙅ðŸ¼â€â™‚ï¸ Man Gesturing No: Medium-Light Skin U+1F645, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🙅ðŸ½â€â™‚ï¸ Man Gesturing No: Medium Skin Tone U+1F645, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🙅ðŸ¾â€â™‚ï¸ Man Gesturing No: Medium-Dark Skin U+1F645, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🙅ðŸ¿â€â™‚ï¸ Man Gesturing No: Dark Skin Tone U+1F645, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🙅â€â™€ï¸ Woman Gesturing No U+1F645, U+200D, U+2640, U+FE0F
+🙅ðŸ»â€â™€ï¸ Woman Gesturing No: Light Skin Tone U+1F645, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🙅ðŸ¼â€â™€ï¸ Woman Gesturing No: Medium-Light Skin U+1F645, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🙅ðŸ½â€â™€ï¸ Woman Gesturing No: Medium Skin Tone U+1F645, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🙅ðŸ¾â€â™€ï¸ Woman Gesturing No: Medium-Dark Skin U+1F645, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🙅ðŸ¿â€â™€ï¸ Woman Gesturing No: Dark Skin Tone U+1F645, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🙆 Person Gesturing OK U+1F646
+🙆🻠Person Gesturing OK: Light Skin Tone U+1F646, U+1F3FB
+🙆🼠Person Gesturing OK: Medium-Light Skin U+1F646, U+1F3FC
+Tone
+🙆🽠Person Gesturing OK: Medium Skin Tone U+1F646, U+1F3FD
+🙆🾠Person Gesturing OK: Medium-Dark Skin U+1F646, U+1F3FE
+Tone
+🙆🿠Person Gesturing OK: Dark Skin Tone U+1F646, U+1F3FF
+🙆â€â™‚ï¸ Man Gesturing OK U+1F646, U+200D, U+2642, U+FE0F
+🙆ðŸ»â€â™‚ï¸ Man Gesturing OK: Light Skin Tone U+1F646, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🙆ðŸ¼â€â™‚ï¸ Man Gesturing OK: Medium-Light Skin U+1F646, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🙆ðŸ½â€â™‚ï¸ Man Gesturing OK: Medium Skin Tone U+1F646, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🙆ðŸ¾â€â™‚ï¸ Man Gesturing OK: Medium-Dark Skin U+1F646, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🙆ðŸ¿â€â™‚ï¸ Man Gesturing OK: Dark Skin Tone U+1F646, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🙆â€â™€ï¸ Woman Gesturing OK U+1F646, U+200D, U+2640, U+FE0F
+🙆ðŸ»â€â™€ï¸ Woman Gesturing OK: Light Skin Tone U+1F646, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🙆ðŸ¼â€â™€ï¸ Woman Gesturing OK: Medium-Light Skin U+1F646, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🙆ðŸ½â€â™€ï¸ Woman Gesturing OK: Medium Skin Tone U+1F646, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🙆ðŸ¾â€â™€ï¸ Woman Gesturing OK: Medium-Dark Skin U+1F646, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🙆ðŸ¿â€â™€ï¸ Woman Gesturing OK: Dark Skin Tone U+1F646, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+💠Person Tipping Hand U+1F481
+ðŸ’🻠Person Tipping Hand: Light Skin Tone U+1F481, U+1F3FB
+ðŸ’🼠Person Tipping Hand: Medium-Light Skin U+1F481, U+1F3FC
+Tone
+ðŸ’🽠Person Tipping Hand: Medium Skin Tone U+1F481, U+1F3FD
+ðŸ’🾠Person Tipping Hand: Medium-Dark Skin U+1F481, U+1F3FE
+Tone
+ðŸ’🿠Person Tipping Hand: Dark Skin Tone U+1F481, U+1F3FF
+ðŸ’â€â™‚ï¸ Man Tipping Hand U+1F481, U+200D, U+2642, U+FE0F
+ðŸ’ðŸ»â€â™‚ï¸ Man Tipping Hand: Light Skin Tone U+1F481, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ’ðŸ¼â€â™‚ï¸ Man Tipping Hand: Medium-Light Skin U+1F481, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+ðŸ’ðŸ½â€â™‚ï¸ Man Tipping Hand: Medium Skin Tone U+1F481, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ’ðŸ¾â€â™‚ï¸ Man Tipping Hand: Medium-Dark Skin U+1F481, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+ðŸ’ðŸ¿â€â™‚ï¸ Man Tipping Hand: Dark Skin Tone U+1F481, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ’â€â™€ï¸ Woman Tipping Hand U+1F481, U+200D, U+2640, U+FE0F
+ðŸ’ðŸ»â€â™€ï¸ Woman Tipping Hand: Light Skin Tone U+1F481, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ’ðŸ¼â€â™€ï¸ Woman Tipping Hand: Medium-Light Skin U+1F481, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+ðŸ’ðŸ½â€â™€ï¸ Woman Tipping Hand: Medium Skin Tone U+1F481, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ’ðŸ¾â€â™€ï¸ Woman Tipping Hand: Medium-Dark Skin U+1F481, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+ðŸ’ðŸ¿â€â™€ï¸ Woman Tipping Hand: Dark Skin Tone U+1F481, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🙋 Person Raising Hand U+1F64B
+🙋🻠Person Raising Hand: Light Skin Tone U+1F64B, U+1F3FB
+🙋🼠Person Raising Hand: Medium-Light Skin U+1F64B, U+1F3FC
+Tone
+🙋🽠Person Raising Hand: Medium Skin Tone U+1F64B, U+1F3FD
+🙋🾠Person Raising Hand: Medium-Dark Skin U+1F64B, U+1F3FE
+Tone
+🙋🿠Person Raising Hand: Dark Skin Tone U+1F64B, U+1F3FF
+🙋â€â™‚ï¸ Man Raising Hand U+1F64B, U+200D, U+2642, U+FE0F
+🙋ðŸ»â€â™‚ï¸ Man Raising Hand: Light Skin Tone U+1F64B, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🙋ðŸ¼â€â™‚ï¸ Man Raising Hand: Medium-Light Skin U+1F64B, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🙋ðŸ½â€â™‚ï¸ Man Raising Hand: Medium Skin Tone U+1F64B, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🙋ðŸ¾â€â™‚ï¸ Man Raising Hand: Medium-Dark Skin U+1F64B, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🙋ðŸ¿â€â™‚ï¸ Man Raising Hand: Dark Skin Tone U+1F64B, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🙋â€â™€ï¸ Woman Raising Hand U+1F64B, U+200D, U+2640, U+FE0F
+🙋ðŸ»â€â™€ï¸ Woman Raising Hand: Light Skin Tone U+1F64B, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🙋ðŸ¼â€â™€ï¸ Woman Raising Hand: Medium-Light Skin U+1F64B, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🙋ðŸ½â€â™€ï¸ Woman Raising Hand: Medium Skin Tone U+1F64B, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🙋ðŸ¾â€â™€ï¸ Woman Raising Hand: Medium-Dark Skin U+1F64B, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🙋ðŸ¿â€â™€ï¸ Woman Raising Hand: Dark Skin Tone U+1F64B, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧠Deaf Person U+1F9CF
+ðŸ§ðŸ» Deaf Person: Light Skin Tone U+1F9CF, U+1F3FB
+ðŸ§ðŸ¼ Deaf Person: Medium-Light Skin Tone U+1F9CF, U+1F3FC
+ðŸ§ðŸ½ Deaf Person: Medium Skin Tone U+1F9CF, U+1F3FD
+ðŸ§ðŸ¾ Deaf Person: Medium-Dark Skin Tone U+1F9CF, U+1F3FE
+ðŸ§ðŸ¿ Deaf Person: Dark Skin Tone U+1F9CF, U+1F3FF
+ðŸ§â€â™‚ï¸ Deaf Man U+1F9CF, U+200D, U+2642, U+FE0F
+ðŸ§ðŸ»â€â™‚ï¸ Deaf Man: Light Skin Tone U+1F9CF, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¼â€â™‚ï¸ Deaf Man: Medium-Light Skin Tone U+1F9CF, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ½â€â™‚ï¸ Deaf Man: Medium Skin Tone U+1F9CF, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¾â€â™‚ï¸ Deaf Man: Medium-Dark Skin Tone U+1F9CF, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¿â€â™‚ï¸ Deaf Man: Dark Skin Tone U+1F9CF, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ§â€â™€ï¸ Deaf Woman U+1F9CF, U+200D, U+2640, U+FE0F
+ðŸ§ðŸ»â€â™€ï¸ Deaf Woman: Light Skin Tone U+1F9CF, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¼â€â™€ï¸ Deaf Woman: Medium-Light Skin Tone U+1F9CF, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ½â€â™€ï¸ Deaf Woman: Medium Skin Tone U+1F9CF, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¾â€â™€ï¸ Deaf Woman: Medium-Dark Skin Tone U+1F9CF, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¿â€â™€ï¸ Deaf Woman: Dark Skin Tone U+1F9CF, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🙇 Person Bowing U+1F647
+🙇🻠Person Bowing: Light Skin Tone U+1F647, U+1F3FB
+🙇🼠Person Bowing: Medium-Light Skin Tone U+1F647, U+1F3FC
+🙇🽠Person Bowing: Medium Skin Tone U+1F647, U+1F3FD
+🙇🾠Person Bowing: Medium-Dark Skin Tone U+1F647, U+1F3FE
+🙇🿠Person Bowing: Dark Skin Tone U+1F647, U+1F3FF
+🙇â€â™‚ï¸ Man Bowing U+1F647, U+200D, U+2642, U+FE0F
+🙇ðŸ»â€â™‚ï¸ Man Bowing: Light Skin Tone U+1F647, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🙇ðŸ¼â€â™‚ï¸ Man Bowing: Medium-Light Skin Tone U+1F647, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🙇ðŸ½â€â™‚ï¸ Man Bowing: Medium Skin Tone U+1F647, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🙇ðŸ¾â€â™‚ï¸ Man Bowing: Medium-Dark Skin Tone U+1F647, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🙇ðŸ¿â€â™‚ï¸ Man Bowing: Dark Skin Tone U+1F647, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🙇â€â™€ï¸ Woman Bowing U+1F647, U+200D, U+2640, U+FE0F
+🙇ðŸ»â€â™€ï¸ Woman Bowing: Light Skin Tone U+1F647, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🙇ðŸ¼â€â™€ï¸ Woman Bowing: Medium-Light Skin Tone U+1F647, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🙇ðŸ½â€â™€ï¸ Woman Bowing: Medium Skin Tone U+1F647, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🙇ðŸ¾â€â™€ï¸ Woman Bowing: Medium-Dark Skin Tone U+1F647, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🙇ðŸ¿â€â™€ï¸ Woman Bowing: Dark Skin Tone U+1F647, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤦 Person Facepalming U+1F926
+🤦🻠Person Facepalming: Light Skin Tone U+1F926, U+1F3FB
+🤦🼠Person Facepalming: Medium-Light Skin U+1F926, U+1F3FC
+Tone
+🤦🽠Person Facepalming: Medium Skin Tone U+1F926, U+1F3FD
+🤦🾠Person Facepalming: Medium-Dark Skin Tone U+1F926, U+1F3FE
+🤦🿠Person Facepalming: Dark Skin Tone U+1F926, U+1F3FF
+🤦â€â™‚ï¸ Man Facepalming U+1F926, U+200D, U+2642, U+FE0F
+🤦ðŸ»â€â™‚ï¸ Man Facepalming: Light Skin Tone U+1F926, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤦ðŸ¼â€â™‚ï¸ Man Facepalming: Medium-Light Skin U+1F926, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🤦ðŸ½â€â™‚ï¸ Man Facepalming: Medium Skin Tone U+1F926, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤦ðŸ¾â€â™‚ï¸ Man Facepalming: Medium-Dark Skin Tone U+1F926, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🤦ðŸ¿â€â™‚ï¸ Man Facepalming: Dark Skin Tone U+1F926, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤦â€â™€ï¸ Woman Facepalming U+1F926, U+200D, U+2640, U+FE0F
+🤦ðŸ»â€â™€ï¸ Woman Facepalming: Light Skin Tone U+1F926, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🤦ðŸ¼â€â™€ï¸ Woman Facepalming: Medium-Light Skin U+1F926, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🤦ðŸ½â€â™€ï¸ Woman Facepalming: Medium Skin Tone U+1F926, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤦ðŸ¾â€â™€ï¸ Woman Facepalming: Medium-Dark Skin U+1F926, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🤦ðŸ¿â€â™€ï¸ Woman Facepalming: Dark Skin Tone U+1F926, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤷 Person Shrugging U+1F937
+🤷🻠Person Shrugging: Light Skin Tone U+1F937, U+1F3FB
+🤷🼠Person Shrugging: Medium-Light Skin Tone U+1F937, U+1F3FC
+🤷🽠Person Shrugging: Medium Skin Tone U+1F937, U+1F3FD
+🤷🾠Person Shrugging: Medium-Dark Skin Tone U+1F937, U+1F3FE
+🤷🿠Person Shrugging: Dark Skin Tone U+1F937, U+1F3FF
+🤷â€â™‚ï¸ Man Shrugging U+1F937, U+200D, U+2642, U+FE0F
+🤷ðŸ»â€â™‚ï¸ Man Shrugging: Light Skin Tone U+1F937, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤷ðŸ¼â€â™‚ï¸ Man Shrugging: Medium-Light Skin Tone U+1F937, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🤷ðŸ½â€â™‚ï¸ Man Shrugging: Medium Skin Tone U+1F937, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤷ðŸ¾â€â™‚ï¸ Man Shrugging: Medium-Dark Skin Tone U+1F937, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🤷ðŸ¿â€â™‚ï¸ Man Shrugging: Dark Skin Tone U+1F937, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤷â€â™€ï¸ Woman Shrugging U+1F937, U+200D, U+2640, U+FE0F
+🤷ðŸ»â€â™€ï¸ Woman Shrugging: Light Skin Tone U+1F937, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🤷ðŸ¼â€â™€ï¸ Woman Shrugging: Medium-Light Skin U+1F937, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🤷ðŸ½â€â™€ï¸ Woman Shrugging: Medium Skin Tone U+1F937, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤷ðŸ¾â€â™€ï¸ Woman Shrugging: Medium-Dark Skin Tone U+1F937, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🤷ðŸ¿â€â™€ï¸ Woman Shrugging: Dark Skin Tone U+1F937, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧑â€âš•ï¸ Health Worker U+1F9D1, U+200D, U+2695, U+FE0F
+🧑ðŸ»â€âš•ï¸ Health Worker: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+2695,
+ U+FE0F
+🧑ðŸ¼â€âš•ï¸ Health Worker: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+2695,
+ U+FE0F
+🧑ðŸ½â€âš•ï¸ Health Worker: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+2695,
+ U+FE0F
+🧑ðŸ¾â€âš•ï¸ Health Worker: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+2695,
+ U+FE0F
+🧑ðŸ¿â€âš•ï¸ Health Worker: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+2695,
+ U+FE0F
+👨â€âš•ï¸ Man Health Worker U+1F468, U+200D, U+2695, U+FE0F
+👨ðŸ»â€âš•ï¸ Man Health Worker: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+2695,
+ U+FE0F
+👨ðŸ¼â€âš•ï¸ Man Health Worker: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+2695,
+Tone U+FE0F
+👨ðŸ½â€âš•ï¸ Man Health Worker: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+2695,
+ U+FE0F
+👨ðŸ¾â€âš•ï¸ Man Health Worker: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+2695,
+Tone U+FE0F
+👨ðŸ¿â€âš•ï¸ Man Health Worker: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+2695,
+ U+FE0F
+👩â€âš•ï¸ Woman Health Worker U+1F469, U+200D, U+2695, U+FE0F
+👩ðŸ»â€âš•ï¸ Woman Health Worker: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+2695,
+ U+FE0F
+👩ðŸ¼â€âš•ï¸ Woman Health Worker: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+2695,
+Tone U+FE0F
+👩ðŸ½â€âš•ï¸ Woman Health Worker: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+2695,
+ U+FE0F
+👩ðŸ¾â€âš•ï¸ Woman Health Worker: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+2695,
+Tone U+FE0F
+👩ðŸ¿â€âš•ï¸ Woman Health Worker: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+2695,
+ U+FE0F
+🧑â€ðŸŽ“ Student U+1F9D1, U+200D, U+1F393
+🧑ðŸ»â€ðŸŽ“ Student: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F393
+🧑ðŸ¼â€ðŸŽ“ Student: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F393
+🧑ðŸ½â€ðŸŽ“ Student: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F393
+🧑ðŸ¾â€ðŸŽ“ Student: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F393
+🧑ðŸ¿â€ðŸŽ“ Student: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F393
+👨â€ðŸŽ“ Man Student U+1F468, U+200D, U+1F393
+👨ðŸ»â€ðŸŽ“ Man Student: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F393
+👨ðŸ¼â€ðŸŽ“ Man Student: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F393
+👨ðŸ½â€ðŸŽ“ Man Student: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F393
+👨ðŸ¾â€ðŸŽ“ Man Student: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F393
+👨ðŸ¿â€ðŸŽ“ Man Student: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F393
+👩â€ðŸŽ“ Woman Student U+1F469, U+200D, U+1F393
+👩ðŸ»â€ðŸŽ“ Woman Student: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F393
+👩ðŸ¼â€ðŸŽ“ Woman Student: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F393
+👩ðŸ½â€ðŸŽ“ Woman Student: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F393
+👩ðŸ¾â€ðŸŽ“ Woman Student: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F393
+👩ðŸ¿â€ðŸŽ“ Woman Student: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F393
+🧑â€ðŸ« Teacher U+1F9D1, U+200D, U+1F3EB
+🧑ðŸ»â€ðŸ« Teacher: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F3EB
+🧑ðŸ¼â€ðŸ« Teacher: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F3EB
+🧑ðŸ½â€ðŸ« Teacher: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F3EB
+🧑ðŸ¾â€ðŸ« Teacher: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F3EB
+🧑ðŸ¿â€ðŸ« Teacher: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F3EB
+👨â€ðŸ« Man Teacher U+1F468, U+200D, U+1F3EB
+👨ðŸ»â€ðŸ« Man Teacher: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F3EB
+👨ðŸ¼â€ðŸ« Man Teacher: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F3EB
+👨ðŸ½â€ðŸ« Man Teacher: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F3EB
+👨ðŸ¾â€ðŸ« Man Teacher: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F3EB
+👨ðŸ¿â€ðŸ« Man Teacher: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F3EB
+👩â€ðŸ« Woman Teacher U+1F469, U+200D, U+1F3EB
+👩ðŸ»â€ðŸ« Woman Teacher: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F3EB
+👩ðŸ¼â€ðŸ« Woman Teacher: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F3EB
+👩ðŸ½â€ðŸ« Woman Teacher: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F3EB
+👩ðŸ¾â€ðŸ« Woman Teacher: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F3EB
+👩ðŸ¿â€ðŸ« Woman Teacher: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F3EB
+🧑â€âš–ï¸ Judge U+1F9D1, U+200D, U+2696, U+FE0F
+🧑ðŸ»â€âš–ï¸ Judge: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+2696,
+ U+FE0F
+🧑ðŸ¼â€âš–ï¸ Judge: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+2696,
+ U+FE0F
+🧑ðŸ½â€âš–ï¸ Judge: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+2696,
+ U+FE0F
+🧑ðŸ¾â€âš–ï¸ Judge: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+2696,
+ U+FE0F
+🧑ðŸ¿â€âš–ï¸ Judge: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+2696,
+ U+FE0F
+👨â€âš–ï¸ Man Judge U+1F468, U+200D, U+2696, U+FE0F
+👨ðŸ»â€âš–ï¸ Man Judge: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+2696,
+ U+FE0F
+👨ðŸ¼â€âš–ï¸ Man Judge: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+2696,
+ U+FE0F
+👨ðŸ½â€âš–ï¸ Man Judge: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+2696,
+ U+FE0F
+👨ðŸ¾â€âš–ï¸ Man Judge: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+2696,
+ U+FE0F
+👨ðŸ¿â€âš–ï¸ Man Judge: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+2696,
+ U+FE0F
+👩â€âš–ï¸ Woman Judge U+1F469, U+200D, U+2696, U+FE0F
+👩ðŸ»â€âš–ï¸ Woman Judge: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+2696,
+ U+FE0F
+👩ðŸ¼â€âš–ï¸ Woman Judge: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+2696,
+ U+FE0F
+👩ðŸ½â€âš–ï¸ Woman Judge: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+2696,
+ U+FE0F
+👩ðŸ¾â€âš–ï¸ Woman Judge: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+2696,
+ U+FE0F
+👩ðŸ¿â€âš–ï¸ Woman Judge: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+2696,
+ U+FE0F
+🧑â€ðŸŒ¾ Farmer U+1F9D1, U+200D, U+1F33E
+🧑ðŸ»â€ðŸŒ¾ Farmer: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F33E
+🧑ðŸ¼â€ðŸŒ¾ Farmer: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F33E
+🧑ðŸ½â€ðŸŒ¾ Farmer: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F33E
+🧑ðŸ¾â€ðŸŒ¾ Farmer: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F33E
+🧑ðŸ¿â€ðŸŒ¾ Farmer: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F33E
+👨â€ðŸŒ¾ Man Farmer U+1F468, U+200D, U+1F33E
+👨ðŸ»â€ðŸŒ¾ Man Farmer: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F33E
+👨ðŸ¼â€ðŸŒ¾ Man Farmer: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F33E
+👨ðŸ½â€ðŸŒ¾ Man Farmer: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F33E
+👨ðŸ¾â€ðŸŒ¾ Man Farmer: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F33E
+👨ðŸ¿â€ðŸŒ¾ Man Farmer: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F33E
+👩â€ðŸŒ¾ Woman Farmer U+1F469, U+200D, U+1F33E
+👩ðŸ»â€ðŸŒ¾ Woman Farmer: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F33E
+👩ðŸ¼â€ðŸŒ¾ Woman Farmer: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F33E
+👩ðŸ½â€ðŸŒ¾ Woman Farmer: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F33E
+👩ðŸ¾â€ðŸŒ¾ Woman Farmer: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F33E
+👩ðŸ¿â€ðŸŒ¾ Woman Farmer: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F33E
+🧑â€ðŸ³ Cook U+1F9D1, U+200D, U+1F373
+🧑ðŸ»â€ðŸ³ Cook: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F373
+🧑ðŸ¼â€ðŸ³ Cook: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F373
+🧑ðŸ½â€ðŸ³ Cook: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F373
+🧑ðŸ¾â€ðŸ³ Cook: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F373
+🧑ðŸ¿â€ðŸ³ Cook: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F373
+👨â€ðŸ³ Man Cook U+1F468, U+200D, U+1F373
+👨ðŸ»â€ðŸ³ Man Cook: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F373
+👨ðŸ¼â€ðŸ³ Man Cook: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F373
+👨ðŸ½â€ðŸ³ Man Cook: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F373
+👨ðŸ¾â€ðŸ³ Man Cook: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F373
+👨ðŸ¿â€ðŸ³ Man Cook: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F373
+👩â€ðŸ³ Woman Cook U+1F469, U+200D, U+1F373
+👩ðŸ»â€ðŸ³ Woman Cook: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F373
+👩ðŸ¼â€ðŸ³ Woman Cook: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F373
+👩ðŸ½â€ðŸ³ Woman Cook: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F373
+👩ðŸ¾â€ðŸ³ Woman Cook: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F373
+👩ðŸ¿â€ðŸ³ Woman Cook: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F373
+🧑â€ðŸ”§ Mechanic U+1F9D1, U+200D, U+1F527
+🧑ðŸ»â€ðŸ”§ Mechanic: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F527
+🧑ðŸ¼â€ðŸ”§ Mechanic: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F527
+🧑ðŸ½â€ðŸ”§ Mechanic: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F527
+🧑ðŸ¾â€ðŸ”§ Mechanic: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F527
+🧑ðŸ¿â€ðŸ”§ Mechanic: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F527
+👨â€ðŸ”§ Man Mechanic U+1F468, U+200D, U+1F527
+👨ðŸ»â€ðŸ”§ Man Mechanic: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F527
+👨ðŸ¼â€ðŸ”§ Man Mechanic: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F527
+👨ðŸ½â€ðŸ”§ Man Mechanic: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F527
+👨ðŸ¾â€ðŸ”§ Man Mechanic: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F527
+👨ðŸ¿â€ðŸ”§ Man Mechanic: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F527
+👩â€ðŸ”§ Woman Mechanic U+1F469, U+200D, U+1F527
+👩ðŸ»â€ðŸ”§ Woman Mechanic: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F527
+👩ðŸ¼â€ðŸ”§ Woman Mechanic: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F527
+👩ðŸ½â€ðŸ”§ Woman Mechanic: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F527
+👩ðŸ¾â€ðŸ”§ Woman Mechanic: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F527
+👩ðŸ¿â€ðŸ”§ Woman Mechanic: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F527
+🧑â€ðŸ­ Factory Worker U+1F9D1, U+200D, U+1F3ED
+🧑ðŸ»â€ðŸ­ Factory Worker: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F3ED
+🧑ðŸ¼â€ðŸ­ Factory Worker: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F3ED
+🧑ðŸ½â€ðŸ­ Factory Worker: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F3ED
+🧑ðŸ¾â€ðŸ­ Factory Worker: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F3ED
+🧑ðŸ¿â€ðŸ­ Factory Worker: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F3ED
+👨â€ðŸ­ Man Factory Worker U+1F468, U+200D, U+1F3ED
+👨ðŸ»â€ðŸ­ Man Factory Worker: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F3ED
+👨ðŸ¼â€ðŸ­ Man Factory Worker: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F3ED
+Tone
+👨ðŸ½â€ðŸ­ Man Factory Worker: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F3ED
+👨ðŸ¾â€ðŸ­ Man Factory Worker: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F3ED
+Tone
+👨ðŸ¿â€ðŸ­ Man Factory Worker: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F3ED
+👩â€ðŸ­ Woman Factory Worker U+1F469, U+200D, U+1F3ED
+👩ðŸ»â€ðŸ­ Woman Factory Worker: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F3ED
+👩ðŸ¼â€ðŸ­ Woman Factory Worker: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+1F3ED
+Tone
+👩ðŸ½â€ðŸ­ Woman Factory Worker: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F3ED
+👩ðŸ¾â€ðŸ­ Woman Factory Worker: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F3ED
+Tone
+👩ðŸ¿â€ðŸ­ Woman Factory Worker: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F3ED
+🧑â€ðŸ’¼ Office Worker U+1F9D1, U+200D, U+1F4BC
+🧑ðŸ»â€ðŸ’¼ Office Worker: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F4BC
+🧑ðŸ¼â€ðŸ’¼ Office Worker: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F4BC
+🧑ðŸ½â€ðŸ’¼ Office Worker: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F4BC
+🧑ðŸ¾â€ðŸ’¼ Office Worker: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F4BC
+🧑ðŸ¿â€ðŸ’¼ Office Worker: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F4BC
+👨â€ðŸ’¼ Man Office Worker U+1F468, U+200D, U+1F4BC
+👨ðŸ»â€ðŸ’¼ Man Office Worker: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F4BC
+👨ðŸ¼â€ðŸ’¼ Man Office Worker: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F4BC
+Tone
+👨ðŸ½â€ðŸ’¼ Man Office Worker: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F4BC
+👨ðŸ¾â€ðŸ’¼ Man Office Worker: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F4BC
+Tone
+👨ðŸ¿â€ðŸ’¼ Man Office Worker: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F4BC
+👩â€ðŸ’¼ Woman Office Worker U+1F469, U+200D, U+1F4BC
+👩ðŸ»â€ðŸ’¼ Woman Office Worker: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F4BC
+👩ðŸ¼â€ðŸ’¼ Woman Office Worker: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+1F4BC
+Tone
+👩ðŸ½â€ðŸ’¼ Woman Office Worker: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F4BC
+👩ðŸ¾â€ðŸ’¼ Woman Office Worker: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F4BC
+Tone
+👩ðŸ¿â€ðŸ’¼ Woman Office Worker: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F4BC
+🧑â€ðŸ”¬ Scientist U+1F9D1, U+200D, U+1F52C
+🧑ðŸ»â€ðŸ”¬ Scientist: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F52C
+🧑ðŸ¼â€ðŸ”¬ Scientist: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F52C
+🧑ðŸ½â€ðŸ”¬ Scientist: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F52C
+🧑ðŸ¾â€ðŸ”¬ Scientist: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F52C
+🧑ðŸ¿â€ðŸ”¬ Scientist: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F52C
+👨â€ðŸ”¬ Man Scientist U+1F468, U+200D, U+1F52C
+👨ðŸ»â€ðŸ”¬ Man Scientist: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F52C
+👨ðŸ¼â€ðŸ”¬ Man Scientist: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F52C
+👨ðŸ½â€ðŸ”¬ Man Scientist: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F52C
+👨ðŸ¾â€ðŸ”¬ Man Scientist: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F52C
+👨ðŸ¿â€ðŸ”¬ Man Scientist: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F52C
+👩â€ðŸ”¬ Woman Scientist U+1F469, U+200D, U+1F52C
+👩ðŸ»â€ðŸ”¬ Woman Scientist: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F52C
+👩ðŸ¼â€ðŸ”¬ Woman Scientist: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F52C
+👩ðŸ½â€ðŸ”¬ Woman Scientist: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F52C
+👩ðŸ¾â€ðŸ”¬ Woman Scientist: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F52C
+👩ðŸ¿â€ðŸ”¬ Woman Scientist: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F52C
+🧑â€ðŸ’» Technologist U+1F9D1, U+200D, U+1F4BB
+🧑ðŸ»â€ðŸ’» Technologist: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F4BB
+🧑ðŸ¼â€ðŸ’» Technologist: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F4BB
+🧑ðŸ½â€ðŸ’» Technologist: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F4BB
+🧑ðŸ¾â€ðŸ’» Technologist: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F4BB
+🧑ðŸ¿â€ðŸ’» Technologist: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F4BB
+👨â€ðŸ’» Man Technologist U+1F468, U+200D, U+1F4BB
+👨ðŸ»â€ðŸ’» Man Technologist: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F4BB
+👨ðŸ¼â€ðŸ’» Man Technologist: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F4BB
+Tone
+👨ðŸ½â€ðŸ’» Man Technologist: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F4BB
+👨ðŸ¾â€ðŸ’» Man Technologist: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F4BB
+👨ðŸ¿â€ðŸ’» Man Technologist: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F4BB
+👩â€ðŸ’» Woman Technologist U+1F469, U+200D, U+1F4BB
+👩ðŸ»â€ðŸ’» Woman Technologist: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F4BB
+👩ðŸ¼â€ðŸ’» Woman Technologist: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+1F4BB
+Tone
+👩ðŸ½â€ðŸ’» Woman Technologist: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F4BB
+👩ðŸ¾â€ðŸ’» Woman Technologist: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F4BB
+Tone
+👩ðŸ¿â€ðŸ’» Woman Technologist: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F4BB
+🧑â€ðŸŽ¤ Singer U+1F9D1, U+200D, U+1F3A4
+🧑ðŸ»â€ðŸŽ¤ Singer: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F3A4
+🧑ðŸ¼â€ðŸŽ¤ Singer: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F3A4
+🧑ðŸ½â€ðŸŽ¤ Singer: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F3A4
+🧑ðŸ¾â€ðŸŽ¤ Singer: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F3A4
+🧑ðŸ¿â€ðŸŽ¤ Singer: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F3A4
+👨â€ðŸŽ¤ Man Singer U+1F468, U+200D, U+1F3A4
+👨ðŸ»â€ðŸŽ¤ Man Singer: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F3A4
+👨ðŸ¼â€ðŸŽ¤ Man Singer: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F3A4
+👨ðŸ½â€ðŸŽ¤ Man Singer: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F3A4
+👨ðŸ¾â€ðŸŽ¤ Man Singer: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F3A4
+👨ðŸ¿â€ðŸŽ¤ Man Singer: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F3A4
+👩â€ðŸŽ¤ Woman Singer U+1F469, U+200D, U+1F3A4
+👩ðŸ»â€ðŸŽ¤ Woman Singer: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F3A4
+👩ðŸ¼â€ðŸŽ¤ Woman Singer: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F3A4
+👩ðŸ½â€ðŸŽ¤ Woman Singer: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F3A4
+👩ðŸ¾â€ðŸŽ¤ Woman Singer: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F3A4
+👩ðŸ¿â€ðŸŽ¤ Woman Singer: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F3A4
+🧑â€ðŸŽ¨ Artist U+1F9D1, U+200D, U+1F3A8
+🧑ðŸ»â€ðŸŽ¨ Artist: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F3A8
+🧑ðŸ¼â€ðŸŽ¨ Artist: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F3A8
+🧑ðŸ½â€ðŸŽ¨ Artist: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F3A8
+🧑ðŸ¾â€ðŸŽ¨ Artist: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F3A8
+🧑ðŸ¿â€ðŸŽ¨ Artist: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F3A8
+👨â€ðŸŽ¨ Man Artist U+1F468, U+200D, U+1F3A8
+👨ðŸ»â€ðŸŽ¨ Man Artist: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F3A8
+👨ðŸ¼â€ðŸŽ¨ Man Artist: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F3A8
+👨ðŸ½â€ðŸŽ¨ Man Artist: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F3A8
+👨ðŸ¾â€ðŸŽ¨ Man Artist: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F3A8
+👨ðŸ¿â€ðŸŽ¨ Man Artist: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F3A8
+👩â€ðŸŽ¨ Woman Artist U+1F469, U+200D, U+1F3A8
+👩ðŸ»â€ðŸŽ¨ Woman Artist: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F3A8
+👩ðŸ¼â€ðŸŽ¨ Woman Artist: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F3A8
+👩ðŸ½â€ðŸŽ¨ Woman Artist: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F3A8
+👩ðŸ¾â€ðŸŽ¨ Woman Artist: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F3A8
+👩ðŸ¿â€ðŸŽ¨ Woman Artist: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F3A8
+🧑â€âœˆï¸ Pilot U+1F9D1, U+200D, U+2708, U+FE0F
+🧑ðŸ»â€âœˆï¸ Pilot: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+2708,
+ U+FE0F
+🧑ðŸ¼â€âœˆï¸ Pilot: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+2708,
+ U+FE0F
+🧑ðŸ½â€âœˆï¸ Pilot: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+2708,
+ U+FE0F
+🧑ðŸ¾â€âœˆï¸ Pilot: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+2708,
+ U+FE0F
+🧑ðŸ¿â€âœˆï¸ Pilot: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+2708,
+ U+FE0F
+👨â€âœˆï¸ Man Pilot U+1F468, U+200D, U+2708, U+FE0F
+👨ðŸ»â€âœˆï¸ Man Pilot: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+2708,
+ U+FE0F
+👨ðŸ¼â€âœˆï¸ Man Pilot: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+2708,
+ U+FE0F
+👨ðŸ½â€âœˆï¸ Man Pilot: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+2708,
+ U+FE0F
+👨ðŸ¾â€âœˆï¸ Man Pilot: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+2708,
+ U+FE0F
+👨ðŸ¿â€âœˆï¸ Man Pilot: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+2708,
+ U+FE0F
+👩â€âœˆï¸ Woman Pilot U+1F469, U+200D, U+2708, U+FE0F
+👩ðŸ»â€âœˆï¸ Woman Pilot: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+2708,
+ U+FE0F
+👩ðŸ¼â€âœˆï¸ Woman Pilot: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+2708,
+ U+FE0F
+👩ðŸ½â€âœˆï¸ Woman Pilot: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+2708,
+ U+FE0F
+👩ðŸ¾â€âœˆï¸ Woman Pilot: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+2708,
+ U+FE0F
+👩ðŸ¿â€âœˆï¸ Woman Pilot: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+2708,
+ U+FE0F
+🧑â€ðŸš€ Astronaut U+1F9D1, U+200D, U+1F680
+🧑ðŸ»â€ðŸš€ Astronaut: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F680
+🧑ðŸ¼â€ðŸš€ Astronaut: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F680
+🧑ðŸ½â€ðŸš€ Astronaut: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F680
+🧑ðŸ¾â€ðŸš€ Astronaut: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F680
+🧑ðŸ¿â€ðŸš€ Astronaut: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F680
+👨â€ðŸš€ Man Astronaut U+1F468, U+200D, U+1F680
+👨ðŸ»â€ðŸš€ Man Astronaut: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F680
+👨ðŸ¼â€ðŸš€ Man Astronaut: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F680
+👨ðŸ½â€ðŸš€ Man Astronaut: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F680
+👨ðŸ¾â€ðŸš€ Man Astronaut: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F680
+👨ðŸ¿â€ðŸš€ Man Astronaut: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F680
+👩â€ðŸš€ Woman Astronaut U+1F469, U+200D, U+1F680
+👩ðŸ»â€ðŸš€ Woman Astronaut: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F680
+👩ðŸ¼â€ðŸš€ Woman Astronaut: Medium-Light Skin Tone U+1F469, U+1F3FC, U+200D, U+1F680
+👩ðŸ½â€ðŸš€ Woman Astronaut: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F680
+👩ðŸ¾â€ðŸš€ Woman Astronaut: Medium-Dark Skin Tone U+1F469, U+1F3FE, U+200D, U+1F680
+👩ðŸ¿â€ðŸš€ Woman Astronaut: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F680
+🧑â€ðŸš’ Firefighter U+1F9D1, U+200D, U+1F692
+🧑ðŸ»â€ðŸš’ Firefighter: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F692
+🧑ðŸ¼â€ðŸš’ Firefighter: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F692
+🧑ðŸ½â€ðŸš’ Firefighter: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F692
+🧑ðŸ¾â€ðŸš’ Firefighter: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F692
+🧑ðŸ¿â€ðŸš’ Firefighter: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F692
+👨â€ðŸš’ Man Firefighter U+1F468, U+200D, U+1F692
+👨ðŸ»â€ðŸš’ Man Firefighter: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F692
+👨ðŸ¼â€ðŸš’ Man Firefighter: Medium-Light Skin Tone U+1F468, U+1F3FC, U+200D, U+1F692
+👨ðŸ½â€ðŸš’ Man Firefighter: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F692
+👨ðŸ¾â€ðŸš’ Man Firefighter: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F692
+👨ðŸ¿â€ðŸš’ Man Firefighter: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F692
+👩â€ðŸš’ Woman Firefighter U+1F469, U+200D, U+1F692
+👩ðŸ»â€ðŸš’ Woman Firefighter: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F692
+👩ðŸ¼â€ðŸš’ Woman Firefighter: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+1F692
+Tone
+👩ðŸ½â€ðŸš’ Woman Firefighter: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F692
+👩ðŸ¾â€ðŸš’ Woman Firefighter: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F692
+Tone
+👩ðŸ¿â€ðŸš’ Woman Firefighter: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F692
+👮 Police Officer U+1F46E
+👮🻠Police Officer: Light Skin Tone U+1F46E, U+1F3FB
+👮🼠Police Officer: Medium-Light Skin Tone U+1F46E, U+1F3FC
+👮🽠Police Officer: Medium Skin Tone U+1F46E, U+1F3FD
+👮🾠Police Officer: Medium-Dark Skin Tone U+1F46E, U+1F3FE
+👮🿠Police Officer: Dark Skin Tone U+1F46E, U+1F3FF
+👮â€â™‚ï¸ Man Police Officer U+1F46E, U+200D, U+2642, U+FE0F
+👮ðŸ»â€â™‚ï¸ Man Police Officer: Light Skin Tone U+1F46E, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+👮ðŸ¼â€â™‚ï¸ Man Police Officer: Medium-Light Skin U+1F46E, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+👮ðŸ½â€â™‚ï¸ Man Police Officer: Medium Skin Tone U+1F46E, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+👮ðŸ¾â€â™‚ï¸ Man Police Officer: Medium-Dark Skin U+1F46E, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+👮ðŸ¿â€â™‚ï¸ Man Police Officer: Dark Skin Tone U+1F46E, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+👮â€â™€ï¸ Woman Police Officer U+1F46E, U+200D, U+2640, U+FE0F
+👮ðŸ»â€â™€ï¸ Woman Police Officer: Light Skin Tone U+1F46E, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+👮ðŸ¼â€â™€ï¸ Woman Police Officer: Medium-Light U+1F46E, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+👮ðŸ½â€â™€ï¸ Woman Police Officer: Medium Skin Tone U+1F46E, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+👮ðŸ¾â€â™€ï¸ Woman Police Officer: Medium-Dark Skin U+1F46E, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+👮ðŸ¿â€â™€ï¸ Woman Police Officer: Dark Skin Tone U+1F46E, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+ðŸ•µï¸ Detective U+1F575, U+FE0F
+🕵🻠Detective: Light Skin Tone U+1F575, U+1F3FB
+🕵🼠Detective: Medium-Light Skin Tone U+1F575, U+1F3FC
+🕵🽠Detective: Medium Skin Tone U+1F575, U+1F3FD
+🕵🾠Detective: Medium-Dark Skin Tone U+1F575, U+1F3FE
+🕵🿠Detective: Dark Skin Tone U+1F575, U+1F3FF
+🕵ï¸â€â™‚ï¸ Man Detective U+1F575, U+FE0F, U+200D, U+2642,
+ U+FE0F
+🕵ðŸ»â€â™‚ï¸ Man Detective: Light Skin Tone U+1F575, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🕵ðŸ¼â€â™‚ï¸ Man Detective: Medium-Light Skin Tone U+1F575, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🕵ðŸ½â€â™‚ï¸ Man Detective: Medium Skin Tone U+1F575, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🕵ðŸ¾â€â™‚ï¸ Man Detective: Medium-Dark Skin Tone U+1F575, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🕵ðŸ¿â€â™‚ï¸ Man Detective: Dark Skin Tone U+1F575, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🕵ï¸â€â™€ï¸ Woman Detective U+1F575, U+FE0F, U+200D, U+2640,
+ U+FE0F
+🕵ðŸ»â€â™€ï¸ Woman Detective: Light Skin Tone U+1F575, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🕵ðŸ¼â€â™€ï¸ Woman Detective: Medium-Light Skin U+1F575, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🕵ðŸ½â€â™€ï¸ Woman Detective: Medium Skin Tone U+1F575, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🕵ðŸ¾â€â™€ï¸ Woman Detective: Medium-Dark Skin Tone U+1F575, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🕵ðŸ¿â€â™€ï¸ Woman Detective: Dark Skin Tone U+1F575, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+💂 Guard U+1F482
+💂🻠Guard: Light Skin Tone U+1F482, U+1F3FB
+💂🼠Guard: Medium-Light Skin Tone U+1F482, U+1F3FC
+💂🽠Guard: Medium Skin Tone U+1F482, U+1F3FD
+💂🾠Guard: Medium-Dark Skin Tone U+1F482, U+1F3FE
+💂🿠Guard: Dark Skin Tone U+1F482, U+1F3FF
+💂â€â™‚ï¸ Man Guard U+1F482, U+200D, U+2642, U+FE0F
+💂ðŸ»â€â™‚ï¸ Man Guard: Light Skin Tone U+1F482, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+💂ðŸ¼â€â™‚ï¸ Man Guard: Medium-Light Skin Tone U+1F482, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+💂ðŸ½â€â™‚ï¸ Man Guard: Medium Skin Tone U+1F482, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+💂ðŸ¾â€â™‚ï¸ Man Guard: Medium-Dark Skin Tone U+1F482, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+💂ðŸ¿â€â™‚ï¸ Man Guard: Dark Skin Tone U+1F482, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+💂â€â™€ï¸ Woman Guard U+1F482, U+200D, U+2640, U+FE0F
+💂ðŸ»â€â™€ï¸ Woman Guard: Light Skin Tone U+1F482, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+💂ðŸ¼â€â™€ï¸ Woman Guard: Medium-Light Skin Tone U+1F482, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+💂ðŸ½â€â™€ï¸ Woman Guard: Medium Skin Tone U+1F482, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+💂ðŸ¾â€â™€ï¸ Woman Guard: Medium-Dark Skin Tone U+1F482, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+💂ðŸ¿â€â™€ï¸ Woman Guard: Dark Skin Tone U+1F482, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🥷 Ninja U+1F977
+🥷🻠Ninja: Light Skin Tone U+1F977, U+1F3FB
+🥷🼠Ninja: Medium-Light Skin Tone U+1F977, U+1F3FC
+🥷🽠Ninja: Medium Skin Tone U+1F977, U+1F3FD
+🥷🾠Ninja: Medium-Dark Skin Tone U+1F977, U+1F3FE
+🥷🿠Ninja: Dark Skin Tone U+1F977, U+1F3FF
+👷 Construction Worker U+1F477
+👷🻠Construction Worker: Light Skin Tone U+1F477, U+1F3FB
+👷🼠Construction Worker: Medium-Light Skin U+1F477, U+1F3FC
+Tone
+👷🽠Construction Worker: Medium Skin Tone U+1F477, U+1F3FD
+👷🾠Construction Worker: Medium-Dark Skin U+1F477, U+1F3FE
+Tone
+👷🿠Construction Worker: Dark Skin Tone U+1F477, U+1F3FF
+👷â€â™‚ï¸ Man Construction Worker U+1F477, U+200D, U+2642, U+FE0F
+👷ðŸ»â€â™‚ï¸ Man Construction Worker: Light Skin U+1F477, U+1F3FB, U+200D, U+2642,
+Tone U+FE0F
+👷ðŸ¼â€â™‚ï¸ Man Construction Worker: Medium-Light U+1F477, U+1F3FC, U+200D, U+2642,
+Skin Tone U+FE0F
+👷ðŸ½â€â™‚ï¸ Man Construction Worker: Medium Skin U+1F477, U+1F3FD, U+200D, U+2642,
+Tone U+FE0F
+👷ðŸ¾â€â™‚ï¸ Man Construction Worker: Medium-Dark U+1F477, U+1F3FE, U+200D, U+2642,
+Skin Tone U+FE0F
+👷ðŸ¿â€â™‚ï¸ Man Construction Worker: Dark Skin U+1F477, U+1F3FF, U+200D, U+2642,
+Tone U+FE0F
+👷â€â™€ï¸ Woman Construction Worker U+1F477, U+200D, U+2640, U+FE0F
+👷ðŸ»â€â™€ï¸ Woman Construction Worker: Light Skin U+1F477, U+1F3FB, U+200D, U+2640,
+Tone U+FE0F
+👷ðŸ¼â€â™€ï¸ Woman Construction Worker: U+1F477, U+1F3FC, U+200D, U+2640,
+Medium-Light Skin Tone U+FE0F
+👷ðŸ½â€â™€ï¸ Woman Construction Worker: Medium Skin U+1F477, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+👷ðŸ¾â€â™€ï¸ Woman Construction Worker: Medium-Dark U+1F477, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+👷ðŸ¿â€â™€ï¸ Woman Construction Worker: Dark Skin U+1F477, U+1F3FF, U+200D, U+2640,
+Tone U+FE0F
+🤴 Prince U+1F934
+🤴🻠Prince: Light Skin Tone U+1F934, U+1F3FB
+🤴🼠Prince: Medium-Light Skin Tone U+1F934, U+1F3FC
+🤴🽠Prince: Medium Skin Tone U+1F934, U+1F3FD
+🤴🾠Prince: Medium-Dark Skin Tone U+1F934, U+1F3FE
+🤴🿠Prince: Dark Skin Tone U+1F934, U+1F3FF
+👸 Princess U+1F478
+👸🻠Princess: Light Skin Tone U+1F478, U+1F3FB
+👸🼠Princess: Medium-Light Skin Tone U+1F478, U+1F3FC
+👸🽠Princess: Medium Skin Tone U+1F478, U+1F3FD
+👸🾠Princess: Medium-Dark Skin Tone U+1F478, U+1F3FE
+👸🿠Princess: Dark Skin Tone U+1F478, U+1F3FF
+👳 Person Wearing Turban U+1F473
+👳🻠Person Wearing Turban: Light Skin Tone U+1F473, U+1F3FB
+👳🼠Person Wearing Turban: Medium-Light Skin U+1F473, U+1F3FC
+Tone
+👳🽠Person Wearing Turban: Medium Skin Tone U+1F473, U+1F3FD
+👳🾠Person Wearing Turban: Medium-Dark Skin U+1F473, U+1F3FE
+Tone
+👳🿠Person Wearing Turban: Dark Skin Tone U+1F473, U+1F3FF
+👳â€â™‚ï¸ Man Wearing Turban U+1F473, U+200D, U+2642, U+FE0F
+👳ðŸ»â€â™‚ï¸ Man Wearing Turban: Light Skin Tone U+1F473, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+👳ðŸ¼â€â™‚ï¸ Man Wearing Turban: Medium-Light Skin U+1F473, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+👳ðŸ½â€â™‚ï¸ Man Wearing Turban: Medium Skin Tone U+1F473, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+👳ðŸ¾â€â™‚ï¸ Man Wearing Turban: Medium-Dark Skin U+1F473, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+👳ðŸ¿â€â™‚ï¸ Man Wearing Turban: Dark Skin Tone U+1F473, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+👳â€â™€ï¸ Woman Wearing Turban U+1F473, U+200D, U+2640, U+FE0F
+👳ðŸ»â€â™€ï¸ Woman Wearing Turban: Light Skin Tone U+1F473, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+👳ðŸ¼â€â™€ï¸ Woman Wearing Turban: Medium-Light U+1F473, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+👳ðŸ½â€â™€ï¸ Woman Wearing Turban: Medium Skin Tone U+1F473, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+👳ðŸ¾â€â™€ï¸ Woman Wearing Turban: Medium-Dark Skin U+1F473, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+👳ðŸ¿â€â™€ï¸ Woman Wearing Turban: Dark Skin Tone U+1F473, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+👲 Person With Skullcap U+1F472
+👲🻠Person With Skullcap: Light Skin Tone U+1F472, U+1F3FB
+👲🼠Person With Skullcap: Medium-Light Skin U+1F472, U+1F3FC
+Tone
+👲🽠Person With Skullcap: Medium Skin Tone U+1F472, U+1F3FD
+👲🾠Person With Skullcap: Medium-Dark Skin U+1F472, U+1F3FE
+Tone
+👲🿠Person With Skullcap: Dark Skin Tone U+1F472, U+1F3FF
+🧕 Woman with Headscarf U+1F9D5
+🧕🻠Woman with Headscarf: Light Skin Tone U+1F9D5, U+1F3FB
+🧕🼠Woman with Headscarf: Medium-Light Skin U+1F9D5, U+1F3FC
+Tone
+🧕🽠Woman with Headscarf: Medium Skin Tone U+1F9D5, U+1F3FD
+🧕🾠Woman with Headscarf: Medium-Dark Skin U+1F9D5, U+1F3FE
+Tone
+🧕🿠Woman with Headscarf: Dark Skin Tone U+1F9D5, U+1F3FF
+🤵 Person in Tuxedo U+1F935
+🤵🻠Person in Tuxedo: Light Skin Tone U+1F935, U+1F3FB
+🤵🼠Person in Tuxedo: Medium-Light Skin Tone U+1F935, U+1F3FC
+🤵🽠Person in Tuxedo: Medium Skin Tone U+1F935, U+1F3FD
+🤵🾠Person in Tuxedo: Medium-Dark Skin Tone U+1F935, U+1F3FE
+🤵🿠Person in Tuxedo: Dark Skin Tone U+1F935, U+1F3FF
+🤵â€â™‚ï¸ Man in Tuxedo U+1F935, U+200D, U+2642, U+FE0F
+🤵ðŸ»â€â™‚ï¸ Man in Tuxedo: Light Skin Tone U+1F935, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤵ðŸ¼â€â™‚ï¸ Man in Tuxedo: Medium-Light Skin Tone U+1F935, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🤵ðŸ½â€â™‚ï¸ Man in Tuxedo: Medium Skin Tone U+1F935, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤵ðŸ¾â€â™‚ï¸ Man in Tuxedo: Medium-Dark Skin Tone U+1F935, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🤵ðŸ¿â€â™‚ï¸ Man in Tuxedo: Dark Skin Tone U+1F935, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤵â€â™€ï¸ Woman in Tuxedo U+1F935, U+200D, U+2640, U+FE0F
+🤵ðŸ»â€â™€ï¸ Woman in Tuxedo: Light Skin Tone U+1F935, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🤵ðŸ¼â€â™€ï¸ Woman in Tuxedo: Medium-Light Skin U+1F935, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🤵ðŸ½â€â™€ï¸ Woman in Tuxedo: Medium Skin Tone U+1F935, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤵ðŸ¾â€â™€ï¸ Woman in Tuxedo: Medium-Dark Skin Tone U+1F935, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🤵ðŸ¿â€â™€ï¸ Woman in Tuxedo: Dark Skin Tone U+1F935, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+👰 Person With Veil U+1F470
+👰🻠Person With Veil: Light Skin Tone U+1F470, U+1F3FB
+👰🼠Person With Veil: Medium-Light Skin Tone U+1F470, U+1F3FC
+👰🽠Person With Veil: Medium Skin Tone U+1F470, U+1F3FD
+👰🾠Person With Veil: Medium-Dark Skin Tone U+1F470, U+1F3FE
+👰🿠Person With Veil: Dark Skin Tone U+1F470, U+1F3FF
+👰â€â™‚ï¸ Man with Veil U+1F470, U+200D, U+2642, U+FE0F
+👰ðŸ»â€â™‚ï¸ Man with Veil: Light Skin Tone U+1F470, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+👰ðŸ¼â€â™‚ï¸ Man with Veil: Medium-Light Skin Tone U+1F470, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+👰ðŸ½â€â™‚ï¸ Man with Veil: Medium Skin Tone U+1F470, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+👰ðŸ¾â€â™‚ï¸ Man with Veil: Medium-Dark Skin Tone U+1F470, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+👰ðŸ¿â€â™‚ï¸ Man with Veil: Dark Skin Tone U+1F470, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+👰â€â™€ï¸ Woman with Veil U+1F470, U+200D, U+2640, U+FE0F
+👰ðŸ»â€â™€ï¸ Woman with Veil: Light Skin Tone U+1F470, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+👰ðŸ¼â€â™€ï¸ Woman with Veil: Medium-Light Skin U+1F470, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+👰ðŸ½â€â™€ï¸ Woman with Veil: Medium Skin Tone U+1F470, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+👰ðŸ¾â€â™€ï¸ Woman with Veil: Medium-Dark Skin Tone U+1F470, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+👰ðŸ¿â€â™€ï¸ Woman with Veil: Dark Skin Tone U+1F470, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤰 Pregnant Woman U+1F930
+🤰🻠Pregnant Woman: Light Skin Tone U+1F930, U+1F3FB
+🤰🼠Pregnant Woman: Medium-Light Skin Tone U+1F930, U+1F3FC
+🤰🽠Pregnant Woman: Medium Skin Tone U+1F930, U+1F3FD
+🤰🾠Pregnant Woman: Medium-Dark Skin Tone U+1F930, U+1F3FE
+🤰🿠Pregnant Woman: Dark Skin Tone U+1F930, U+1F3FF
+🤱 Breast-Feeding U+1F931
+🤱🻠Breast-Feeding: Light Skin Tone U+1F931, U+1F3FB
+🤱🼠Breast-Feeding: Medium-Light Skin Tone U+1F931, U+1F3FC
+🤱🽠Breast-Feeding: Medium Skin Tone U+1F931, U+1F3FD
+🤱🾠Breast-Feeding: Medium-Dark Skin Tone U+1F931, U+1F3FE
+🤱🿠Breast-Feeding: Dark Skin Tone U+1F931, U+1F3FF
+👩â€ðŸ¼ Woman Feeding Baby U+1F469, U+200D, U+1F37C
+👩ðŸ»â€ðŸ¼ Woman Feeding Baby: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F37C
+👩ðŸ¼â€ðŸ¼ Woman Feeding Baby: Medium-Light Skin U+1F469, U+1F3FC, U+200D, U+1F37C
+Tone
+👩ðŸ½â€ðŸ¼ Woman Feeding Baby: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F37C
+👩ðŸ¾â€ðŸ¼ Woman Feeding Baby: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F37C
+Tone
+👩ðŸ¿â€ðŸ¼ Woman Feeding Baby: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F37C
+👨â€ðŸ¼ Man Feeding Baby U+1F468, U+200D, U+1F37C
+👨ðŸ»â€ðŸ¼ Man Feeding Baby: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F37C
+👨ðŸ¼â€ðŸ¼ Man Feeding Baby: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F37C
+Tone
+👨ðŸ½â€ðŸ¼ Man Feeding Baby: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F37C
+👨ðŸ¾â€ðŸ¼ Man Feeding Baby: Medium-Dark Skin Tone U+1F468, U+1F3FE, U+200D, U+1F37C
+👨ðŸ¿â€ðŸ¼ Man Feeding Baby: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F37C
+🧑â€ðŸ¼ Person Feeding Baby U+1F9D1, U+200D, U+1F37C
+🧑ðŸ»â€ðŸ¼ Person Feeding Baby: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F37C
+🧑ðŸ¼â€ðŸ¼ Person Feeding Baby: Medium-Light Skin U+1F9D1, U+1F3FC, U+200D, U+1F37C
+Tone
+🧑ðŸ½â€ðŸ¼ Person Feeding Baby: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F37C
+🧑ðŸ¾â€ðŸ¼ Person Feeding Baby: Medium-Dark Skin U+1F9D1, U+1F3FE, U+200D, U+1F37C
+Tone
+🧑ðŸ¿â€ðŸ¼ Person Feeding Baby: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F37C
+👼 Baby Angel U+1F47C
+👼🻠Baby Angel: Light Skin Tone U+1F47C, U+1F3FB
+👼🼠Baby Angel: Medium-Light Skin Tone U+1F47C, U+1F3FC
+👼🽠Baby Angel: Medium Skin Tone U+1F47C, U+1F3FD
+👼🾠Baby Angel: Medium-Dark Skin Tone U+1F47C, U+1F3FE
+👼🿠Baby Angel: Dark Skin Tone U+1F47C, U+1F3FF
+🎅 Santa Claus U+1F385
+🎅🻠Santa Claus: Light Skin Tone U+1F385, U+1F3FB
+🎅🼠Santa Claus: Medium-Light Skin Tone U+1F385, U+1F3FC
+🎅🽠Santa Claus: Medium Skin Tone U+1F385, U+1F3FD
+🎅🾠Santa Claus: Medium-Dark Skin Tone U+1F385, U+1F3FE
+🎅🿠Santa Claus: Dark Skin Tone U+1F385, U+1F3FF
+🤶 Mrs. Claus U+1F936
+🤶🻠Mrs. Claus: Light Skin Tone U+1F936, U+1F3FB
+🤶🼠Mrs. Claus: Medium-Light Skin Tone U+1F936, U+1F3FC
+🤶🽠Mrs. Claus: Medium Skin Tone U+1F936, U+1F3FD
+🤶🾠Mrs. Claus: Medium-Dark Skin Tone U+1F936, U+1F3FE
+🤶🿠Mrs. Claus: Dark Skin Tone U+1F936, U+1F3FF
+🧑â€ðŸŽ„ Mx Claus U+1F9D1, U+200D, U+1F384
+🧑ðŸ»â€ðŸŽ„ Mx Claus: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F384
+🧑ðŸ¼â€ðŸŽ„ Mx Claus: Medium-Light Skin Tone U+1F9D1, U+1F3FC, U+200D, U+1F384
+🧑ðŸ½â€ðŸŽ„ Mx Claus: Medium Skin Tone U+1F9D1, U+1F3FD, U+200D, U+1F384
+🧑ðŸ¾â€ðŸŽ„ Mx Claus: Medium-Dark Skin Tone U+1F9D1, U+1F3FE, U+200D, U+1F384
+🧑ðŸ¿â€ðŸŽ„ Mx Claus: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F384
+🦸 Superhero U+1F9B8
+🦸🻠Superhero: Light Skin Tone U+1F9B8, U+1F3FB
+🦸🼠Superhero: Medium-Light Skin Tone U+1F9B8, U+1F3FC
+🦸🽠Superhero: Medium Skin Tone U+1F9B8, U+1F3FD
+🦸🾠Superhero: Medium-Dark Skin Tone U+1F9B8, U+1F3FE
+🦸🿠Superhero: Dark Skin Tone U+1F9B8, U+1F3FF
+🦸â€â™‚ï¸ Man Superhero U+1F9B8, U+200D, U+2642, U+FE0F
+🦸ðŸ»â€â™‚ï¸ Man Superhero: Light Skin Tone U+1F9B8, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🦸ðŸ¼â€â™‚ï¸ Man Superhero: Medium-Light Skin Tone U+1F9B8, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🦸ðŸ½â€â™‚ï¸ Man Superhero: Medium Skin Tone U+1F9B8, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🦸ðŸ¾â€â™‚ï¸ Man Superhero: Medium-Dark Skin Tone U+1F9B8, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🦸ðŸ¿â€â™‚ï¸ Man Superhero: Dark Skin Tone U+1F9B8, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🦸â€â™€ï¸ Woman Superhero U+1F9B8, U+200D, U+2640, U+FE0F
+🦸ðŸ»â€â™€ï¸ Woman Superhero: Light Skin Tone U+1F9B8, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🦸ðŸ¼â€â™€ï¸ Woman Superhero: Medium-Light Skin U+1F9B8, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🦸ðŸ½â€â™€ï¸ Woman Superhero: Medium Skin Tone U+1F9B8, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🦸ðŸ¾â€â™€ï¸ Woman Superhero: Medium-Dark Skin Tone U+1F9B8, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🦸ðŸ¿â€â™€ï¸ Woman Superhero: Dark Skin Tone U+1F9B8, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🦹 Supervillain U+1F9B9
+🦹🻠Supervillain: Light Skin Tone U+1F9B9, U+1F3FB
+🦹🼠Supervillain: Medium-Light Skin Tone U+1F9B9, U+1F3FC
+🦹🽠Supervillain: Medium Skin Tone U+1F9B9, U+1F3FD
+🦹🾠Supervillain: Medium-Dark Skin Tone U+1F9B9, U+1F3FE
+🦹🿠Supervillain: Dark Skin Tone U+1F9B9, U+1F3FF
+🦹â€â™‚ï¸ Man Supervillain U+1F9B9, U+200D, U+2642, U+FE0F
+🦹ðŸ»â€â™‚ï¸ Man Supervillain: Light Skin Tone U+1F9B9, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🦹ðŸ¼â€â™‚ï¸ Man Supervillain: Medium-Light Skin U+1F9B9, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🦹ðŸ½â€â™‚ï¸ Man Supervillain: Medium Skin Tone U+1F9B9, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🦹ðŸ¾â€â™‚ï¸ Man Supervillain: Medium-Dark Skin U+1F9B9, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🦹ðŸ¿â€â™‚ï¸ Man Supervillain: Dark Skin Tone U+1F9B9, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🦹â€â™€ï¸ Woman Supervillain U+1F9B9, U+200D, U+2640, U+FE0F
+🦹ðŸ»â€â™€ï¸ Woman Supervillain: Light Skin Tone U+1F9B9, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🦹ðŸ¼â€â™€ï¸ Woman Supervillain: Medium-Light Skin U+1F9B9, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🦹ðŸ½â€â™€ï¸ Woman Supervillain: Medium Skin Tone U+1F9B9, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🦹ðŸ¾â€â™€ï¸ Woman Supervillain: Medium-Dark Skin U+1F9B9, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🦹ðŸ¿â€â™€ï¸ Woman Supervillain: Dark Skin Tone U+1F9B9, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧙 Mage U+1F9D9
+🧙🻠Mage: Light Skin Tone U+1F9D9, U+1F3FB
+🧙🼠Mage: Medium-Light Skin Tone U+1F9D9, U+1F3FC
+🧙🽠Mage: Medium Skin Tone U+1F9D9, U+1F3FD
+🧙🾠Mage: Medium-Dark Skin Tone U+1F9D9, U+1F3FE
+🧙🿠Mage: Dark Skin Tone U+1F9D9, U+1F3FF
+🧙â€â™‚ï¸ Man Mage U+1F9D9, U+200D, U+2642, U+FE0F
+🧙ðŸ»â€â™‚ï¸ Man Mage: Light Skin Tone U+1F9D9, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧙ðŸ¼â€â™‚ï¸ Man Mage: Medium-Light Skin Tone U+1F9D9, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧙ðŸ½â€â™‚ï¸ Man Mage: Medium Skin Tone U+1F9D9, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧙ðŸ¾â€â™‚ï¸ Man Mage: Medium-Dark Skin Tone U+1F9D9, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧙ðŸ¿â€â™‚ï¸ Man Mage: Dark Skin Tone U+1F9D9, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧙â€â™€ï¸ Woman Mage U+1F9D9, U+200D, U+2640, U+FE0F
+🧙ðŸ»â€â™€ï¸ Woman Mage: Light Skin Tone U+1F9D9, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧙ðŸ¼â€â™€ï¸ Woman Mage: Medium-Light Skin Tone U+1F9D9, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧙ðŸ½â€â™€ï¸ Woman Mage: Medium Skin Tone U+1F9D9, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧙ðŸ¾â€â™€ï¸ Woman Mage: Medium-Dark Skin Tone U+1F9D9, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧙ðŸ¿â€â™€ï¸ Woman Mage: Dark Skin Tone U+1F9D9, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧚 Fairy U+1F9DA
+🧚🻠Fairy: Light Skin Tone U+1F9DA, U+1F3FB
+🧚🼠Fairy: Medium-Light Skin Tone U+1F9DA, U+1F3FC
+🧚🽠Fairy: Medium Skin Tone U+1F9DA, U+1F3FD
+🧚🾠Fairy: Medium-Dark Skin Tone U+1F9DA, U+1F3FE
+🧚🿠Fairy: Dark Skin Tone U+1F9DA, U+1F3FF
+🧚â€â™‚ï¸ Man Fairy U+1F9DA, U+200D, U+2642, U+FE0F
+🧚ðŸ»â€â™‚ï¸ Man Fairy: Light Skin Tone U+1F9DA, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧚ðŸ¼â€â™‚ï¸ Man Fairy: Medium-Light Skin Tone U+1F9DA, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧚ðŸ½â€â™‚ï¸ Man Fairy: Medium Skin Tone U+1F9DA, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧚ðŸ¾â€â™‚ï¸ Man Fairy: Medium-Dark Skin Tone U+1F9DA, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧚ðŸ¿â€â™‚ï¸ Man Fairy: Dark Skin Tone U+1F9DA, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧚â€â™€ï¸ Woman Fairy U+1F9DA, U+200D, U+2640, U+FE0F
+🧚ðŸ»â€â™€ï¸ Woman Fairy: Light Skin Tone U+1F9DA, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧚ðŸ¼â€â™€ï¸ Woman Fairy: Medium-Light Skin Tone U+1F9DA, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧚ðŸ½â€â™€ï¸ Woman Fairy: Medium Skin Tone U+1F9DA, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧚ðŸ¾â€â™€ï¸ Woman Fairy: Medium-Dark Skin Tone U+1F9DA, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧚ðŸ¿â€â™€ï¸ Woman Fairy: Dark Skin Tone U+1F9DA, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧛 Vampire U+1F9DB
+🧛🻠Vampire: Light Skin Tone U+1F9DB, U+1F3FB
+🧛🼠Vampire: Medium-Light Skin Tone U+1F9DB, U+1F3FC
+🧛🽠Vampire: Medium Skin Tone U+1F9DB, U+1F3FD
+🧛🾠Vampire: Medium-Dark Skin Tone U+1F9DB, U+1F3FE
+🧛🿠Vampire: Dark Skin Tone U+1F9DB, U+1F3FF
+🧛â€â™‚ï¸ Man Vampire U+1F9DB, U+200D, U+2642, U+FE0F
+🧛ðŸ»â€â™‚ï¸ Man Vampire: Light Skin Tone U+1F9DB, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧛ðŸ¼â€â™‚ï¸ Man Vampire: Medium-Light Skin Tone U+1F9DB, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧛ðŸ½â€â™‚ï¸ Man Vampire: Medium Skin Tone U+1F9DB, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧛ðŸ¾â€â™‚ï¸ Man Vampire: Medium-Dark Skin Tone U+1F9DB, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧛ðŸ¿â€â™‚ï¸ Man Vampire: Dark Skin Tone U+1F9DB, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧛â€â™€ï¸ Woman Vampire U+1F9DB, U+200D, U+2640, U+FE0F
+🧛ðŸ»â€â™€ï¸ Woman Vampire: Light Skin Tone U+1F9DB, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧛ðŸ¼â€â™€ï¸ Woman Vampire: Medium-Light Skin Tone U+1F9DB, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧛ðŸ½â€â™€ï¸ Woman Vampire: Medium Skin Tone U+1F9DB, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧛ðŸ¾â€â™€ï¸ Woman Vampire: Medium-Dark Skin Tone U+1F9DB, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧛ðŸ¿â€â™€ï¸ Woman Vampire: Dark Skin Tone U+1F9DB, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧜 Merperson U+1F9DC
+🧜🻠Merperson: Light Skin Tone U+1F9DC, U+1F3FB
+🧜🼠Merperson: Medium-Light Skin Tone U+1F9DC, U+1F3FC
+🧜🽠Merperson: Medium Skin Tone U+1F9DC, U+1F3FD
+🧜🾠Merperson: Medium-Dark Skin Tone U+1F9DC, U+1F3FE
+🧜🿠Merperson: Dark Skin Tone U+1F9DC, U+1F3FF
+🧜â€â™‚ï¸ Merman U+1F9DC, U+200D, U+2642, U+FE0F
+🧜ðŸ»â€â™‚ï¸ Merman: Light Skin Tone U+1F9DC, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧜ðŸ¼â€â™‚ï¸ Merman: Medium-Light Skin Tone U+1F9DC, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧜ðŸ½â€â™‚ï¸ Merman: Medium Skin Tone U+1F9DC, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧜ðŸ¾â€â™‚ï¸ Merman: Medium-Dark Skin Tone U+1F9DC, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧜ðŸ¿â€â™‚ï¸ Merman: Dark Skin Tone U+1F9DC, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧜â€â™€ï¸ Mermaid U+1F9DC, U+200D, U+2640, U+FE0F
+🧜ðŸ»â€â™€ï¸ Mermaid: Light Skin Tone U+1F9DC, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧜ðŸ¼â€â™€ï¸ Mermaid: Medium-Light Skin Tone U+1F9DC, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧜ðŸ½â€â™€ï¸ Mermaid: Medium Skin Tone U+1F9DC, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧜ðŸ¾â€â™€ï¸ Mermaid: Medium-Dark Skin Tone U+1F9DC, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧜ðŸ¿â€â™€ï¸ Mermaid: Dark Skin Tone U+1F9DC, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧠Elf U+1F9DD
+ðŸ§ðŸ» Elf: Light Skin Tone U+1F9DD, U+1F3FB
+ðŸ§ðŸ¼ Elf: Medium-Light Skin Tone U+1F9DD, U+1F3FC
+ðŸ§ðŸ½ Elf: Medium Skin Tone U+1F9DD, U+1F3FD
+ðŸ§ðŸ¾ Elf: Medium-Dark Skin Tone U+1F9DD, U+1F3FE
+ðŸ§ðŸ¿ Elf: Dark Skin Tone U+1F9DD, U+1F3FF
+ðŸ§â€â™‚ï¸ Man Elf U+1F9DD, U+200D, U+2642, U+FE0F
+ðŸ§ðŸ»â€â™‚ï¸ Man Elf: Light Skin Tone U+1F9DD, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¼â€â™‚ï¸ Man Elf: Medium-Light Skin Tone U+1F9DD, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ½â€â™‚ï¸ Man Elf: Medium Skin Tone U+1F9DD, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¾â€â™‚ï¸ Man Elf: Medium-Dark Skin Tone U+1F9DD, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¿â€â™‚ï¸ Man Elf: Dark Skin Tone U+1F9DD, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ§â€â™€ï¸ Woman Elf U+1F9DD, U+200D, U+2640, U+FE0F
+ðŸ§ðŸ»â€â™€ï¸ Woman Elf: Light Skin Tone U+1F9DD, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¼â€â™€ï¸ Woman Elf: Medium-Light Skin Tone U+1F9DD, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ½â€â™€ï¸ Woman Elf: Medium Skin Tone U+1F9DD, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¾â€â™€ï¸ Woman Elf: Medium-Dark Skin Tone U+1F9DD, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¿â€â™€ï¸ Woman Elf: Dark Skin Tone U+1F9DD, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧞 Genie U+1F9DE
+🧞â€â™‚ï¸ Man Genie U+1F9DE, U+200D, U+2642, U+FE0F
+🧞â€â™€ï¸ Woman Genie U+1F9DE, U+200D, U+2640, U+FE0F
+🧟 Zombie U+1F9DF
+🧟â€â™‚ï¸ Man Zombie U+1F9DF, U+200D, U+2642, U+FE0F
+🧟â€â™€ï¸ Woman Zombie U+1F9DF, U+200D, U+2640, U+FE0F
+💆 Person Getting Massage U+1F486
+💆🻠Person Getting Massage: Light Skin Tone U+1F486, U+1F3FB
+💆🼠Person Getting Massage: Medium-Light Skin U+1F486, U+1F3FC
+Tone
+💆🽠Person Getting Massage: Medium Skin Tone U+1F486, U+1F3FD
+💆🾠Person Getting Massage: Medium-Dark Skin U+1F486, U+1F3FE
+Tone
+💆🿠Person Getting Massage: Dark Skin Tone U+1F486, U+1F3FF
+💆â€â™‚ï¸ Man Getting Massage U+1F486, U+200D, U+2642, U+FE0F
+💆ðŸ»â€â™‚ï¸ Man Getting Massage: Light Skin Tone U+1F486, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+💆ðŸ¼â€â™‚ï¸ Man Getting Massage: Medium-Light Skin U+1F486, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+💆ðŸ½â€â™‚ï¸ Man Getting Massage: Medium Skin Tone U+1F486, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+💆ðŸ¾â€â™‚ï¸ Man Getting Massage: Medium-Dark Skin U+1F486, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+💆ðŸ¿â€â™‚ï¸ Man Getting Massage: Dark Skin Tone U+1F486, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+💆â€â™€ï¸ Woman Getting Massage U+1F486, U+200D, U+2640, U+FE0F
+💆ðŸ»â€â™€ï¸ Woman Getting Massage: Light Skin Tone U+1F486, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+💆ðŸ¼â€â™€ï¸ Woman Getting Massage: Medium-Light U+1F486, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+💆ðŸ½â€â™€ï¸ Woman Getting Massage: Medium Skin U+1F486, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+💆ðŸ¾â€â™€ï¸ Woman Getting Massage: Medium-Dark U+1F486, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+💆ðŸ¿â€â™€ï¸ Woman Getting Massage: Dark Skin Tone U+1F486, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+💇 Person Getting Haircut U+1F487
+💇🻠Person Getting Haircut: Light Skin Tone U+1F487, U+1F3FB
+💇🼠Person Getting Haircut: Medium-Light Skin U+1F487, U+1F3FC
+Tone
+💇🽠Person Getting Haircut: Medium Skin Tone U+1F487, U+1F3FD
+💇🾠Person Getting Haircut: Medium-Dark Skin U+1F487, U+1F3FE
+Tone
+💇🿠Person Getting Haircut: Dark Skin Tone U+1F487, U+1F3FF
+💇â€â™‚ï¸ Man Getting Haircut U+1F487, U+200D, U+2642, U+FE0F
+💇ðŸ»â€â™‚ï¸ Man Getting Haircut: Light Skin Tone U+1F487, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+💇ðŸ¼â€â™‚ï¸ Man Getting Haircut: Medium-Light Skin U+1F487, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+💇ðŸ½â€â™‚ï¸ Man Getting Haircut: Medium Skin Tone U+1F487, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+💇ðŸ¾â€â™‚ï¸ Man Getting Haircut: Medium-Dark Skin U+1F487, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+💇ðŸ¿â€â™‚ï¸ Man Getting Haircut: Dark Skin Tone U+1F487, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+💇â€â™€ï¸ Woman Getting Haircut U+1F487, U+200D, U+2640, U+FE0F
+💇ðŸ»â€â™€ï¸ Woman Getting Haircut: Light Skin Tone U+1F487, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+💇ðŸ¼â€â™€ï¸ Woman Getting Haircut: Medium-Light U+1F487, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+💇ðŸ½â€â™€ï¸ Woman Getting Haircut: Medium Skin U+1F487, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+💇ðŸ¾â€â™€ï¸ Woman Getting Haircut: Medium-Dark U+1F487, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+💇ðŸ¿â€â™€ï¸ Woman Getting Haircut: Dark Skin Tone U+1F487, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🚶 Person Walking U+1F6B6
+🚶🻠Person Walking: Light Skin Tone U+1F6B6, U+1F3FB
+🚶🼠Person Walking: Medium-Light Skin Tone U+1F6B6, U+1F3FC
+🚶🽠Person Walking: Medium Skin Tone U+1F6B6, U+1F3FD
+🚶🾠Person Walking: Medium-Dark Skin Tone U+1F6B6, U+1F3FE
+🚶🿠Person Walking: Dark Skin Tone U+1F6B6, U+1F3FF
+🚶â€â™‚ï¸ Man Walking U+1F6B6, U+200D, U+2642, U+FE0F
+🚶ðŸ»â€â™‚ï¸ Man Walking: Light Skin Tone U+1F6B6, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🚶ðŸ¼â€â™‚ï¸ Man Walking: Medium-Light Skin Tone U+1F6B6, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🚶ðŸ½â€â™‚ï¸ Man Walking: Medium Skin Tone U+1F6B6, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🚶ðŸ¾â€â™‚ï¸ Man Walking: Medium-Dark Skin Tone U+1F6B6, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🚶ðŸ¿â€â™‚ï¸ Man Walking: Dark Skin Tone U+1F6B6, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🚶â€â™€ï¸ Woman Walking U+1F6B6, U+200D, U+2640, U+FE0F
+🚶ðŸ»â€â™€ï¸ Woman Walking: Light Skin Tone U+1F6B6, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🚶ðŸ¼â€â™€ï¸ Woman Walking: Medium-Light Skin Tone U+1F6B6, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🚶ðŸ½â€â™€ï¸ Woman Walking: Medium Skin Tone U+1F6B6, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🚶ðŸ¾â€â™€ï¸ Woman Walking: Medium-Dark Skin Tone U+1F6B6, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🚶ðŸ¿â€â™€ï¸ Woman Walking: Dark Skin Tone U+1F6B6, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧠Person Standing U+1F9CD
+ðŸ§ðŸ» Person Standing: Light Skin Tone U+1F9CD, U+1F3FB
+ðŸ§ðŸ¼ Person Standing: Medium-Light Skin Tone U+1F9CD, U+1F3FC
+ðŸ§ðŸ½ Person Standing: Medium Skin Tone U+1F9CD, U+1F3FD
+ðŸ§ðŸ¾ Person Standing: Medium-Dark Skin Tone U+1F9CD, U+1F3FE
+ðŸ§ðŸ¿ Person Standing: Dark Skin Tone U+1F9CD, U+1F3FF
+ðŸ§â€â™‚ï¸ Man Standing U+1F9CD, U+200D, U+2642, U+FE0F
+ðŸ§ðŸ»â€â™‚ï¸ Man Standing: Light Skin Tone U+1F9CD, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¼â€â™‚ï¸ Man Standing: Medium-Light Skin Tone U+1F9CD, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ½â€â™‚ï¸ Man Standing: Medium Skin Tone U+1F9CD, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¾â€â™‚ï¸ Man Standing: Medium-Dark Skin Tone U+1F9CD, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸ§ðŸ¿â€â™‚ï¸ Man Standing: Dark Skin Tone U+1F9CD, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ§â€â™€ï¸ Woman Standing U+1F9CD, U+200D, U+2640, U+FE0F
+ðŸ§ðŸ»â€â™€ï¸ Woman Standing: Light Skin Tone U+1F9CD, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¼â€â™€ï¸ Woman Standing: Medium-Light Skin Tone U+1F9CD, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ½â€â™€ï¸ Woman Standing: Medium Skin Tone U+1F9CD, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¾â€â™€ï¸ Woman Standing: Medium-Dark Skin Tone U+1F9CD, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸ§ðŸ¿â€â™€ï¸ Woman Standing: Dark Skin Tone U+1F9CD, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧎 Person Kneeling U+1F9CE
+🧎🻠Person Kneeling: Light Skin Tone U+1F9CE, U+1F3FB
+🧎🼠Person Kneeling: Medium-Light Skin Tone U+1F9CE, U+1F3FC
+🧎🽠Person Kneeling: Medium Skin Tone U+1F9CE, U+1F3FD
+🧎🾠Person Kneeling: Medium-Dark Skin Tone U+1F9CE, U+1F3FE
+🧎🿠Person Kneeling: Dark Skin Tone U+1F9CE, U+1F3FF
+🧎â€â™‚ï¸ Man Kneeling U+1F9CE, U+200D, U+2642, U+FE0F
+🧎ðŸ»â€â™‚ï¸ Man Kneeling: Light Skin Tone U+1F9CE, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧎ðŸ¼â€â™‚ï¸ Man Kneeling: Medium-Light Skin Tone U+1F9CE, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧎ðŸ½â€â™‚ï¸ Man Kneeling: Medium Skin Tone U+1F9CE, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧎ðŸ¾â€â™‚ï¸ Man Kneeling: Medium-Dark Skin Tone U+1F9CE, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧎ðŸ¿â€â™‚ï¸ Man Kneeling: Dark Skin Tone U+1F9CE, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧎â€â™€ï¸ Woman Kneeling U+1F9CE, U+200D, U+2640, U+FE0F
+🧎ðŸ»â€â™€ï¸ Woman Kneeling: Light Skin Tone U+1F9CE, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧎ðŸ¼â€â™€ï¸ Woman Kneeling: Medium-Light Skin Tone U+1F9CE, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧎ðŸ½â€â™€ï¸ Woman Kneeling: Medium Skin Tone U+1F9CE, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧎ðŸ¾â€â™€ï¸ Woman Kneeling: Medium-Dark Skin Tone U+1F9CE, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧎ðŸ¿â€â™€ï¸ Woman Kneeling: Dark Skin Tone U+1F9CE, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧑â€ðŸ¦¯ Person with White Cane U+1F9D1, U+200D, U+1F9AF
+🧑ðŸ»â€ðŸ¦¯ Person with White Cane: Light Skin Tone U+1F9D1, U+1F3FB, U+200D, U+1F9AF
+🧑ðŸ¼â€ðŸ¦¯ Person with White Cane: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F9AF
+Skin Tone
+🧑ðŸ½â€ðŸ¦¯ Person with White Cane: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F9AF
+Tone
+🧑ðŸ¾â€ðŸ¦¯ Person with White Cane: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F9AF
+Skin Tone
+🧑ðŸ¿â€ðŸ¦¯ Person with White Cane: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F9AF
+👨â€ðŸ¦¯ Man with White Cane U+1F468, U+200D, U+1F9AF
+👨ðŸ»â€ðŸ¦¯ Man with White Cane: Light Skin Tone U+1F468, U+1F3FB, U+200D, U+1F9AF
+👨ðŸ¼â€ðŸ¦¯ Man with White Cane: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F9AF
+Tone
+👨ðŸ½â€ðŸ¦¯ Man with White Cane: Medium Skin Tone U+1F468, U+1F3FD, U+200D, U+1F9AF
+👨ðŸ¾â€ðŸ¦¯ Man with White Cane: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F9AF
+Tone
+👨ðŸ¿â€ðŸ¦¯ Man with White Cane: Dark Skin Tone U+1F468, U+1F3FF, U+200D, U+1F9AF
+👩â€ðŸ¦¯ Woman with White Cane U+1F469, U+200D, U+1F9AF
+👩ðŸ»â€ðŸ¦¯ Woman with White Cane: Light Skin Tone U+1F469, U+1F3FB, U+200D, U+1F9AF
+👩ðŸ¼â€ðŸ¦¯ Woman with White Cane: Medium-Light U+1F469, U+1F3FC, U+200D, U+1F9AF
+Skin Tone
+👩ðŸ½â€ðŸ¦¯ Woman with White Cane: Medium Skin Tone U+1F469, U+1F3FD, U+200D, U+1F9AF
+👩ðŸ¾â€ðŸ¦¯ Woman with White Cane: Medium-Dark Skin U+1F469, U+1F3FE, U+200D, U+1F9AF
+Tone
+👩ðŸ¿â€ðŸ¦¯ Woman with White Cane: Dark Skin Tone U+1F469, U+1F3FF, U+200D, U+1F9AF
+🧑â€ðŸ¦¼ Person in Motorized Wheelchair U+1F9D1, U+200D, U+1F9BC
+🧑ðŸ»â€ðŸ¦¼ Person in Motorized Wheelchair: Light U+1F9D1, U+1F3FB, U+200D, U+1F9BC
+Skin Tone
+🧑ðŸ¼â€ðŸ¦¼ Person in Motorized Wheelchair: U+1F9D1, U+1F3FC, U+200D, U+1F9BC
+Medium-Light Skin Tone
+🧑ðŸ½â€ðŸ¦¼ Person in Motorized Wheelchair: Medium U+1F9D1, U+1F3FD, U+200D, U+1F9BC
+Skin Tone
+🧑ðŸ¾â€ðŸ¦¼ Person in Motorized Wheelchair: U+1F9D1, U+1F3FE, U+200D, U+1F9BC
+Medium-Dark Skin Tone
+🧑ðŸ¿â€ðŸ¦¼ Person in Motorized Wheelchair: Dark U+1F9D1, U+1F3FF, U+200D, U+1F9BC
+Skin Tone
+👨â€ðŸ¦¼ Man in Motorized Wheelchair U+1F468, U+200D, U+1F9BC
+👨ðŸ»â€ðŸ¦¼ Man in Motorized Wheelchair: Light Skin U+1F468, U+1F3FB, U+200D, U+1F9BC
+Tone
+👨ðŸ¼â€ðŸ¦¼ Man in Motorized Wheelchair: U+1F468, U+1F3FC, U+200D, U+1F9BC
+Medium-Light Skin Tone
+👨ðŸ½â€ðŸ¦¼ Man in Motorized Wheelchair: Medium U+1F468, U+1F3FD, U+200D, U+1F9BC
+Skin Tone
+👨ðŸ¾â€ðŸ¦¼ Man in Motorized Wheelchair: U+1F468, U+1F3FE, U+200D, U+1F9BC
+Medium-Dark Skin Tone
+👨ðŸ¿â€ðŸ¦¼ Man in Motorized Wheelchair: Dark Skin U+1F468, U+1F3FF, U+200D, U+1F9BC
+Tone
+👩â€ðŸ¦¼ Woman in Motorized Wheelchair U+1F469, U+200D, U+1F9BC
+👩ðŸ»â€ðŸ¦¼ Woman in Motorized Wheelchair: Light U+1F469, U+1F3FB, U+200D, U+1F9BC
+Skin Tone
+👩ðŸ¼â€ðŸ¦¼ Woman in Motorized Wheelchair: U+1F469, U+1F3FC, U+200D, U+1F9BC
+Medium-Light Skin Tone
+👩ðŸ½â€ðŸ¦¼ Woman in Motorized Wheelchair: Medium U+1F469, U+1F3FD, U+200D, U+1F9BC
+Skin Tone
+👩ðŸ¾â€ðŸ¦¼ Woman in Motorized Wheelchair: U+1F469, U+1F3FE, U+200D, U+1F9BC
+Medium-Dark Skin Tone
+👩ðŸ¿â€ðŸ¦¼ Woman in Motorized Wheelchair: Dark U+1F469, U+1F3FF, U+200D, U+1F9BC
+Skin Tone
+🧑â€ðŸ¦½ Person in Manual Wheelchair U+1F9D1, U+200D, U+1F9BD
+🧑ðŸ»â€ðŸ¦½ Person in Manual Wheelchair: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F9BD
+Tone
+🧑ðŸ¼â€ðŸ¦½ Person in Manual Wheelchair: U+1F9D1, U+1F3FC, U+200D, U+1F9BD
+Medium-Light Skin Tone
+🧑ðŸ½â€ðŸ¦½ Person in Manual Wheelchair: Medium U+1F9D1, U+1F3FD, U+200D, U+1F9BD
+Skin Tone
+🧑ðŸ¾â€ðŸ¦½ Person in Manual Wheelchair: U+1F9D1, U+1F3FE, U+200D, U+1F9BD
+Medium-Dark Skin Tone
+🧑ðŸ¿â€ðŸ¦½ Person in Manual Wheelchair: Dark Skin U+1F9D1, U+1F3FF, U+200D, U+1F9BD
+Tone
+👨â€ðŸ¦½ Man in Manual Wheelchair U+1F468, U+200D, U+1F9BD
+👨ðŸ»â€ðŸ¦½ Man in Manual Wheelchair: Light Skin U+1F468, U+1F3FB, U+200D, U+1F9BD
+Tone
+👨ðŸ¼â€ðŸ¦½ Man in Manual Wheelchair: Medium-Light U+1F468, U+1F3FC, U+200D, U+1F9BD
+Skin Tone
+👨ðŸ½â€ðŸ¦½ Man in Manual Wheelchair: Medium Skin U+1F468, U+1F3FD, U+200D, U+1F9BD
+Tone
+👨ðŸ¾â€ðŸ¦½ Man in Manual Wheelchair: Medium-Dark U+1F468, U+1F3FE, U+200D, U+1F9BD
+Skin Tone
+👨ðŸ¿â€ðŸ¦½ Man in Manual Wheelchair: Dark Skin U+1F468, U+1F3FF, U+200D, U+1F9BD
+Tone
+👩â€ðŸ¦½ Woman in Manual Wheelchair U+1F469, U+200D, U+1F9BD
+👩ðŸ»â€ðŸ¦½ Woman in Manual Wheelchair: Light Skin U+1F469, U+1F3FB, U+200D, U+1F9BD
+Tone
+👩ðŸ¼â€ðŸ¦½ Woman in Manual Wheelchair: U+1F469, U+1F3FC, U+200D, U+1F9BD
+Medium-Light Skin Tone
+👩ðŸ½â€ðŸ¦½ Woman in Manual Wheelchair: Medium Skin U+1F469, U+1F3FD, U+200D, U+1F9BD
+Tone
+👩ðŸ¾â€ðŸ¦½ Woman in Manual Wheelchair: Medium-Dark U+1F469, U+1F3FE, U+200D, U+1F9BD
+Skin Tone
+👩ðŸ¿â€ðŸ¦½ Woman in Manual Wheelchair: Dark Skin U+1F469, U+1F3FF, U+200D, U+1F9BD
+Tone
+🃠Person Running U+1F3C3
+ðŸƒðŸ» Person Running: Light Skin Tone U+1F3C3, U+1F3FB
+ðŸƒðŸ¼ Person Running: Medium-Light Skin Tone U+1F3C3, U+1F3FC
+ðŸƒðŸ½ Person Running: Medium Skin Tone U+1F3C3, U+1F3FD
+ðŸƒðŸ¾ Person Running: Medium-Dark Skin Tone U+1F3C3, U+1F3FE
+ðŸƒðŸ¿ Person Running: Dark Skin Tone U+1F3C3, U+1F3FF
+ðŸƒâ€â™‚ï¸ Man Running U+1F3C3, U+200D, U+2642, U+FE0F
+ðŸƒðŸ»â€â™‚ï¸ Man Running: Light Skin Tone U+1F3C3, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸƒðŸ¼â€â™‚ï¸ Man Running: Medium-Light Skin Tone U+1F3C3, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸƒðŸ½â€â™‚ï¸ Man Running: Medium Skin Tone U+1F3C3, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸƒðŸ¾â€â™‚ï¸ Man Running: Medium-Dark Skin Tone U+1F3C3, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸƒðŸ¿â€â™‚ï¸ Man Running: Dark Skin Tone U+1F3C3, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸƒâ€â™€ï¸ Woman Running U+1F3C3, U+200D, U+2640, U+FE0F
+ðŸƒðŸ»â€â™€ï¸ Woman Running: Light Skin Tone U+1F3C3, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸƒðŸ¼â€â™€ï¸ Woman Running: Medium-Light Skin Tone U+1F3C3, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸƒðŸ½â€â™€ï¸ Woman Running: Medium Skin Tone U+1F3C3, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸƒðŸ¾â€â™€ï¸ Woman Running: Medium-Dark Skin Tone U+1F3C3, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸƒðŸ¿â€â™€ï¸ Woman Running: Dark Skin Tone U+1F3C3, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+💃 Woman Dancing U+1F483
+💃🻠Woman Dancing: Light Skin Tone U+1F483, U+1F3FB
+💃🼠Woman Dancing: Medium-Light Skin Tone U+1F483, U+1F3FC
+💃🽠Woman Dancing: Medium Skin Tone U+1F483, U+1F3FD
+💃🾠Woman Dancing: Medium-Dark Skin Tone U+1F483, U+1F3FE
+💃🿠Woman Dancing: Dark Skin Tone U+1F483, U+1F3FF
+🕺 Man Dancing U+1F57A
+🕺🻠Man Dancing: Light Skin Tone U+1F57A, U+1F3FB
+🕺🼠Man Dancing: Medium-Light Skin Tone U+1F57A, U+1F3FC
+🕺🽠Man Dancing: Medium Skin Tone U+1F57A, U+1F3FD
+🕺🾠Man Dancing: Medium-Dark Skin Tone U+1F57A, U+1F3FE
+🕺🿠Man Dancing: Dark Skin Tone U+1F57A, U+1F3FF
+ðŸ•´ï¸ Person in Suit Levitating U+1F574, U+FE0F
+🕴🻠Person in Suit Levitating: Light Skin U+1F574, U+1F3FB
+Tone
+🕴🼠Person in Suit Levitating: Medium-Light U+1F574, U+1F3FC
+Skin Tone
+🕴🽠Person in Suit Levitating: Medium Skin U+1F574, U+1F3FD
+Tone
+🕴🾠Person in Suit Levitating: Medium-Dark U+1F574, U+1F3FE
+Skin Tone
+🕴🿠Person in Suit Levitating: Dark Skin Tone U+1F574, U+1F3FF
+👯 People with Bunny Ears U+1F46F
+👯â€â™‚ï¸ Men with Bunny Ears U+1F46F, U+200D, U+2642, U+FE0F
+👯â€â™€ï¸ Women with Bunny Ears U+1F46F, U+200D, U+2640, U+FE0F
+🧖 Person in Steamy Room U+1F9D6
+🧖🻠Person in Steamy Room: Light Skin Tone U+1F9D6, U+1F3FB
+🧖🼠Person in Steamy Room: Medium-Light Skin U+1F9D6, U+1F3FC
+Tone
+🧖🽠Person in Steamy Room: Medium Skin Tone U+1F9D6, U+1F3FD
+🧖🾠Person in Steamy Room: Medium-Dark Skin U+1F9D6, U+1F3FE
+Tone
+🧖🿠Person in Steamy Room: Dark Skin Tone U+1F9D6, U+1F3FF
+🧖â€â™‚ï¸ Man in Steamy Room U+1F9D6, U+200D, U+2642, U+FE0F
+🧖ðŸ»â€â™‚ï¸ Man in Steamy Room: Light Skin Tone U+1F9D6, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧖ðŸ¼â€â™‚ï¸ Man in Steamy Room: Medium-Light Skin U+1F9D6, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🧖ðŸ½â€â™‚ï¸ Man in Steamy Room: Medium Skin Tone U+1F9D6, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧖ðŸ¾â€â™‚ï¸ Man in Steamy Room: Medium-Dark Skin U+1F9D6, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🧖ðŸ¿â€â™‚ï¸ Man in Steamy Room: Dark Skin Tone U+1F9D6, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧖â€â™€ï¸ Woman in Steamy Room U+1F9D6, U+200D, U+2640, U+FE0F
+🧖ðŸ»â€â™€ï¸ Woman in Steamy Room: Light Skin Tone U+1F9D6, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧖ðŸ¼â€â™€ï¸ Woman in Steamy Room: Medium-Light U+1F9D6, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+🧖ðŸ½â€â™€ï¸ Woman in Steamy Room: Medium Skin Tone U+1F9D6, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧖ðŸ¾â€â™€ï¸ Woman in Steamy Room: Medium-Dark Skin U+1F9D6, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🧖ðŸ¿â€â™€ï¸ Woman in Steamy Room: Dark Skin Tone U+1F9D6, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧗 Person Climbing U+1F9D7
+🧗🻠Person Climbing: Light Skin Tone U+1F9D7, U+1F3FB
+🧗🼠Person Climbing: Medium-Light Skin Tone U+1F9D7, U+1F3FC
+🧗🽠Person Climbing: Medium Skin Tone U+1F9D7, U+1F3FD
+🧗🾠Person Climbing: Medium-Dark Skin Tone U+1F9D7, U+1F3FE
+🧗🿠Person Climbing: Dark Skin Tone U+1F9D7, U+1F3FF
+🧗â€â™‚ï¸ Man Climbing U+1F9D7, U+200D, U+2642, U+FE0F
+🧗ðŸ»â€â™‚ï¸ Man Climbing: Light Skin Tone U+1F9D7, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧗ðŸ¼â€â™‚ï¸ Man Climbing: Medium-Light Skin Tone U+1F9D7, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🧗ðŸ½â€â™‚ï¸ Man Climbing: Medium Skin Tone U+1F9D7, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🧗ðŸ¾â€â™‚ï¸ Man Climbing: Medium-Dark Skin Tone U+1F9D7, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🧗ðŸ¿â€â™‚ï¸ Man Climbing: Dark Skin Tone U+1F9D7, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧗â€â™€ï¸ Woman Climbing U+1F9D7, U+200D, U+2640, U+FE0F
+🧗ðŸ»â€â™€ï¸ Woman Climbing: Light Skin Tone U+1F9D7, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧗ðŸ¼â€â™€ï¸ Woman Climbing: Medium-Light Skin Tone U+1F9D7, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🧗ðŸ½â€â™€ï¸ Woman Climbing: Medium Skin Tone U+1F9D7, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🧗ðŸ¾â€â™€ï¸ Woman Climbing: Medium-Dark Skin Tone U+1F9D7, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🧗ðŸ¿â€â™€ï¸ Woman Climbing: Dark Skin Tone U+1F9D7, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤺 Person Fencing U+1F93A
+🇠Horse Racing U+1F3C7
+ðŸ‡ðŸ» Horse Racing: Light Skin Tone U+1F3C7, U+1F3FB
+ðŸ‡ðŸ¼ Horse Racing: Medium-Light Skin Tone U+1F3C7, U+1F3FC
+ðŸ‡ðŸ½ Horse Racing: Medium Skin Tone U+1F3C7, U+1F3FD
+ðŸ‡ðŸ¾ Horse Racing: Medium-Dark Skin Tone U+1F3C7, U+1F3FE
+ðŸ‡ðŸ¿ Horse Racing: Dark Skin Tone U+1F3C7, U+1F3FF
+â›·ï¸ Skier U+26F7, U+FE0F
+🂠Snowboarder U+1F3C2
+ðŸ‚🻠Snowboarder: Light Skin Tone U+1F3C2, U+1F3FB
+ðŸ‚🼠Snowboarder: Medium-Light Skin Tone U+1F3C2, U+1F3FC
+ðŸ‚🽠Snowboarder: Medium Skin Tone U+1F3C2, U+1F3FD
+ðŸ‚🾠Snowboarder: Medium-Dark Skin Tone U+1F3C2, U+1F3FE
+ðŸ‚🿠Snowboarder: Dark Skin Tone U+1F3C2, U+1F3FF
+ðŸŒï¸ Person Golfing U+1F3CC, U+FE0F
+ðŸŒðŸ» Person Golfing: Light Skin Tone U+1F3CC, U+1F3FB
+ðŸŒðŸ¼ Person Golfing: Medium-Light Skin Tone U+1F3CC, U+1F3FC
+ðŸŒðŸ½ Person Golfing: Medium Skin Tone U+1F3CC, U+1F3FD
+ðŸŒðŸ¾ Person Golfing: Medium-Dark Skin Tone U+1F3CC, U+1F3FE
+ðŸŒðŸ¿ Person Golfing: Dark Skin Tone U+1F3CC, U+1F3FF
+ðŸŒï¸â€â™‚ï¸ Man Golfing U+1F3CC, U+FE0F, U+200D, U+2642,
+ U+FE0F
+ðŸŒðŸ»â€â™‚ï¸ Man Golfing: Light Skin Tone U+1F3CC, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸŒðŸ¼â€â™‚ï¸ Man Golfing: Medium-Light Skin Tone U+1F3CC, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸŒðŸ½â€â™‚ï¸ Man Golfing: Medium Skin Tone U+1F3CC, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸŒðŸ¾â€â™‚ï¸ Man Golfing: Medium-Dark Skin Tone U+1F3CC, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸŒðŸ¿â€â™‚ï¸ Man Golfing: Dark Skin Tone U+1F3CC, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸŒï¸â€â™€ï¸ Woman Golfing U+1F3CC, U+FE0F, U+200D, U+2640,
+ U+FE0F
+ðŸŒðŸ»â€â™€ï¸ Woman Golfing: Light Skin Tone U+1F3CC, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸŒðŸ¼â€â™€ï¸ Woman Golfing: Medium-Light Skin Tone U+1F3CC, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸŒðŸ½â€â™€ï¸ Woman Golfing: Medium Skin Tone U+1F3CC, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸŒðŸ¾â€â™€ï¸ Woman Golfing: Medium-Dark Skin Tone U+1F3CC, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸŒðŸ¿â€â™€ï¸ Woman Golfing: Dark Skin Tone U+1F3CC, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🄠Person Surfing U+1F3C4
+ðŸ„🻠Person Surfing: Light Skin Tone U+1F3C4, U+1F3FB
+ðŸ„🼠Person Surfing: Medium-Light Skin Tone U+1F3C4, U+1F3FC
+ðŸ„🽠Person Surfing: Medium Skin Tone U+1F3C4, U+1F3FD
+ðŸ„🾠Person Surfing: Medium-Dark Skin Tone U+1F3C4, U+1F3FE
+ðŸ„🿠Person Surfing: Dark Skin Tone U+1F3C4, U+1F3FF
+ðŸ„â€â™‚ï¸ Man Surfing U+1F3C4, U+200D, U+2642, U+FE0F
+ðŸ„ðŸ»â€â™‚ï¸ Man Surfing: Light Skin Tone U+1F3C4, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ„ðŸ¼â€â™‚ï¸ Man Surfing: Medium-Light Skin Tone U+1F3C4, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸ„ðŸ½â€â™‚ï¸ Man Surfing: Medium Skin Tone U+1F3C4, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ„ðŸ¾â€â™‚ï¸ Man Surfing: Medium-Dark Skin Tone U+1F3C4, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸ„ðŸ¿â€â™‚ï¸ Man Surfing: Dark Skin Tone U+1F3C4, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ„â€â™€ï¸ Woman Surfing U+1F3C4, U+200D, U+2640, U+FE0F
+ðŸ„ðŸ»â€â™€ï¸ Woman Surfing: Light Skin Tone U+1F3C4, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ„ðŸ¼â€â™€ï¸ Woman Surfing: Medium-Light Skin Tone U+1F3C4, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸ„ðŸ½â€â™€ï¸ Woman Surfing: Medium Skin Tone U+1F3C4, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸ„ðŸ¾â€â™€ï¸ Woman Surfing: Medium-Dark Skin Tone U+1F3C4, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸ„ðŸ¿â€â™€ï¸ Woman Surfing: Dark Skin Tone U+1F3C4, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🚣 Person Rowing Boat U+1F6A3
+🚣🻠Person Rowing Boat: Light Skin Tone U+1F6A3, U+1F3FB
+🚣🼠Person Rowing Boat: Medium-Light Skin U+1F6A3, U+1F3FC
+Tone
+🚣🽠Person Rowing Boat: Medium Skin Tone U+1F6A3, U+1F3FD
+🚣🾠Person Rowing Boat: Medium-Dark Skin Tone U+1F6A3, U+1F3FE
+🚣🿠Person Rowing Boat: Dark Skin Tone U+1F6A3, U+1F3FF
+🚣â€â™‚ï¸ Man Rowing Boat U+1F6A3, U+200D, U+2642, U+FE0F
+🚣ðŸ»â€â™‚ï¸ Man Rowing Boat: Light Skin Tone U+1F6A3, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🚣ðŸ¼â€â™‚ï¸ Man Rowing Boat: Medium-Light Skin U+1F6A3, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🚣ðŸ½â€â™‚ï¸ Man Rowing Boat: Medium Skin Tone U+1F6A3, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🚣ðŸ¾â€â™‚ï¸ Man Rowing Boat: Medium-Dark Skin Tone U+1F6A3, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🚣ðŸ¿â€â™‚ï¸ Man Rowing Boat: Dark Skin Tone U+1F6A3, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🚣â€â™€ï¸ Woman Rowing Boat U+1F6A3, U+200D, U+2640, U+FE0F
+🚣ðŸ»â€â™€ï¸ Woman Rowing Boat: Light Skin Tone U+1F6A3, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🚣ðŸ¼â€â™€ï¸ Woman Rowing Boat: Medium-Light Skin U+1F6A3, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🚣ðŸ½â€â™€ï¸ Woman Rowing Boat: Medium Skin Tone U+1F6A3, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🚣ðŸ¾â€â™€ï¸ Woman Rowing Boat: Medium-Dark Skin U+1F6A3, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🚣ðŸ¿â€â™€ï¸ Woman Rowing Boat: Dark Skin Tone U+1F6A3, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🊠Person Swimming U+1F3CA
+ðŸŠðŸ» Person Swimming: Light Skin Tone U+1F3CA, U+1F3FB
+ðŸŠðŸ¼ Person Swimming: Medium-Light Skin Tone U+1F3CA, U+1F3FC
+ðŸŠðŸ½ Person Swimming: Medium Skin Tone U+1F3CA, U+1F3FD
+ðŸŠðŸ¾ Person Swimming: Medium-Dark Skin Tone U+1F3CA, U+1F3FE
+ðŸŠðŸ¿ Person Swimming: Dark Skin Tone U+1F3CA, U+1F3FF
+ðŸŠâ€â™‚ï¸ Man Swimming U+1F3CA, U+200D, U+2642, U+FE0F
+ðŸŠðŸ»â€â™‚ï¸ Man Swimming: Light Skin Tone U+1F3CA, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸŠðŸ¼â€â™‚ï¸ Man Swimming: Medium-Light Skin Tone U+1F3CA, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+ðŸŠðŸ½â€â™‚ï¸ Man Swimming: Medium Skin Tone U+1F3CA, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸŠðŸ¾â€â™‚ï¸ Man Swimming: Medium-Dark Skin Tone U+1F3CA, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+ðŸŠðŸ¿â€â™‚ï¸ Man Swimming: Dark Skin Tone U+1F3CA, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸŠâ€â™€ï¸ Woman Swimming U+1F3CA, U+200D, U+2640, U+FE0F
+ðŸŠðŸ»â€â™€ï¸ Woman Swimming: Light Skin Tone U+1F3CA, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸŠðŸ¼â€â™€ï¸ Woman Swimming: Medium-Light Skin Tone U+1F3CA, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+ðŸŠðŸ½â€â™€ï¸ Woman Swimming: Medium Skin Tone U+1F3CA, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+ðŸŠðŸ¾â€â™€ï¸ Woman Swimming: Medium-Dark Skin Tone U+1F3CA, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+ðŸŠðŸ¿â€â™€ï¸ Woman Swimming: Dark Skin Tone U+1F3CA, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+â›¹ï¸ Person Bouncing Ball U+26F9, U+FE0F
+⛹🻠Person Bouncing Ball: Light Skin Tone U+26F9, U+1F3FB
+⛹🼠Person Bouncing Ball: Medium-Light Skin U+26F9, U+1F3FC
+Tone
+⛹🽠Person Bouncing Ball: Medium Skin Tone U+26F9, U+1F3FD
+⛹🾠Person Bouncing Ball: Medium-Dark Skin U+26F9, U+1F3FE
+Tone
+⛹🿠Person Bouncing Ball: Dark Skin Tone U+26F9, U+1F3FF
+⛹ï¸â€â™‚ï¸ Man Bouncing Ball U+26F9, U+FE0F, U+200D, U+2642,
+ U+FE0F
+⛹ðŸ»â€â™‚ï¸ Man Bouncing Ball: Light Skin Tone U+26F9, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+⛹ðŸ¼â€â™‚ï¸ Man Bouncing Ball: Medium-Light Skin U+26F9, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+⛹ðŸ½â€â™‚ï¸ Man Bouncing Ball: Medium Skin Tone U+26F9, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+⛹ðŸ¾â€â™‚ï¸ Man Bouncing Ball: Medium-Dark Skin U+26F9, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+⛹ðŸ¿â€â™‚ï¸ Man Bouncing Ball: Dark Skin Tone U+26F9, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+⛹ï¸â€â™€ï¸ Woman Bouncing Ball U+26F9, U+FE0F, U+200D, U+2640,
+ U+FE0F
+⛹ðŸ»â€â™€ï¸ Woman Bouncing Ball: Light Skin Tone U+26F9, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+⛹ðŸ¼â€â™€ï¸ Woman Bouncing Ball: Medium-Light Skin U+26F9, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+⛹ðŸ½â€â™€ï¸ Woman Bouncing Ball: Medium Skin Tone U+26F9, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+⛹ðŸ¾â€â™€ï¸ Woman Bouncing Ball: Medium-Dark Skin U+26F9, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+⛹ðŸ¿â€â™€ï¸ Woman Bouncing Ball: Dark Skin Tone U+26F9, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+ðŸ‹ï¸ Person Lifting Weights U+1F3CB, U+FE0F
+ðŸ‹ðŸ» Person Lifting Weights: Light Skin Tone U+1F3CB, U+1F3FB
+ðŸ‹ðŸ¼ Person Lifting Weights: Medium-Light Skin U+1F3CB, U+1F3FC
+Tone
+ðŸ‹ðŸ½ Person Lifting Weights: Medium Skin Tone U+1F3CB, U+1F3FD
+ðŸ‹ðŸ¾ Person Lifting Weights: Medium-Dark Skin U+1F3CB, U+1F3FE
+Tone
+ðŸ‹ðŸ¿ Person Lifting Weights: Dark Skin Tone U+1F3CB, U+1F3FF
+ðŸ‹ï¸â€â™‚ï¸ Man Lifting Weights U+1F3CB, U+FE0F, U+200D, U+2642,
+ U+FE0F
+ðŸ‹ðŸ»â€â™‚ï¸ Man Lifting Weights: Light Skin Tone U+1F3CB, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+ðŸ‹ðŸ¼â€â™‚ï¸ Man Lifting Weights: Medium-Light Skin U+1F3CB, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+ðŸ‹ðŸ½â€â™‚ï¸ Man Lifting Weights: Medium Skin Tone U+1F3CB, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+ðŸ‹ðŸ¾â€â™‚ï¸ Man Lifting Weights: Medium-Dark Skin U+1F3CB, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+ðŸ‹ðŸ¿â€â™‚ï¸ Man Lifting Weights: Dark Skin Tone U+1F3CB, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+ðŸ‹ï¸â€â™€ï¸ Woman Lifting Weights U+1F3CB, U+FE0F, U+200D, U+2640,
+ U+FE0F
+ðŸ‹ðŸ»â€â™€ï¸ Woman Lifting Weights: Light Skin Tone U+1F3CB, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+ðŸ‹ðŸ¼â€â™€ï¸ Woman Lifting Weights: Medium-Light U+1F3CB, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+ðŸ‹ðŸ½â€â™€ï¸ Woman Lifting Weights: Medium Skin U+1F3CB, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+ðŸ‹ðŸ¾â€â™€ï¸ Woman Lifting Weights: Medium-Dark U+1F3CB, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+ðŸ‹ðŸ¿â€â™€ï¸ Woman Lifting Weights: Dark Skin Tone U+1F3CB, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🚴 Person Biking U+1F6B4
+🚴🻠Person Biking: Light Skin Tone U+1F6B4, U+1F3FB
+🚴🼠Person Biking: Medium-Light Skin Tone U+1F6B4, U+1F3FC
+🚴🽠Person Biking: Medium Skin Tone U+1F6B4, U+1F3FD
+🚴🾠Person Biking: Medium-Dark Skin Tone U+1F6B4, U+1F3FE
+🚴🿠Person Biking: Dark Skin Tone U+1F6B4, U+1F3FF
+🚴â€â™‚ï¸ Man Biking U+1F6B4, U+200D, U+2642, U+FE0F
+🚴ðŸ»â€â™‚ï¸ Man Biking: Light Skin Tone U+1F6B4, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🚴ðŸ¼â€â™‚ï¸ Man Biking: Medium-Light Skin Tone U+1F6B4, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🚴ðŸ½â€â™‚ï¸ Man Biking: Medium Skin Tone U+1F6B4, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🚴ðŸ¾â€â™‚ï¸ Man Biking: Medium-Dark Skin Tone U+1F6B4, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🚴ðŸ¿â€â™‚ï¸ Man Biking: Dark Skin Tone U+1F6B4, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🚴â€â™€ï¸ Woman Biking U+1F6B4, U+200D, U+2640, U+FE0F
+🚴ðŸ»â€â™€ï¸ Woman Biking: Light Skin Tone U+1F6B4, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🚴ðŸ¼â€â™€ï¸ Woman Biking: Medium-Light Skin Tone U+1F6B4, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🚴ðŸ½â€â™€ï¸ Woman Biking: Medium Skin Tone U+1F6B4, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🚴ðŸ¾â€â™€ï¸ Woman Biking: Medium-Dark Skin Tone U+1F6B4, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🚴ðŸ¿â€â™€ï¸ Woman Biking: Dark Skin Tone U+1F6B4, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🚵 Person Mountain Biking U+1F6B5
+🚵🻠Person Mountain Biking: Light Skin Tone U+1F6B5, U+1F3FB
+🚵🼠Person Mountain Biking: Medium-Light Skin U+1F6B5, U+1F3FC
+Tone
+🚵🽠Person Mountain Biking: Medium Skin Tone U+1F6B5, U+1F3FD
+🚵🾠Person Mountain Biking: Medium-Dark Skin U+1F6B5, U+1F3FE
+Tone
+🚵🿠Person Mountain Biking: Dark Skin Tone U+1F6B5, U+1F3FF
+🚵â€â™‚ï¸ Man Mountain Biking U+1F6B5, U+200D, U+2642, U+FE0F
+🚵ðŸ»â€â™‚ï¸ Man Mountain Biking: Light Skin Tone U+1F6B5, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🚵ðŸ¼â€â™‚ï¸ Man Mountain Biking: Medium-Light Skin U+1F6B5, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🚵ðŸ½â€â™‚ï¸ Man Mountain Biking: Medium Skin Tone U+1F6B5, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🚵ðŸ¾â€â™‚ï¸ Man Mountain Biking: Medium-Dark Skin U+1F6B5, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🚵ðŸ¿â€â™‚ï¸ Man Mountain Biking: Dark Skin Tone U+1F6B5, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🚵â€â™€ï¸ Woman Mountain Biking U+1F6B5, U+200D, U+2640, U+FE0F
+🚵ðŸ»â€â™€ï¸ Woman Mountain Biking: Light Skin Tone U+1F6B5, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🚵ðŸ¼â€â™€ï¸ Woman Mountain Biking: Medium-Light U+1F6B5, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+🚵ðŸ½â€â™€ï¸ Woman Mountain Biking: Medium Skin U+1F6B5, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+🚵ðŸ¾â€â™€ï¸ Woman Mountain Biking: Medium-Dark U+1F6B5, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+🚵ðŸ¿â€â™€ï¸ Woman Mountain Biking: Dark Skin Tone U+1F6B5, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤸 Person Cartwheeling U+1F938
+🤸🻠Person Cartwheeling: Light Skin Tone U+1F938, U+1F3FB
+🤸🼠Person Cartwheeling: Medium-Light Skin U+1F938, U+1F3FC
+Tone
+🤸🽠Person Cartwheeling: Medium Skin Tone U+1F938, U+1F3FD
+🤸🾠Person Cartwheeling: Medium-Dark Skin U+1F938, U+1F3FE
+Tone
+🤸🿠Person Cartwheeling: Dark Skin Tone U+1F938, U+1F3FF
+🤸â€â™‚ï¸ Man Cartwheeling U+1F938, U+200D, U+2642, U+FE0F
+🤸ðŸ»â€â™‚ï¸ Man Cartwheeling: Light Skin Tone U+1F938, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤸ðŸ¼â€â™‚ï¸ Man Cartwheeling: Medium-Light Skin U+1F938, U+1F3FC, U+200D, U+2642,
+Tone U+FE0F
+🤸ðŸ½â€â™‚ï¸ Man Cartwheeling: Medium Skin Tone U+1F938, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤸ðŸ¾â€â™‚ï¸ Man Cartwheeling: Medium-Dark Skin U+1F938, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🤸ðŸ¿â€â™‚ï¸ Man Cartwheeling: Dark Skin Tone U+1F938, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤸â€â™€ï¸ Woman Cartwheeling U+1F938, U+200D, U+2640, U+FE0F
+🤸ðŸ»â€â™€ï¸ Woman Cartwheeling: Light Skin Tone U+1F938, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🤸ðŸ¼â€â™€ï¸ Woman Cartwheeling: Medium-Light Skin U+1F938, U+1F3FC, U+200D, U+2640,
+Tone U+FE0F
+🤸ðŸ½â€â™€ï¸ Woman Cartwheeling: Medium Skin Tone U+1F938, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤸ðŸ¾â€â™€ï¸ Woman Cartwheeling: Medium-Dark Skin U+1F938, U+1F3FE, U+200D, U+2640,
+Tone U+FE0F
+🤸ðŸ¿â€â™€ï¸ Woman Cartwheeling: Dark Skin Tone U+1F938, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤼 People Wrestling U+1F93C
+🤼â€â™‚ï¸ Men Wrestling U+1F93C, U+200D, U+2642, U+FE0F
+🤼â€â™€ï¸ Women Wrestling U+1F93C, U+200D, U+2640, U+FE0F
+🤽 Person Playing Water Polo U+1F93D
+🤽🻠Person Playing Water Polo: Light Skin U+1F93D, U+1F3FB
+Tone
+🤽🼠Person Playing Water Polo: Medium-Light U+1F93D, U+1F3FC
+Skin Tone
+🤽🽠Person Playing Water Polo: Medium Skin U+1F93D, U+1F3FD
+Tone
+👯ðŸ»â€â™‚ï¸ Men With Bunny Ears Partying, Type-1-2 U+1F46F, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤽🾠Person Playing Water Polo: Medium-Dark U+1F93D, U+1F3FE
+Skin Tone
+🤽🿠Person Playing Water Polo: Dark Skin Tone U+1F93D, U+1F3FF
+👯ðŸ¼â€â™‚ï¸ Men With Bunny Ears Partying, Type-3 U+1F46F, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🤽â€â™‚ï¸ Man Playing Water Polo U+1F93D, U+200D, U+2642, U+FE0F
+🤽ðŸ»â€â™‚ï¸ Man Playing Water Polo: Light Skin U+1F93D, U+1F3FB, U+200D, U+2642,
+Tone U+FE0F
+👯ðŸ½â€â™‚ï¸ Men With Bunny Ears Partying, Type-4 U+1F46F, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤽ðŸ¼â€â™‚ï¸ Man Playing Water Polo: Medium-Light U+1F93D, U+1F3FC, U+200D, U+2642,
+Skin Tone U+FE0F
+👯ðŸ¾â€â™‚ï¸ Men With Bunny Ears Partying, Type-5 U+1F46F, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🤽ðŸ½â€â™‚ï¸ Man Playing Water Polo: Medium Skin U+1F93D, U+1F3FD, U+200D, U+2642,
+Tone U+FE0F
+🤽ðŸ¾â€â™‚ï¸ Man Playing Water Polo: Medium-Dark U+1F93D, U+1F3FE, U+200D, U+2642,
+Skin Tone U+FE0F
+🤽ðŸ¿â€â™‚ï¸ Man Playing Water Polo: Dark Skin Tone U+1F93D, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+👯ðŸ¿â€â™‚ï¸ Men With Bunny Ears Partying, Type-6 U+1F46F, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤽â€â™€ï¸ Woman Playing Water Polo U+1F93D, U+200D, U+2640, U+FE0F
+🤽ðŸ»â€â™€ï¸ Woman Playing Water Polo: Light Skin U+1F93D, U+1F3FB, U+200D, U+2640,
+Tone U+FE0F
+🤽ðŸ¼â€â™€ï¸ Woman Playing Water Polo: Medium-Light U+1F93D, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+🤽ðŸ½â€â™€ï¸ Woman Playing Water Polo: Medium Skin U+1F93D, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+👯ðŸ»â€â™€ï¸ Women With Bunny Ears Partying, U+1F46F, U+1F3FB, U+200D, U+2640,
+Type-1-2 U+FE0F
+🤽ðŸ¾â€â™€ï¸ Woman Playing Water Polo: Medium-Dark U+1F93D, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+👯ðŸ¼â€â™€ï¸ Women With Bunny Ears Partying, Type-3 U+1F46F, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🤽ðŸ¿â€â™€ï¸ Woman Playing Water Polo: Dark Skin U+1F93D, U+1F3FF, U+200D, U+2640,
+Tone U+FE0F
+🤾 Person Playing Handball U+1F93E
+👯ðŸ½â€â™€ï¸ Women With Bunny Ears Partying, Type-4 U+1F46F, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤾🻠Person Playing Handball: Light Skin Tone U+1F93E, U+1F3FB
+🤾🼠Person Playing Handball: Medium-Light U+1F93E, U+1F3FC
+Skin Tone
+🤾🽠Person Playing Handball: Medium Skin Tone U+1F93E, U+1F3FD
+👯ðŸ¾â€â™€ï¸ Women With Bunny Ears Partying, Type-5 U+1F46F, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🤾🾠Person Playing Handball: Medium-Dark Skin U+1F93E, U+1F3FE
+Tone
+👯ðŸ¿â€â™€ï¸ Women With Bunny Ears Partying, Type-6 U+1F46F, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤾🿠Person Playing Handball: Dark Skin Tone U+1F93E, U+1F3FF
+🤾â€â™‚ï¸ Man Playing Handball U+1F93E, U+200D, U+2642, U+FE0F
+🤾ðŸ»â€â™‚ï¸ Man Playing Handball: Light Skin Tone U+1F93E, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤾ðŸ¼â€â™‚ï¸ Man Playing Handball: Medium-Light U+1F93E, U+1F3FC, U+200D, U+2642,
+Skin Tone U+FE0F
+🤾ðŸ½â€â™‚ï¸ Man Playing Handball: Medium Skin Tone U+1F93E, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤾ðŸ¾â€â™‚ï¸ Man Playing Handball: Medium-Dark Skin U+1F93E, U+1F3FE, U+200D, U+2642,
+Tone U+FE0F
+🤾ðŸ¿â€â™‚ï¸ Man Playing Handball: Dark Skin Tone U+1F93E, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤾â€â™€ï¸ Woman Playing Handball U+1F93E, U+200D, U+2640, U+FE0F
+🤾ðŸ»â€â™€ï¸ Woman Playing Handball: Light Skin U+1F93E, U+1F3FB, U+200D, U+2640,
+Tone U+FE0F
+🤾ðŸ¼â€â™€ï¸ Woman Playing Handball: Medium-Light U+1F93E, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+🤾ðŸ½â€â™€ï¸ Woman Playing Handball: Medium Skin U+1F93E, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+🤾ðŸ¾â€â™€ï¸ Woman Playing Handball: Medium-Dark U+1F93E, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+🤾ðŸ¿â€â™€ï¸ Woman Playing Handball: Dark Skin Tone U+1F93E, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🤹 Person Juggling U+1F939
+🤹🻠Person Juggling: Light Skin Tone U+1F939, U+1F3FB
+🤹🼠Person Juggling: Medium-Light Skin Tone U+1F939, U+1F3FC
+🤹🽠Person Juggling: Medium Skin Tone U+1F939, U+1F3FD
+🤹🾠Person Juggling: Medium-Dark Skin Tone U+1F939, U+1F3FE
+🤹🿠Person Juggling: Dark Skin Tone U+1F939, U+1F3FF
+🤹â€â™‚ï¸ Man Juggling U+1F939, U+200D, U+2642, U+FE0F
+🤹ðŸ»â€â™‚ï¸ Man Juggling: Light Skin Tone U+1F939, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🤹ðŸ¼â€â™‚ï¸ Man Juggling: Medium-Light Skin Tone U+1F939, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🤹ðŸ½â€â™‚ï¸ Man Juggling: Medium Skin Tone U+1F939, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🤹ðŸ¾â€â™‚ï¸ Man Juggling: Medium-Dark Skin Tone U+1F939, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🤹ðŸ¿â€â™‚ï¸ Man Juggling: Dark Skin Tone U+1F939, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🤹â€â™€ï¸ Woman Juggling U+1F939, U+200D, U+2640, U+FE0F
+🤹ðŸ»â€â™€ï¸ Woman Juggling: Light Skin Tone U+1F939, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🤹ðŸ¼â€â™€ï¸ Woman Juggling: Medium-Light Skin Tone U+1F939, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🤹ðŸ½â€â™€ï¸ Woman Juggling: Medium Skin Tone U+1F939, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🤹ðŸ¾â€â™€ï¸ Woman Juggling: Medium-Dark Skin Tone U+1F939, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🤹ðŸ¿â€â™€ï¸ Woman Juggling: Dark Skin Tone U+1F939, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🧘 Person in Lotus Position U+1F9D8
+🧘🻠Person in Lotus Position: Light Skin Tone U+1F9D8, U+1F3FB
+🧘🼠Person in Lotus Position: Medium-Light U+1F9D8, U+1F3FC
+Skin Tone
+🧘🽠Person in Lotus Position: Medium Skin U+1F9D8, U+1F3FD
+Tone
+🧘🾠Person in Lotus Position: Medium-Dark U+1F9D8, U+1F3FE
+Skin Tone
+🧘🿠Person in Lotus Position: Dark Skin Tone U+1F9D8, U+1F3FF
+🧘â€â™‚ï¸ Man in Lotus Position U+1F9D8, U+200D, U+2642, U+FE0F
+🧘ðŸ»â€â™‚ï¸ Man in Lotus Position: Light Skin Tone U+1F9D8, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🧘ðŸ¼â€â™‚ï¸ Man in Lotus Position: Medium-Light U+1F9D8, U+1F3FC, U+200D, U+2642,
+Skin Tone U+FE0F
+🧘ðŸ½â€â™‚ï¸ Man in Lotus Position: Medium Skin U+1F9D8, U+1F3FD, U+200D, U+2642,
+Tone U+FE0F
+🧘ðŸ¾â€â™‚ï¸ Man in Lotus Position: Medium-Dark U+1F9D8, U+1F3FE, U+200D, U+2642,
+Skin Tone U+FE0F
+🧘ðŸ¿â€â™‚ï¸ Man in Lotus Position: Dark Skin Tone U+1F9D8, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🧘â€â™€ï¸ Woman in Lotus Position U+1F9D8, U+200D, U+2640, U+FE0F
+🧘ðŸ»â€â™€ï¸ Woman in Lotus Position: Light Skin U+1F9D8, U+1F3FB, U+200D, U+2640,
+Tone U+FE0F
+🧘ðŸ¼â€â™€ï¸ Woman in Lotus Position: Medium-Light U+1F9D8, U+1F3FC, U+200D, U+2640,
+Skin Tone U+FE0F
+🧘ðŸ½â€â™€ï¸ Woman in Lotus Position: Medium Skin U+1F9D8, U+1F3FD, U+200D, U+2640,
+Tone U+FE0F
+🧘ðŸ¾â€â™€ï¸ Woman in Lotus Position: Medium-Dark U+1F9D8, U+1F3FE, U+200D, U+2640,
+Skin Tone U+FE0F
+🧘ðŸ¿â€â™€ï¸ Woman in Lotus Position: Dark Skin U+1F9D8, U+1F3FF, U+200D, U+2640,
+Tone U+FE0F
+🛀 Person Taking Bath U+1F6C0
+🛀🻠Person Taking Bath: Light Skin Tone U+1F6C0, U+1F3FB
+🛀🼠Person Taking Bath: Medium-Light Skin U+1F6C0, U+1F3FC
+Tone
+🛀🽠Person Taking Bath: Medium Skin Tone U+1F6C0, U+1F3FD
+🛀🾠Person Taking Bath: Medium-Dark Skin Tone U+1F6C0, U+1F3FE
+🛀🿠Person Taking Bath: Dark Skin Tone U+1F6C0, U+1F3FF
+🛌 Person in Bed U+1F6CC
+🛌🻠Person in Bed: Light Skin Tone U+1F6CC, U+1F3FB
+🛌🼠Person in Bed: Medium-Light Skin Tone U+1F6CC, U+1F3FC
+🛌🽠Person in Bed: Medium Skin Tone U+1F6CC, U+1F3FD
+🛌🾠Person in Bed: Medium-Dark Skin Tone U+1F6CC, U+1F3FE
+🛌🿠Person in Bed: Dark Skin Tone U+1F6CC, U+1F3FF
+🧑â€ðŸ¤â€ðŸ§‘ People Holding Hands U+1F9D1, U+200D, U+1F91D, U+200D,
+ U+1F9D1
+🧑ðŸ»â€ðŸ¤â€ðŸ§‘🻠People Holding Hands: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F91D,
+Tone U+200D, U+1F9D1, U+1F3FB
+🧑ðŸ»â€ðŸ¤â€ðŸ§‘🼠People Holding Hands: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F9D1, U+1F3FC
+🧑ðŸ»â€ðŸ¤â€ðŸ§‘🽠People Holding Hands: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium Skin Tone U+200D, U+1F9D1, U+1F3FD
+🧑ðŸ»â€ðŸ¤â€ðŸ§‘🾠People Holding Hands: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F9D1, U+1F3FE
+🧑ðŸ»â€ðŸ¤â€ðŸ§‘🿠People Holding Hands: Light Skin U+1F9D1, U+1F3FB, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F9D1, U+1F3FF
+🧑ðŸ¼â€ðŸ¤â€ðŸ§‘🻠People Holding Hands: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F9D1, U+1F3FB
+🧑ðŸ¼â€ðŸ¤â€ðŸ§‘🼠People Holding Hands: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F91D,
+Skin Tone U+200D, U+1F9D1, U+1F3FC
+🧑ðŸ¼â€ðŸ¤â€ðŸ§‘🽠People Holding Hands: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F9D1, U+1F3FD
+🧑ðŸ¼â€ðŸ¤â€ðŸ§‘🾠People Holding Hands: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Medium-Dark Skin Tone U+200D, U+1F9D1, U+1F3FE
+🧑ðŸ¼â€ðŸ¤â€ðŸ§‘🿠People Holding Hands: Medium-Light U+1F9D1, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F9D1, U+1F3FF
+🧑ðŸ½â€ðŸ¤â€ðŸ§‘🻠People Holding Hands: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F91D,
+Tone, Light Skin Tone U+200D, U+1F9D1, U+1F3FB
+🧑ðŸ½â€ðŸ¤â€ðŸ§‘🼠People Holding Hands: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F9D1, U+1F3FC
+🧑ðŸ½â€ðŸ¤â€ðŸ§‘🽠People Holding Hands: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F91D,
+Tone U+200D, U+1F9D1, U+1F3FD
+🧑ðŸ½â€ðŸ¤â€ðŸ§‘🾠People Holding Hands: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F9D1, U+1F3FE
+🧑ðŸ½â€ðŸ¤â€ðŸ§‘🿠People Holding Hands: Medium Skin U+1F9D1, U+1F3FD, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F9D1, U+1F3FF
+🧑ðŸ¾â€ðŸ¤â€ðŸ§‘🻠People Holding Hands: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F9D1, U+1F3FB
+🧑ðŸ¾â€ðŸ¤â€ðŸ§‘🼠People Holding Hands: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Medium-Light Skin Tone U+200D, U+1F9D1, U+1F3FC
+🧑ðŸ¾â€ðŸ¤â€ðŸ§‘🽠People Holding Hands: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F9D1, U+1F3FD
+🧑ðŸ¾â€ðŸ¤â€ðŸ§‘🾠People Holding Hands: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F91D,
+Skin Tone U+200D, U+1F9D1, U+1F3FE
+🧑ðŸ¾â€ðŸ¤â€ðŸ§‘🿠People Holding Hands: Medium-Dark U+1F9D1, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F9D1, U+1F3FF
+🧑ðŸ¿â€ðŸ¤â€ðŸ§‘🻠People Holding Hands: Dark Skin U+1F9D1, U+1F3FF, U+200D, U+1F91D,
+Tone, Light Skin Tone U+200D, U+1F9D1, U+1F3FB
+🧑ðŸ¿â€ðŸ¤â€ðŸ§‘🼠People Holding Hands: Dark Skin U+1F9D1, U+1F3FF, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F9D1, U+1F3FC
+🧑ðŸ¿â€ðŸ¤â€ðŸ§‘🽠People Holding Hands: Dark Skin U+1F9D1, U+1F3FF, U+200D, U+1F91D,
+Tone, Medium Skin Tone U+200D, U+1F9D1, U+1F3FD
+🧑ðŸ¿â€ðŸ¤â€ðŸ§‘🾠People Holding Hands: Dark Skin U+1F9D1, U+1F3FF, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F9D1, U+1F3FE
+🧑ðŸ¿â€ðŸ¤â€ðŸ§‘🿠People Holding Hands: Dark Skin Tone U+1F9D1, U+1F3FF, U+200D, U+1F91D,
+ U+200D, U+1F9D1, U+1F3FF
+👭 Women Holding Hands U+1F46D
+👭🻠Women Holding Hands: Light Skin Tone U+1F46D, U+1F3FB
+👩ðŸ»â€ðŸ¤â€ðŸ‘©ðŸ¼ Women Holding Hands: Light Skin U+1F469, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F469, U+1F3FC
+👩ðŸ»â€ðŸ¤â€ðŸ‘©ðŸ½ Women Holding Hands: Light Skin U+1F469, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium Skin Tone U+200D, U+1F469, U+1F3FD
+👩ðŸ»â€ðŸ¤â€ðŸ‘©ðŸ¾ Women Holding Hands: Light Skin U+1F469, U+1F3FB, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F469, U+1F3FE
+👩ðŸ»â€ðŸ¤â€ðŸ‘©ðŸ¿ Women Holding Hands: Light Skin U+1F469, U+1F3FB, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F469, U+1F3FF
+👩ðŸ¼â€ðŸ¤â€ðŸ‘©ðŸ» Women Holding Hands: Medium-Light U+1F469, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F469, U+1F3FB
+👭🼠Women Holding Hands: Medium-Light Skin U+1F46D, U+1F3FC
+Tone
+👩ðŸ¼â€ðŸ¤â€ðŸ‘©ðŸ½ Women Holding Hands: Medium-Light U+1F469, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F469, U+1F3FD
+👩ðŸ¼â€ðŸ¤â€ðŸ‘©ðŸ¾ Women Holding Hands: Medium-Light U+1F469, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Medium-Dark Skin Tone U+200D, U+1F469, U+1F3FE
+👩ðŸ¼â€ðŸ¤â€ðŸ‘©ðŸ¿ Women Holding Hands: Medium-Light U+1F469, U+1F3FC, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F469, U+1F3FF
+👩ðŸ½â€ðŸ¤â€ðŸ‘©ðŸ» Women Holding Hands: Medium Skin U+1F469, U+1F3FD, U+200D, U+1F91D,
+Tone, Light Skin Tone U+200D, U+1F469, U+1F3FB
+👩ðŸ½â€ðŸ¤â€ðŸ‘©ðŸ¼ Women Holding Hands: Medium Skin U+1F469, U+1F3FD, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F469, U+1F3FC
+👭🽠Women Holding Hands: Medium Skin Tone U+1F46D, U+1F3FD
+👩ðŸ½â€ðŸ¤â€ðŸ‘©ðŸ¾ Women Holding Hands: Medium Skin U+1F469, U+1F3FD, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F469, U+1F3FE
+👩ðŸ½â€ðŸ¤â€ðŸ‘©ðŸ¿ Women Holding Hands: Medium Skin U+1F469, U+1F3FD, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F469, U+1F3FF
+👩ðŸ¾â€ðŸ¤â€ðŸ‘©ðŸ» Women Holding Hands: Medium-Dark U+1F469, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F469, U+1F3FB
+👩ðŸ¾â€ðŸ¤â€ðŸ‘©ðŸ¼ Women Holding Hands: Medium-Dark U+1F469, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Medium-Light Skin Tone U+200D, U+1F469, U+1F3FC
+👩ðŸ¾â€ðŸ¤â€ðŸ‘©ðŸ½ Women Holding Hands: Medium-Dark U+1F469, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F469, U+1F3FD
+👭🾠Women Holding Hands: Medium-Dark Skin U+1F46D, U+1F3FE
+Tone
+👩ðŸ¾â€ðŸ¤â€ðŸ‘©ðŸ¿ Women Holding Hands: Medium-Dark U+1F469, U+1F3FE, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F469, U+1F3FF
+👩ðŸ¿â€ðŸ¤â€ðŸ‘©ðŸ» Women Holding Hands: Dark Skin Tone, U+1F469, U+1F3FF, U+200D, U+1F91D,
+Light Skin Tone U+200D, U+1F469, U+1F3FB
+👩ðŸ¿â€ðŸ¤â€ðŸ‘©ðŸ¼ Women Holding Hands: Dark Skin Tone, U+1F469, U+1F3FF, U+200D, U+1F91D,
+Medium-Light Skin Tone U+200D, U+1F469, U+1F3FC
+👩ðŸ¿â€ðŸ¤â€ðŸ‘©ðŸ½ Women Holding Hands: Dark Skin Tone, U+1F469, U+1F3FF, U+200D, U+1F91D,
+Medium Skin Tone U+200D, U+1F469, U+1F3FD
+👩ðŸ¿â€ðŸ¤â€ðŸ‘©ðŸ¾ Women Holding Hands: Dark Skin Tone, U+1F469, U+1F3FF, U+200D, U+1F91D,
+Medium-Dark Skin Tone U+200D, U+1F469, U+1F3FE
+👭🿠Women Holding Hands: Dark Skin Tone U+1F46D, U+1F3FF
+👫 Woman and Man Holding Hands U+1F46B
+👫🻠Woman and Man Holding Hands: Light Skin U+1F46B, U+1F3FB
+Tone
+👩ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¼ Woman and Man Holding Hands: Light U+1F469, U+1F3FB, U+200D, U+1F91D,
+Skin Tone, Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👩ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ½ Woman and Man Holding Hands: Light U+1F469, U+1F3FB, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👩ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¾ Woman and Man Holding Hands: Light U+1F469, U+1F3FB, U+200D, U+1F91D,
+Skin Tone, Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👩ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¿ Woman and Man Holding Hands: Light U+1F469, U+1F3FB, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👩ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ» Woman and Man Holding Hands: U+1F469, U+1F3FC, U+200D, U+1F91D,
+Medium-Light Skin Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👫🼠Woman and Man Holding Hands: Medium-Light U+1F46B, U+1F3FC
+Skin Tone
+👩ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ½ Woman and Man Holding Hands: U+1F469, U+1F3FC, U+200D, U+1F91D,
+Medium-Light Skin Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👩ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ¾ Woman and Man Holding Hands: U+1F469, U+1F3FC, U+200D, U+1F91D,
+Medium-Light Skin Tone, Medium-Dark Skin U+200D, U+1F468, U+1F3FE
+Tone
+👩ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ¿ Woman and Man Holding Hands: U+1F469, U+1F3FC, U+200D, U+1F91D,
+Medium-Light Skin Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👩ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ» Woman and Man Holding Hands: Medium U+1F469, U+1F3FD, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👩ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¼ Woman and Man Holding Hands: Medium U+1F469, U+1F3FD, U+200D, U+1F91D,
+Skin Tone, Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👫🽠Woman and Man Holding Hands: Medium Skin U+1F46B, U+1F3FD
+Tone
+👩ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¾ Woman and Man Holding Hands: Medium U+1F469, U+1F3FD, U+200D, U+1F91D,
+Skin Tone, Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👩ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¿ Woman and Man Holding Hands: Medium U+1F469, U+1F3FD, U+200D, U+1F91D,
+Skin Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👩ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ» Woman and Man Holding Hands: U+1F469, U+1F3FE, U+200D, U+1F91D,
+Medium-Dark Skin Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👩ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ¼ Woman and Man Holding Hands: U+1F469, U+1F3FE, U+200D, U+1F91D,
+Medium-Dark Skin Tone, Medium-Light Skin U+200D, U+1F468, U+1F3FC
+Tone
+👩ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ½ Woman and Man Holding Hands: U+1F469, U+1F3FE, U+200D, U+1F91D,
+Medium-Dark Skin Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👫🾠Woman and Man Holding Hands: Medium-Dark U+1F46B, U+1F3FE
+Skin Tone
+👩ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ¿ Woman and Man Holding Hands: U+1F469, U+1F3FE, U+200D, U+1F91D,
+Medium-Dark Skin Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👩ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ» Woman and Man Holding Hands: Dark U+1F469, U+1F3FF, U+200D, U+1F91D,
+Skin Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👩ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ¼ Woman and Man Holding Hands: Dark U+1F469, U+1F3FF, U+200D, U+1F91D,
+Skin Tone, Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👩ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ½ Woman and Man Holding Hands: Dark U+1F469, U+1F3FF, U+200D, U+1F91D,
+Skin Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👩ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ¾ Woman and Man Holding Hands: Dark U+1F469, U+1F3FF, U+200D, U+1F91D,
+Skin Tone, Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👫🿠Woman and Man Holding Hands: Dark Skin U+1F46B, U+1F3FF
+Tone
+👬 Men Holding Hands U+1F46C
+👬🻠Men Holding Hands: Light Skin Tone U+1F46C, U+1F3FB
+👨ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¼ Men Holding Hands: Light Skin Tone, U+1F468, U+1F3FB, U+200D, U+1F91D,
+Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👨ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ½ Men Holding Hands: Light Skin Tone, U+1F468, U+1F3FB, U+200D, U+1F91D,
+Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👨ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¾ Men Holding Hands: Light Skin Tone, U+1F468, U+1F3FB, U+200D, U+1F91D,
+Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👨ðŸ»â€ðŸ¤â€ðŸ‘¨ðŸ¿ Men Holding Hands: Light Skin Tone, U+1F468, U+1F3FB, U+200D, U+1F91D,
+Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👨ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ» Men Holding Hands: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F91D,
+Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👬🼠Men Holding Hands: Medium-Light Skin Tone U+1F46C, U+1F3FC
+👨ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ½ Men Holding Hands: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F91D,
+Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👨ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ¾ Men Holding Hands: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F91D,
+Tone, Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👨ðŸ¼â€ðŸ¤â€ðŸ‘¨ðŸ¿ Men Holding Hands: Medium-Light Skin U+1F468, U+1F3FC, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👨ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ» Men Holding Hands: Medium Skin Tone, U+1F468, U+1F3FD, U+200D, U+1F91D,
+Light Skin Tone U+200D, U+1F468, U+1F3FB
+👨ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¼ Men Holding Hands: Medium Skin Tone, U+1F468, U+1F3FD, U+200D, U+1F91D,
+Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👬🽠Men Holding Hands: Medium Skin Tone U+1F46C, U+1F3FD
+👨ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¾ Men Holding Hands: Medium Skin Tone, U+1F468, U+1F3FD, U+200D, U+1F91D,
+Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👨ðŸ½â€ðŸ¤â€ðŸ‘¨ðŸ¿ Men Holding Hands: Medium Skin Tone, U+1F468, U+1F3FD, U+200D, U+1F91D,
+Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👨ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ» Men Holding Hands: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F91D,
+Tone, Light Skin Tone U+200D, U+1F468, U+1F3FB
+👨ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ¼ Men Holding Hands: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F91D,
+Tone, Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👨ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ½ Men Holding Hands: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F91D,
+Tone, Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👬🾠Men Holding Hands: Medium-Dark Skin Tone U+1F46C, U+1F3FE
+👨ðŸ¾â€ðŸ¤â€ðŸ‘¨ðŸ¿ Men Holding Hands: Medium-Dark Skin U+1F468, U+1F3FE, U+200D, U+1F91D,
+Tone, Dark Skin Tone U+200D, U+1F468, U+1F3FF
+👨ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ» Men Holding Hands: Dark Skin Tone, U+1F468, U+1F3FF, U+200D, U+1F91D,
+Light Skin Tone U+200D, U+1F468, U+1F3FB
+👨ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ¼ Men Holding Hands: Dark Skin Tone, U+1F468, U+1F3FF, U+200D, U+1F91D,
+Medium-Light Skin Tone U+200D, U+1F468, U+1F3FC
+👨ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ½ Men Holding Hands: Dark Skin Tone, U+1F468, U+1F3FF, U+200D, U+1F91D,
+Medium Skin Tone U+200D, U+1F468, U+1F3FD
+👨ðŸ¿â€ðŸ¤â€ðŸ‘¨ðŸ¾ Men Holding Hands: Dark Skin Tone, U+1F468, U+1F3FF, U+200D, U+1F91D,
+Medium-Dark Skin Tone U+200D, U+1F468, U+1F3FE
+👬🿠Men Holding Hands: Dark Skin Tone U+1F46C, U+1F3FF
+💠Kiss U+1F48F
+👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ Kiss: Woman, Man U+1F469, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F48B, U+200D, U+1F468
+👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨ Kiss: Man, Man U+1F468, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F48B, U+200D, U+1F468
+👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘© Kiss: Woman, Woman U+1F469, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F48B, U+200D, U+1F469
+💑 Couple with Heart U+1F491
+👩â€â¤ï¸â€ðŸ‘¨ Couple with Heart: Woman, Man U+1F469, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F468
+👨â€â¤ï¸â€ðŸ‘¨ Couple with Heart: Man, Man U+1F468, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F468
+👩â€â¤ï¸â€ðŸ‘© Couple with Heart: Woman, Woman U+1F469, U+200D, U+2764, U+FE0F,
+ U+200D, U+1F469
+👪 Family U+1F46A
+👨â€ðŸ‘©â€ðŸ‘¦ Family: Man, Woman, Boy U+1F468, U+200D, U+1F469, U+200D,
+ U+1F466
+👨â€ðŸ‘©â€ðŸ‘§ Family: Man, Woman, Girl U+1F468, U+200D, U+1F469, U+200D,
+ U+1F467
+👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦ Family: Man, Woman, Girl, Boy U+1F468, U+200D, U+1F469, U+200D,
+ U+1F467, U+200D, U+1F466
+👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦ Family: Man, Woman, Boy, Boy U+1F468, U+200D, U+1F469, U+200D,
+ U+1F466, U+200D, U+1F466
+👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§ Family: Man, Woman, Girl, Girl U+1F468, U+200D, U+1F469, U+200D,
+ U+1F467, U+200D, U+1F467
+👨â€ðŸ‘¨â€ðŸ‘¦ Family: Man, Man, Boy U+1F468, U+200D, U+1F468, U+200D,
+ U+1F466
+👨â€ðŸ‘¨â€ðŸ‘§ Family: Man, Man, Girl U+1F468, U+200D, U+1F468, U+200D,
+ U+1F467
+👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦ Family: Man, Man, Girl, Boy U+1F468, U+200D, U+1F468, U+200D,
+ U+1F467, U+200D, U+1F466
+👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦ Family: Man, Man, Boy, Boy U+1F468, U+200D, U+1F468, U+200D,
+ U+1F466, U+200D, U+1F466
+👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§ Family: Man, Man, Girl, Girl U+1F468, U+200D, U+1F468, U+200D,
+ U+1F467, U+200D, U+1F467
+👩â€ðŸ‘©â€ðŸ‘¦ Family: Woman, Woman, Boy U+1F469, U+200D, U+1F469, U+200D,
+ U+1F466
+👩â€ðŸ‘©â€ðŸ‘§ Family: Woman, Woman, Girl U+1F469, U+200D, U+1F469, U+200D,
+ U+1F467
+👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦ Family: Woman, Woman, Girl, Boy U+1F469, U+200D, U+1F469, U+200D,
+ U+1F467, U+200D, U+1F466
+👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦ Family: Woman, Woman, Boy, Boy U+1F469, U+200D, U+1F469, U+200D,
+ U+1F466, U+200D, U+1F466
+👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§ Family: Woman, Woman, Girl, Girl U+1F469, U+200D, U+1F469, U+200D,
+ U+1F467, U+200D, U+1F467
+👨â€ðŸ‘¦ Family: Man, Boy U+1F468, U+200D, U+1F466
+👨â€ðŸ‘¦â€ðŸ‘¦ Family: Man, Boy, Boy U+1F468, U+200D, U+1F466, U+200D,
+ U+1F466
+👨â€ðŸ‘§ Family: Man, Girl U+1F468, U+200D, U+1F467
+👨â€ðŸ‘§â€ðŸ‘¦ Family: Man, Girl, Boy U+1F468, U+200D, U+1F467, U+200D,
+ U+1F466
+👨â€ðŸ‘§â€ðŸ‘§ Family: Man, Girl, Girl U+1F468, U+200D, U+1F467, U+200D,
+ U+1F467
+👩â€ðŸ‘¦ Family: Woman, Boy U+1F469, U+200D, U+1F466
+👩â€ðŸ‘¦â€ðŸ‘¦ Family: Woman, Boy, Boy U+1F469, U+200D, U+1F466, U+200D,
+ U+1F466
+👩â€ðŸ‘§ Family: Woman, Girl U+1F469, U+200D, U+1F467
+👩â€ðŸ‘§â€ðŸ‘¦ Family: Woman, Girl, Boy U+1F469, U+200D, U+1F467, U+200D,
+ U+1F466
+👩â€ðŸ‘§â€ðŸ‘§ Family: Woman, Girl, Girl U+1F469, U+200D, U+1F467, U+200D,
+ U+1F467
+ðŸ—£ï¸ Speaking Head U+1F5E3, U+FE0F
+👤 Bust in Silhouette U+1F464
+👥 Busts in Silhouette U+1F465
+🫂 People Hugging U+1FAC2
+👣 Footprints U+1F463
+🻠Light Skin Tone U+1F3FB
+🼠Medium-Light Skin Tone U+1F3FC
+🽠Medium Skin Tone U+1F3FD
+🾠Medium-Dark Skin Tone U+1F3FE
+🿠Dark Skin Tone U+1F3FF
+🦰 Red Hair U+1F9B0
+🦱 Curly Hair U+1F9B1
+🦳 White Hair U+1F9B3
+🦲 Bald U+1F9B2
+🵠Monkey Face U+1F435
+💠Monkey U+1F412
+🦠Gorilla U+1F98D
+🦧 Orangutan U+1F9A7
+🶠Dog Face U+1F436
+🕠Dog U+1F415
+🦮 Guide Dog U+1F9AE
+ðŸ•â€ðŸ¦º Service Dog U+1F415, U+200D, U+1F9BA
+🩠Poodle U+1F429
+🺠Wolf U+1F43A
+🦊 Fox U+1F98A
+🦠Raccoon U+1F99D
+🱠Cat Face U+1F431
+🈠Cat U+1F408
+ðŸˆâ€â¬› Black Cat U+1F408, U+200D, U+2B1B
+🦠Lion U+1F981
+🯠Tiger Face U+1F42F
+🅠Tiger U+1F405
+🆠Leopard U+1F406
+🴠Horse Face U+1F434
+🎠Horse U+1F40E
+🦄 Unicorn U+1F984
+🦓 Zebra U+1F993
+🦌 Deer U+1F98C
+🦬 Bison U+1F9AC
+🮠Cow Face U+1F42E
+🂠Ox U+1F402
+🃠Water Buffalo U+1F403
+🄠Cow U+1F404
+🷠Pig Face U+1F437
+🖠Pig U+1F416
+🗠Boar U+1F417
+🽠Pig Nose U+1F43D
+ðŸ Ram U+1F40F
+👠Ewe U+1F411
+ðŸ Goat U+1F410
+🪠Camel U+1F42A
+🫠Two-Hump Camel U+1F42B
+🦙 Llama U+1F999
+🦒 Giraffe U+1F992
+😠Elephant U+1F418
+🦣 Mammoth U+1F9A3
+🦠Rhinoceros U+1F98F
+🦛 Hippopotamus U+1F99B
+🭠Mouse Face U+1F42D
+ðŸ Mouse U+1F401
+🀠Rat U+1F400
+🹠Hamster U+1F439
+🰠Rabbit Face U+1F430
+🇠Rabbit U+1F407
+ðŸ¿ï¸ Chipmunk U+1F43F, U+FE0F
+🦫 Beaver U+1F9AB
+🦔 Hedgehog U+1F994
+🦇 Bat U+1F987
+🻠Bear U+1F43B
+ðŸ»â€â„ï¸ Polar Bear U+1F43B, U+200D, U+2744, U+FE0F
+🨠Koala U+1F428
+🼠Panda U+1F43C
+🦥 Sloth U+1F9A5
+🦦 Otter U+1F9A6
+🦨 Skunk U+1F9A8
+🦘 Kangaroo U+1F998
+🦡 Badger U+1F9A1
+🾠Paw Prints U+1F43E
+🦃 Turkey U+1F983
+🔠Chicken U+1F414
+📠Rooster U+1F413
+🣠Hatching Chick U+1F423
+🤠Baby Chick U+1F424
+🥠Front-Facing Baby Chick U+1F425
+🦠Bird U+1F426
+🧠Penguin U+1F427
+ðŸ•Šï¸ Dove U+1F54A, U+FE0F
+🦅 Eagle U+1F985
+🦆 Duck U+1F986
+🦢 Swan U+1F9A2
+🦉 Owl U+1F989
+🦤 Dodo U+1F9A4
+🪶 Feather U+1FAB6
+🦩 Flamingo U+1F9A9
+🦚 Peacock U+1F99A
+🦜 Parrot U+1F99C
+🸠Frog U+1F438
+🊠Crocodile U+1F40A
+🢠Turtle U+1F422
+🦎 Lizard U+1F98E
+ðŸ Snake U+1F40D
+🲠Dragon Face U+1F432
+🉠Dragon U+1F409
+🦕 Sauropod U+1F995
+🦖 T-Rex U+1F996
+🳠Spouting Whale U+1F433
+🋠Whale U+1F40B
+🬠Dolphin U+1F42C
+🦭 Seal U+1F9AD
+🟠Fish U+1F41F
+🠠Tropical Fish U+1F420
+🡠Blowfish U+1F421
+🦈 Shark U+1F988
+🙠Octopus U+1F419
+🚠Spiral Shell U+1F41A
+🌠Snail U+1F40C
+🦋 Butterfly U+1F98B
+🛠Bug U+1F41B
+🜠Ant U+1F41C
+ðŸ Honeybee U+1F41D
+🪲 Beetle U+1FAB2
+🞠Lady Beetle U+1F41E
+🦗 Cricket U+1F997
+🪳 Cockroach U+1FAB3
+ðŸ•·ï¸ Spider U+1F577, U+FE0F
+ðŸ•¸ï¸ Spider Web U+1F578, U+FE0F
+🦂 Scorpion U+1F982
+🦟 Mosquito U+1F99F
+🪰 Fly U+1FAB0
+🪱 Worm U+1FAB1
+🦠 Microbe U+1F9A0
+💠Bouquet U+1F490
+🌸 Cherry Blossom U+1F338
+💮 White Flower U+1F4AE
+ðŸµï¸ Rosette U+1F3F5, U+FE0F
+🌹 Rose U+1F339
+🥀 Wilted Flower U+1F940
+🌺 Hibiscus U+1F33A
+🌻 Sunflower U+1F33B
+🌼 Blossom U+1F33C
+🌷 Tulip U+1F337
+🌱 Seedling U+1F331
+🪴 Potted Plant U+1FAB4
+🌲 Evergreen Tree U+1F332
+🌳 Deciduous Tree U+1F333
+🌴 Palm Tree U+1F334
+🌵 Cactus U+1F335
+🌾 Sheaf of Rice U+1F33E
+🌿 Herb U+1F33F
+â˜˜ï¸ Shamrock U+2618, U+FE0F
+🀠Four Leaf Clover U+1F340
+ðŸ Maple Leaf U+1F341
+🂠Fallen Leaf U+1F342
+🃠Leaf Fluttering in Wind U+1F343
+🇠Grapes U+1F347
+🈠Melon U+1F348
+🉠Watermelon U+1F349
+🊠Tangerine U+1F34A
+🋠Lemon U+1F34B
+🌠Banana U+1F34C
+ðŸ Pineapple U+1F34D
+🥭 Mango U+1F96D
+🎠Red Apple U+1F34E
+ðŸ Green Apple U+1F34F
+ðŸ Pear U+1F350
+🤼🻠Wrestlers, Type-1-2 U+1F93C, U+1F3FB
+👠Peach U+1F351
+🤼🼠Wrestlers, Type-3 U+1F93C, U+1F3FC
+💠Cherries U+1F352
+🤼🽠Wrestlers, Type-4 U+1F93C, U+1F3FD
+📠Strawberry U+1F353
+🤼🾠Wrestlers, Type-5 U+1F93C, U+1F3FE
+🫠Blueberries U+1FAD0
+🤼🿠Wrestlers, Type-6 U+1F93C, U+1F3FF
+🥠Kiwi Fruit U+1F95D
+🅠Tomato U+1F345
+🤼ðŸ»â€â™‚ï¸ Men Wrestling, Type-1-2 U+1F93C, U+1F3FB, U+200D, U+2642,
+ U+FE0F
+🫒 Olive U+1FAD2
+🥥 Coconut U+1F965
+🥑 Avocado U+1F951
+🤼ðŸ¼â€â™‚ï¸ Men Wrestling, Type-3 U+1F93C, U+1F3FC, U+200D, U+2642,
+ U+FE0F
+🆠Eggplant U+1F346
+🥔 Potato U+1F954
+🤼ðŸ½â€â™‚ï¸ Men Wrestling, Type-4 U+1F93C, U+1F3FD, U+200D, U+2642,
+ U+FE0F
+🥕 Carrot U+1F955
+🤼ðŸ¾â€â™‚ï¸ Men Wrestling, Type-5 U+1F93C, U+1F3FE, U+200D, U+2642,
+ U+FE0F
+🌽 Ear of Corn U+1F33D
+ðŸŒ¶ï¸ Hot Pepper U+1F336, U+FE0F
+🫑 Bell Pepper U+1FAD1
+🤼ðŸ¿â€â™‚ï¸ Men Wrestling, Type-6 U+1F93C, U+1F3FF, U+200D, U+2642,
+ U+FE0F
+🥒 Cucumber U+1F952
+🥬 Leafy Green U+1F96C
+🥦 Broccoli U+1F966
+🤼ðŸ»â€â™€ï¸ Women Wrestling, Type-1-2 U+1F93C, U+1F3FB, U+200D, U+2640,
+ U+FE0F
+🧄 Garlic U+1F9C4
+🧅 Onion U+1F9C5
+🄠Mushroom U+1F344
+🤼ðŸ¼â€â™€ï¸ Women Wrestling, Type-3 U+1F93C, U+1F3FC, U+200D, U+2640,
+ U+FE0F
+🥜 Peanuts U+1F95C
+🌰 Chestnut U+1F330
+🤼ðŸ½â€â™€ï¸ Women Wrestling, Type-4 U+1F93C, U+1F3FD, U+200D, U+2640,
+ U+FE0F
+🞠Bread U+1F35E
+🥠Croissant U+1F950
+🤼ðŸ¾â€â™€ï¸ Women Wrestling, Type-5 U+1F93C, U+1F3FE, U+200D, U+2640,
+ U+FE0F
+🥖 Baguette Bread U+1F956
+🫓 Flatbread U+1FAD3
+🤼ðŸ¿â€â™€ï¸ Women Wrestling, Type-6 U+1F93C, U+1F3FF, U+200D, U+2640,
+ U+FE0F
+🥨 Pretzel U+1F968
+🥯 Bagel U+1F96F
+🥞 Pancakes U+1F95E
+🧇 Waffle U+1F9C7
+🧀 Cheese Wedge U+1F9C0
+🖠Meat on Bone U+1F356
+🗠Poultry Leg U+1F357
+🥩 Cut of Meat U+1F969
+🥓 Bacon U+1F953
+🔠Hamburger U+1F354
+🟠French Fries U+1F35F
+🕠Pizza U+1F355
+🌭 Hot Dog U+1F32D
+🥪 Sandwich U+1F96A
+🌮 Taco U+1F32E
+🌯 Burrito U+1F32F
+🫔 Tamale U+1FAD4
+🥙 Stuffed Flatbread U+1F959
+🧆 Falafel U+1F9C6
+🥚 Egg U+1F95A
+🳠Cooking U+1F373
+🥘 Shallow Pan of Food U+1F958
+🲠Pot of Food U+1F372
+🫕 Fondue U+1FAD5
+🥣 Bowl with Spoon U+1F963
+🥗 Green Salad U+1F957
+🿠Popcorn U+1F37F
+🧈 Butter U+1F9C8
+🧂 Salt U+1F9C2
+🥫 Canned Food U+1F96B
+🱠Bento Box U+1F371
+😠Rice Cracker U+1F358
+🙠Rice Ball U+1F359
+🚠Cooked Rice U+1F35A
+🛠Curry Rice U+1F35B
+🜠Steaming Bowl U+1F35C
+ðŸ Spaghetti U+1F35D
+🠠Roasted Sweet Potato U+1F360
+🢠Oden U+1F362
+🣠Sushi U+1F363
+🤠Fried Shrimp U+1F364
+🥠Fish Cake with Swirl U+1F365
+🥮 Moon Cake U+1F96E
+🡠Dango U+1F361
+🥟 Dumpling U+1F95F
+🥠 Fortune Cookie U+1F960
+🥡 Takeout Box U+1F961
+🦀 Crab U+1F980
+🦞 Lobster U+1F99E
+🦠Shrimp U+1F990
+🦑 Squid U+1F991
+🦪 Oyster U+1F9AA
+🦠Soft Ice Cream U+1F366
+🧠Shaved Ice U+1F367
+🨠Ice Cream U+1F368
+🩠Doughnut U+1F369
+🪠Cookie U+1F36A
+🎂 Birthday Cake U+1F382
+🰠Shortcake U+1F370
+🧠Cupcake U+1F9C1
+🥧 Pie U+1F967
+🫠Chocolate Bar U+1F36B
+🬠Candy U+1F36C
+🭠Lollipop U+1F36D
+🮠Custard U+1F36E
+🯠Honey Pot U+1F36F
+🼠Baby Bottle U+1F37C
+🥛 Glass of Milk U+1F95B
+☕ Hot Beverage U+2615
+🫖 Teapot U+1FAD6
+🵠Teacup Without Handle U+1F375
+🶠Sake U+1F376
+🾠Bottle with Popping Cork U+1F37E
+🷠Wine Glass U+1F377
+🸠Cocktail Glass U+1F378
+🹠Tropical Drink U+1F379
+🺠Beer Mug U+1F37A
+🻠Clinking Beer Mugs U+1F37B
+🥂 Clinking Glasses U+1F942
+🥃 Tumbler Glass U+1F943
+🥤 Cup with Straw U+1F964
+🧋 Bubble Tea U+1F9CB
+🧃 Beverage Box U+1F9C3
+🧉 Mate U+1F9C9
+🧊 Ice U+1F9CA
+🥢 Chopsticks U+1F962
+ðŸ½ï¸ Fork and Knife with Plate U+1F37D, U+FE0F
+🴠Fork and Knife U+1F374
+🥄 Spoon U+1F944
+🔪 Kitchen Knife U+1F52A
+🺠Amphora U+1F3FA
+🌠Globe Showing Europe-Africa U+1F30D
+🌎 Globe Showing Americas U+1F30E
+🌠Globe Showing Asia-Australia U+1F30F
+🌠Globe with Meridians U+1F310
+ðŸ—ºï¸ World Map U+1F5FA, U+FE0F
+🗾 Map of Japan U+1F5FE
+🧭 Compass U+1F9ED
+ðŸ”ï¸ Snow-Capped Mountain U+1F3D4, U+FE0F
+â›°ï¸ Mountain U+26F0, U+FE0F
+🌋 Volcano U+1F30B
+🗻 Mount Fuji U+1F5FB
+ðŸ•ï¸ Camping U+1F3D5, U+FE0F
+ðŸ–ï¸ Beach with Umbrella U+1F3D6, U+FE0F
+ðŸœï¸ Desert U+1F3DC, U+FE0F
+ðŸï¸ Desert Island U+1F3DD, U+FE0F
+ðŸžï¸ National Park U+1F3DE, U+FE0F
+ðŸŸï¸ Stadium U+1F3DF, U+FE0F
+ðŸ›ï¸ Classical Building U+1F3DB, U+FE0F
+ðŸ—ï¸ Building Construction U+1F3D7, U+FE0F
+🧱 Brick U+1F9F1
+🪨 Rock U+1FAA8
+🪵 Wood U+1FAB5
+🛖 Hut U+1F6D6
+ðŸ˜ï¸ Houses U+1F3D8, U+FE0F
+ðŸšï¸ Derelict House U+1F3DA, U+FE0F
+🠠House U+1F3E0
+🡠House with Garden U+1F3E1
+🢠Office Building U+1F3E2
+🣠Japanese Post Office U+1F3E3
+🤠Post Office U+1F3E4
+🥠Hospital U+1F3E5
+🦠Bank U+1F3E6
+🨠Hotel U+1F3E8
+🩠Love Hotel U+1F3E9
+🪠Convenience Store U+1F3EA
+🫠School U+1F3EB
+🬠Department Store U+1F3EC
+🭠Factory U+1F3ED
+🯠Japanese Castle U+1F3EF
+🰠Castle U+1F3F0
+💒 Wedding U+1F492
+🗼 Tokyo Tower U+1F5FC
+🗽 Statue of Liberty U+1F5FD
+⛪ Church U+26EA
+🕌 Mosque U+1F54C
+🛕 Hindu Temple U+1F6D5
+🕠Synagogue U+1F54D
+â›©ï¸ Shinto Shrine U+26E9, U+FE0F
+🕋 Kaaba U+1F54B
+⛲ Fountain U+26F2
+⛺ Tent U+26FA
+🌠Foggy U+1F301
+🌃 Night with Stars U+1F303
+ðŸ™ï¸ Cityscape U+1F3D9, U+FE0F
+🌄 Sunrise Over Mountains U+1F304
+🌅 Sunrise U+1F305
+🌆 Cityscape at Dusk U+1F306
+🌇 Sunset U+1F307
+🌉 Bridge at Night U+1F309
+â™¨ï¸ Hot Springs U+2668, U+FE0F
+🎠 Carousel Horse U+1F3A0
+🎡 Ferris Wheel U+1F3A1
+🎢 Roller Coaster U+1F3A2
+💈 Barber Pole U+1F488
+🎪 Circus Tent U+1F3AA
+🚂 Locomotive U+1F682
+🚃 Railway Car U+1F683
+🚄 High-Speed Train U+1F684
+🚅 Bullet Train U+1F685
+🚆 Train U+1F686
+🚇 Metro U+1F687
+🚈 Light Rail U+1F688
+🚉 Station U+1F689
+🚊 Tram U+1F68A
+🚠Monorail U+1F69D
+🚞 Mountain Railway U+1F69E
+🚋 Tram Car U+1F68B
+🚌 Bus U+1F68C
+🚠Oncoming Bus U+1F68D
+🚎 Trolleybus U+1F68E
+🚠Minibus U+1F690
+🚑 Ambulance U+1F691
+🚒 Fire Engine U+1F692
+🚓 Police Car U+1F693
+🚔 Oncoming Police Car U+1F694
+🚕 Taxi U+1F695
+🚖 Oncoming Taxi U+1F696
+🚗 Automobile U+1F697
+🚘 Oncoming Automobile U+1F698
+🚙 Sport Utility Vehicle U+1F699
+🛻 Pickup Truck U+1F6FB
+🚚 Delivery Truck U+1F69A
+🚛 Articulated Lorry U+1F69B
+🚜 Tractor U+1F69C
+ðŸŽï¸ Racing Car U+1F3CE, U+FE0F
+ðŸï¸ Motorcycle U+1F3CD, U+FE0F
+🛵 Motor Scooter U+1F6F5
+🦽 Manual Wheelchair U+1F9BD
+🦼 Motorized Wheelchair U+1F9BC
+🛺 Auto Rickshaw U+1F6FA
+🚲 Bicycle U+1F6B2
+🛴 Kick Scooter U+1F6F4
+🛹 Skateboard U+1F6F9
+🛼 Roller Skate U+1F6FC
+🚠Bus Stop U+1F68F
+ðŸ›£ï¸ Motorway U+1F6E3, U+FE0F
+ðŸ›¤ï¸ Railway Track U+1F6E4, U+FE0F
+ðŸ›¢ï¸ Oil Drum U+1F6E2, U+FE0F
+⛽ Fuel Pump U+26FD
+🚨 Police Car Light U+1F6A8
+🚥 Horizontal Traffic Light U+1F6A5
+🚦 Vertical Traffic Light U+1F6A6
+🛑 Stop Sign U+1F6D1
+🚧 Construction U+1F6A7
+âš“ Anchor U+2693
+⛵ Sailboat U+26F5
+🛶 Canoe U+1F6F6
+🚤 Speedboat U+1F6A4
+ðŸ›³ï¸ Passenger Ship U+1F6F3, U+FE0F
+â›´ï¸ Ferry U+26F4, U+FE0F
+ðŸ›¥ï¸ Motor Boat U+1F6E5, U+FE0F
+🚢 Ship U+1F6A2
+âœˆï¸ Airplane U+2708, U+FE0F
+ðŸ›©ï¸ Small Airplane U+1F6E9, U+FE0F
+🛫 Airplane Departure U+1F6EB
+🛬 Airplane Arrival U+1F6EC
+🪂 Parachute U+1FA82
+💺 Seat U+1F4BA
+🚠Helicopter U+1F681
+🚟 Suspension Railway U+1F69F
+🚠 Mountain Cableway U+1F6A0
+🚡 Aerial Tramway U+1F6A1
+ðŸ›°ï¸ Satellite U+1F6F0, U+FE0F
+🚀 Rocket U+1F680
+🛸 Flying Saucer U+1F6F8
+ðŸ›Žï¸ Bellhop Bell U+1F6CE, U+FE0F
+🧳 Luggage U+1F9F3
+⌛ Hourglass Done U+231B
+â³ Hourglass Not Done U+23F3
+⌚ Watch U+231A
+â° Alarm Clock U+23F0
+â±ï¸ Stopwatch U+23F1, U+FE0F
+â²ï¸ Timer Clock U+23F2, U+FE0F
+ðŸ•°ï¸ Mantelpiece Clock U+1F570, U+FE0F
+🕛 Twelve O’Clock U+1F55B
+🕧 Twelve-Thirty U+1F567
+🕠One O’Clock U+1F550
+🕜 One-Thirty U+1F55C
+🕑 Two O’Clock U+1F551
+🕠Two-Thirty U+1F55D
+🕒 Three O’Clock U+1F552
+🕞 Three-Thirty U+1F55E
+🕓 Four O’Clock U+1F553
+🕟 Four-Thirty U+1F55F
+🕔 Five O’Clock U+1F554
+🕠 Five-Thirty U+1F560
+🕕 Six O’Clock U+1F555
+🕡 Six-Thirty U+1F561
+🕖 Seven O’Clock U+1F556
+🕢 Seven-Thirty U+1F562
+🕗 Eight O’Clock U+1F557
+🕣 Eight-Thirty U+1F563
+🕘 Nine O’Clock U+1F558
+🕤 Nine-Thirty U+1F564
+🕙 Ten O’Clock U+1F559
+🕥 Ten-Thirty U+1F565
+🕚 Eleven O’Clock U+1F55A
+🕦 Eleven-Thirty U+1F566
+🌑 New Moon U+1F311
+🌒 Waxing Crescent Moon U+1F312
+🌓 First Quarter Moon U+1F313
+🌔 Waxing Gibbous Moon U+1F314
+🌕 Full Moon U+1F315
+🌖 Waning Gibbous Moon U+1F316
+🌗 Last Quarter Moon U+1F317
+🌘 Waning Crescent Moon U+1F318
+🌙 Crescent Moon U+1F319
+🌚 New Moon Face U+1F31A
+🌛 First Quarter Moon Face U+1F31B
+🌜 Last Quarter Moon Face U+1F31C
+ðŸŒ¡ï¸ Thermometer U+1F321, U+FE0F
+â˜€ï¸ Sun U+2600, U+FE0F
+🌠Full Moon Face U+1F31D
+🌞 Sun with Face U+1F31E
+🪠Ringed Planet U+1FA90
+â­ Star U+2B50
+🌟 Glowing Star U+1F31F
+🌠 Shooting Star U+1F320
+🌌 Milky Way U+1F30C
+â˜ï¸ Cloud U+2601, U+FE0F
+â›… Sun Behind Cloud U+26C5
+â›ˆï¸ Cloud with Lightning and Rain U+26C8, U+FE0F
+ðŸŒ¤ï¸ Sun Behind Small Cloud U+1F324, U+FE0F
+ðŸŒ¥ï¸ Sun Behind Large Cloud U+1F325, U+FE0F
+ðŸŒ¦ï¸ Sun Behind Rain Cloud U+1F326, U+FE0F
+ðŸŒ§ï¸ Cloud with Rain U+1F327, U+FE0F
+ðŸŒ¨ï¸ Cloud with Snow U+1F328, U+FE0F
+ðŸŒ©ï¸ Cloud with Lightning U+1F329, U+FE0F
+ðŸŒªï¸ Tornado U+1F32A, U+FE0F
+ðŸŒ«ï¸ Fog U+1F32B, U+FE0F
+ðŸŒ¬ï¸ Wind Face U+1F32C, U+FE0F
+🌀 Cyclone U+1F300
+🌈 Rainbow U+1F308
+🌂 Closed Umbrella U+1F302
+â˜‚ï¸ Umbrella U+2602, U+FE0F
+☔ Umbrella with Rain Drops U+2614
+â›±ï¸ Umbrella on Ground U+26F1, U+FE0F
+âš¡ High Voltage U+26A1
+â„ï¸ Snowflake U+2744, U+FE0F
+â˜ƒï¸ Snowman U+2603, U+FE0F
+⛄ Snowman Without Snow U+26C4
+â˜„ï¸ Comet U+2604, U+FE0F
+🔥 Fire U+1F525
+💧 Droplet U+1F4A7
+🌊 Water Wave U+1F30A
+🎃 Jack-O-Lantern U+1F383
+🎄 Christmas Tree U+1F384
+🎆 Fireworks U+1F386
+🎇 Sparkler U+1F387
+🧨 Firecracker U+1F9E8
+✨ Sparkles U+2728
+🎈 Balloon U+1F388
+🎉 Party Popper U+1F389
+🎊 Confetti Ball U+1F38A
+🎋 Tanabata Tree U+1F38B
+🎠Pine Decoration U+1F38D
+🎎 Japanese Dolls U+1F38E
+🎠Carp Streamer U+1F38F
+🎠Wind Chime U+1F390
+🎑 Moon Viewing Ceremony U+1F391
+🧧 Red Envelope U+1F9E7
+🎀 Ribbon U+1F380
+🎠Wrapped Gift U+1F381
+ðŸŽ—ï¸ Reminder Ribbon U+1F397, U+FE0F
+ðŸŽŸï¸ Admission Tickets U+1F39F, U+FE0F
+🎫 Ticket U+1F3AB
+ðŸŽ–ï¸ Military Medal U+1F396, U+FE0F
+🆠Trophy U+1F3C6
+🅠Sports Medal U+1F3C5
+🥇 1st Place Medal U+1F947
+🥈 2nd Place Medal U+1F948
+🥉 3rd Place Medal U+1F949
+âš½ Soccer Ball U+26BD
+âš¾ Baseball U+26BE
+🥎 Softball U+1F94E
+🀠Basketball U+1F3C0
+ðŸ Volleyball U+1F3D0
+🈠American Football U+1F3C8
+🉠Rugby Football U+1F3C9
+🎾 Tennis U+1F3BE
+🥠Flying Disc U+1F94F
+🎳 Bowling U+1F3B3
+ðŸ Cricket Game U+1F3CF
+👠Field Hockey U+1F3D1
+💠Ice Hockey U+1F3D2
+🥠Lacrosse U+1F94D
+📠Ping Pong U+1F3D3
+🸠Badminton U+1F3F8
+🥊 Boxing Glove U+1F94A
+🥋 Martial Arts Uniform U+1F94B
+🥅 Goal Net U+1F945
+⛳ Flag in Hole U+26F3
+â›¸ï¸ Ice Skate U+26F8, U+FE0F
+🎣 Fishing Pole U+1F3A3
+🤿 Diving Mask U+1F93F
+🎽 Running Shirt U+1F3BD
+🎿 Skis U+1F3BF
+🛷 Sled U+1F6F7
+🥌 Curling Stone U+1F94C
+🎯 Direct Hit U+1F3AF
+🪀 Yo-Yo U+1FA80
+🪠Kite U+1FA81
+🎱 Pool 8 Ball U+1F3B1
+🔮 Crystal Ball U+1F52E
+🪄 Magic Wand U+1FA84
+🧿 Nazar Amulet U+1F9FF
+🎮 Video Game U+1F3AE
+ðŸ•¹ï¸ Joystick U+1F579, U+FE0F
+🎰 Slot Machine U+1F3B0
+🎲 Game Die U+1F3B2
+🧩 Puzzle Piece U+1F9E9
+🧸 Teddy Bear U+1F9F8
+🪅 Piñata U+1FA85
+🪆 Nesting Dolls U+1FA86
+â™ ï¸ Spade Suit U+2660, U+FE0F
+â™¥ï¸ Heart Suit U+2665, U+FE0F
+â™¦ï¸ Diamond Suit U+2666, U+FE0F
+â™£ï¸ Club Suit U+2663, U+FE0F
+â™Ÿï¸ Chess Pawn U+265F, U+FE0F
+🃠Joker U+1F0CF
+🀄 Mahjong Red Dragon U+1F004
+🎴 Flower Playing Cards U+1F3B4
+🎭 Performing Arts U+1F3AD
+ðŸ–¼ï¸ Framed Picture U+1F5BC, U+FE0F
+🎨 Artist Palette U+1F3A8
+🧵 Thread U+1F9F5
+🪡 Sewing Needle U+1FAA1
+🧶 Yarn U+1F9F6
+🪢 Knot U+1FAA2
+👓 Glasses U+1F453
+ðŸ•¶ï¸ Sunglasses U+1F576, U+FE0F
+🥽 Goggles U+1F97D
+🥼 Lab Coat U+1F97C
+🦺 Safety Vest U+1F9BA
+👔 Necktie U+1F454
+👕 T-Shirt U+1F455
+👖 Jeans U+1F456
+🧣 Scarf U+1F9E3
+🧤 Gloves U+1F9E4
+🧥 Coat U+1F9E5
+🧦 Socks U+1F9E6
+👗 Dress U+1F457
+👘 Kimono U+1F458
+🥻 Sari U+1F97B
+🩱 One-Piece Swimsuit U+1FA71
+🩲 Briefs U+1FA72
+🩳 Shorts U+1FA73
+👙 Bikini U+1F459
+👚 Woman’s Clothes U+1F45A
+👛 Purse U+1F45B
+👜 Handbag U+1F45C
+👠Clutch Bag U+1F45D
+ðŸ›ï¸ Shopping Bags U+1F6CD, U+FE0F
+🎒 Backpack U+1F392
+🩴 Thong Sandal U+1FA74
+👞 Man’s Shoe U+1F45E
+👟 Running Shoe U+1F45F
+🥾 Hiking Boot U+1F97E
+🥿 Flat Shoe U+1F97F
+👠 High-Heeled Shoe U+1F460
+👡 Woman’s Sandal U+1F461
+🩰 Ballet Shoes U+1FA70
+👢 Woman’s Boot U+1F462
+👑 Crown U+1F451
+👒 Woman’s Hat U+1F452
+🎩 Top Hat U+1F3A9
+🎓 Graduation Cap U+1F393
+🧢 Billed Cap U+1F9E2
+🪖 Military Helmet U+1FA96
+â›‘ï¸ Rescue Worker’s Helmet U+26D1, U+FE0F
+📿 Prayer Beads U+1F4FF
+💄 Lipstick U+1F484
+💠Ring U+1F48D
+💎 Gem Stone U+1F48E
+🔇 Muted Speaker U+1F507
+🔈 Speaker Low Volume U+1F508
+🔉 Speaker Medium Volume U+1F509
+🔊 Speaker High Volume U+1F50A
+📢 Loudspeaker U+1F4E2
+📣 Megaphone U+1F4E3
+📯 Postal Horn U+1F4EF
+🔔 Bell U+1F514
+🔕 Bell with Slash U+1F515
+🎼 Musical Score U+1F3BC
+🎵 Musical Note U+1F3B5
+🎶 Musical Notes U+1F3B6
+ðŸŽ™ï¸ Studio Microphone U+1F399, U+FE0F
+ðŸŽšï¸ Level Slider U+1F39A, U+FE0F
+ðŸŽ›ï¸ Control Knobs U+1F39B, U+FE0F
+🎤 Microphone U+1F3A4
+🎧 Headphone U+1F3A7
+📻 Radio U+1F4FB
+🎷 Saxophone U+1F3B7
+🪗 Accordion U+1FA97
+🎸 Guitar U+1F3B8
+🎹 Musical Keyboard U+1F3B9
+🎺 Trumpet U+1F3BA
+🎻 Violin U+1F3BB
+🪕 Banjo U+1FA95
+🥠Drum U+1F941
+🪘 Long Drum U+1FA98
+📱 Mobile Phone U+1F4F1
+📲 Mobile Phone with Arrow U+1F4F2
+â˜Žï¸ Telephone U+260E, U+FE0F
+📞 Telephone Receiver U+1F4DE
+📟 Pager U+1F4DF
+📠 Fax Machine U+1F4E0
+🔋 Battery U+1F50B
+🔌 Electric Plug U+1F50C
+💻 Laptop U+1F4BB
+ðŸ–¥ï¸ Desktop Computer U+1F5A5, U+FE0F
+ðŸ–¨ï¸ Printer U+1F5A8, U+FE0F
+âŒ¨ï¸ Keyboard U+2328, U+FE0F
+ðŸ–±ï¸ Computer Mouse U+1F5B1, U+FE0F
+ðŸ–²ï¸ Trackball U+1F5B2, U+FE0F
+💽 Computer Disk U+1F4BD
+💾 Floppy Disk U+1F4BE
+💿 Optical Disk U+1F4BF
+📀 DVD U+1F4C0
+🧮 Abacus U+1F9EE
+🎥 Movie Camera U+1F3A5
+ðŸŽžï¸ Film Frames U+1F39E, U+FE0F
+ðŸ“½ï¸ Film Projector U+1F4FD, U+FE0F
+🎬 Clapper Board U+1F3AC
+📺 Television U+1F4FA
+📷 Camera U+1F4F7
+📸 Camera with Flash U+1F4F8
+📹 Video Camera U+1F4F9
+📼 Videocassette U+1F4FC
+🔠Magnifying Glass Tilted Left U+1F50D
+🔎 Magnifying Glass Tilted Right U+1F50E
+ðŸ•¯ï¸ Candle U+1F56F, U+FE0F
+💡 Light Bulb U+1F4A1
+🔦 Flashlight U+1F526
+🮠Red Paper Lantern U+1F3EE
+🪔 Diya Lamp U+1FA94
+📔 Notebook with Decorative Cover U+1F4D4
+📕 Closed Book U+1F4D5
+📖 Open Book U+1F4D6
+📗 Green Book U+1F4D7
+📘 Blue Book U+1F4D8
+📙 Orange Book U+1F4D9
+📚 Books U+1F4DA
+📓 Notebook U+1F4D3
+📒 Ledger U+1F4D2
+📃 Page with Curl U+1F4C3
+📜 Scroll U+1F4DC
+📄 Page Facing Up U+1F4C4
+📰 Newspaper U+1F4F0
+ðŸ—žï¸ Rolled-Up Newspaper U+1F5DE, U+FE0F
+📑 Bookmark Tabs U+1F4D1
+🔖 Bookmark U+1F516
+ðŸ·ï¸ Label U+1F3F7, U+FE0F
+💰 Money Bag U+1F4B0
+🪙 Coin U+1FA99
+💴 Yen Banknote U+1F4B4
+💵 Dollar Banknote U+1F4B5
+💶 Euro Banknote U+1F4B6
+💷 Pound Banknote U+1F4B7
+💸 Money with Wings U+1F4B8
+💳 Credit Card U+1F4B3
+🧾 Receipt U+1F9FE
+💹 Chart Increasing with Yen U+1F4B9
+âœ‰ï¸ Envelope U+2709, U+FE0F
+📧 E-Mail U+1F4E7
+📨 Incoming Envelope U+1F4E8
+📩 Envelope with Arrow U+1F4E9
+📤 Outbox Tray U+1F4E4
+📥 Inbox Tray U+1F4E5
+📦 Package U+1F4E6
+📫 Closed Mailbox with Raised Flag U+1F4EB
+📪 Closed Mailbox with Lowered Flag U+1F4EA
+📬 Open Mailbox with Raised Flag U+1F4EC
+📭 Open Mailbox with Lowered Flag U+1F4ED
+📮 Postbox U+1F4EE
+ðŸ—³ï¸ Ballot Box with Ballot U+1F5F3, U+FE0F
+âœï¸ Pencil U+270F, U+FE0F
+âœ’ï¸ Black Nib U+2712, U+FE0F
+ðŸ–‹ï¸ Fountain Pen U+1F58B, U+FE0F
+ðŸ–Šï¸ Pen U+1F58A, U+FE0F
+ðŸ–Œï¸ Paintbrush U+1F58C, U+FE0F
+ðŸ–ï¸ Crayon U+1F58D, U+FE0F
+📠Memo U+1F4DD
+💼 Briefcase U+1F4BC
+📠File Folder U+1F4C1
+📂 Open File Folder U+1F4C2
+ðŸ—‚ï¸ Card Index Dividers U+1F5C2, U+FE0F
+📅 Calendar U+1F4C5
+📆 Tear-Off Calendar U+1F4C6
+ðŸ—’ï¸ Spiral Notepad U+1F5D2, U+FE0F
+ðŸ—“ï¸ Spiral Calendar U+1F5D3, U+FE0F
+📇 Card Index U+1F4C7
+📈 Chart Increasing U+1F4C8
+📉 Chart Decreasing U+1F4C9
+📊 Bar Chart U+1F4CA
+📋 Clipboard U+1F4CB
+📌 Pushpin U+1F4CC
+📠Round Pushpin U+1F4CD
+📎 Paperclip U+1F4CE
+ðŸ–‡ï¸ Linked Paperclips U+1F587, U+FE0F
+📠Straight Ruler U+1F4CF
+📠Triangular Ruler U+1F4D0
+âœ‚ï¸ Scissors U+2702, U+FE0F
+ðŸ—ƒï¸ Card File Box U+1F5C3, U+FE0F
+ðŸ—„ï¸ File Cabinet U+1F5C4, U+FE0F
+ðŸ—‘ï¸ Wastebasket U+1F5D1, U+FE0F
+🔒 Locked U+1F512
+🔓 Unlocked U+1F513
+🔠Locked with Pen U+1F50F
+🔠Locked with Key U+1F510
+🔑 Key U+1F511
+ðŸ—ï¸ Old Key U+1F5DD, U+FE0F
+🔨 Hammer U+1F528
+🪓 Axe U+1FA93
+â›ï¸ Pick U+26CF, U+FE0F
+âš’ï¸ Hammer and Pick U+2692, U+FE0F
+ðŸ› ï¸ Hammer and Wrench U+1F6E0, U+FE0F
+ðŸ—¡ï¸ Dagger U+1F5E1, U+FE0F
+âš”ï¸ Crossed Swords U+2694, U+FE0F
+🔫 Pistol U+1F52B
+🪃 Boomerang U+1FA83
+🹠Bow and Arrow U+1F3F9
+ðŸ›¡ï¸ Shield U+1F6E1, U+FE0F
+🪚 Carpentry Saw U+1FA9A
+🔧 Wrench U+1F527
+🪛 Screwdriver U+1FA9B
+🔩 Nut and Bolt U+1F529
+âš™ï¸ Gear U+2699, U+FE0F
+ðŸ—œï¸ Clamp U+1F5DC, U+FE0F
+âš–ï¸ Balance Scale U+2696, U+FE0F
+🦯 White Cane U+1F9AF
+🔗 Link U+1F517
+â›“ï¸ Chains U+26D3, U+FE0F
+🪠Hook U+1FA9D
+🧰 Toolbox U+1F9F0
+🧲 Magnet U+1F9F2
+🪜 Ladder U+1FA9C
+âš—ï¸ Alembic U+2697, U+FE0F
+🧪 Test Tube U+1F9EA
+🧫 Petri Dish U+1F9EB
+🧬 DNA U+1F9EC
+🔬 Microscope U+1F52C
+🔭 Telescope U+1F52D
+📡 Satellite Antenna U+1F4E1
+💉 Syringe U+1F489
+🩸 Drop of Blood U+1FA78
+💊 Pill U+1F48A
+🩹 Adhesive Bandage U+1FA79
+🩺 Stethoscope U+1FA7A
+🚪 Door U+1F6AA
+🛗 Elevator U+1F6D7
+🪞 Mirror U+1FA9E
+🪟 Window U+1FA9F
+ðŸ›ï¸ Bed U+1F6CF, U+FE0F
+ðŸ›‹ï¸ Couch and Lamp U+1F6CB, U+FE0F
+🪑 Chair U+1FA91
+🚽 Toilet U+1F6BD
+🪠 Plunger U+1FAA0
+🚿 Shower U+1F6BF
+🛠Bathtub U+1F6C1
+🪤 Mouse Trap U+1FAA4
+🪒 Razor U+1FA92
+🧴 Lotion Bottle U+1F9F4
+🧷 Safety Pin U+1F9F7
+🧹 Broom U+1F9F9
+🧺 Basket U+1F9FA
+🧻 Roll of Paper U+1F9FB
+🪣 Bucket U+1FAA3
+🧼 Soap U+1F9FC
+🪥 Toothbrush U+1FAA5
+🧽 Sponge U+1F9FD
+🧯 Fire Extinguisher U+1F9EF
+🛒 Shopping Cart U+1F6D2
+🚬 Cigarette U+1F6AC
+âš°ï¸ Coffin U+26B0, U+FE0F
+🪦 Headstone U+1FAA6
+âš±ï¸ Funeral Urn U+26B1, U+FE0F
+🗿 Moai U+1F5FF
+🪧 Placard U+1FAA7
+🧠ATM Sign U+1F3E7
+🚮 Litter in Bin Sign U+1F6AE
+🚰 Potable Water U+1F6B0
+♿ Wheelchair Symbol U+267F
+🚹 Men’s Room U+1F6B9
+🚺 Women’s Room U+1F6BA
+🚻 Restroom U+1F6BB
+🚼 Baby Symbol U+1F6BC
+🚾 Water Closet U+1F6BE
+🛂 Passport Control U+1F6C2
+🛃 Customs U+1F6C3
+🛄 Baggage Claim U+1F6C4
+🛅 Left Luggage U+1F6C5
+âš ï¸ Warning U+26A0, U+FE0F
+🚸 Children Crossing U+1F6B8
+â›” No Entry U+26D4
+🚫 Prohibited U+1F6AB
+🚳 No Bicycles U+1F6B3
+🚭 No Smoking U+1F6AD
+🚯 No Littering U+1F6AF
+🚱 Non-Potable Water U+1F6B1
+🚷 No Pedestrians U+1F6B7
+📵 No Mobile Phones U+1F4F5
+🔞 No One Under Eighteen U+1F51E
+â˜¢ï¸ Radioactive U+2622, U+FE0F
+â˜£ï¸ Biohazard U+2623, U+FE0F
+â¬†ï¸ Up Arrow U+2B06, U+FE0F
+â†—ï¸ Up-Right Arrow U+2197, U+FE0F
+âž¡ï¸ Right Arrow U+27A1, U+FE0F
+â†˜ï¸ Down-Right Arrow U+2198, U+FE0F
+â¬‡ï¸ Down Arrow U+2B07, U+FE0F
+â†™ï¸ Down-Left Arrow U+2199, U+FE0F
+â¬…ï¸ Left Arrow U+2B05, U+FE0F
+â†–ï¸ Up-Left Arrow U+2196, U+FE0F
+â†•ï¸ Up-Down Arrow U+2195, U+FE0F
+â†”ï¸ Left-Right Arrow U+2194, U+FE0F
+â†©ï¸ Right Arrow Curving Left U+21A9, U+FE0F
+â†ªï¸ Left Arrow Curving Right U+21AA, U+FE0F
+â¤´ï¸ Right Arrow Curving Up U+2934, U+FE0F
+â¤µï¸ Right Arrow Curving Down U+2935, U+FE0F
+🔃 Clockwise Vertical Arrows U+1F503
+🔄 Counterclockwise Arrows Button U+1F504
+🔙 Back Arrow U+1F519
+🔚 End Arrow U+1F51A
+🔛 On! Arrow U+1F51B
+🔜 Soon Arrow U+1F51C
+🔠Top Arrow U+1F51D
+🛠Place of Worship U+1F6D0
+âš›ï¸ Atom Symbol U+269B, U+FE0F
+ðŸ•‰ï¸ Om U+1F549, U+FE0F
+âœ¡ï¸ Star of David U+2721, U+FE0F
+â˜¸ï¸ Wheel of Dharma U+2638, U+FE0F
+â˜¯ï¸ Yin Yang U+262F, U+FE0F
+âœï¸ Latin Cross U+271D, U+FE0F
+â˜¦ï¸ Orthodox Cross U+2626, U+FE0F
+â˜ªï¸ Star and Crescent U+262A, U+FE0F
+â˜®ï¸ Peace Symbol U+262E, U+FE0F
+🕎 Menorah U+1F54E
+🔯 Dotted Six-Pointed Star U+1F52F
+♈ Aries U+2648
+♉ Taurus U+2649
+♊ Gemini U+264A
+♋ Cancer U+264B
+♌ Leo U+264C
+â™ Virgo U+264D
+♎ Libra U+264E
+â™ Scorpio U+264F
+â™ Sagittarius U+2650
+♑ Capricorn U+2651
+â™’ Aquarius U+2652
+♓ Pisces U+2653
+⛎ Ophiuchus U+26CE
+🔀 Shuffle Tracks Button U+1F500
+🔠Repeat Button U+1F501
+🔂 Repeat Single Button U+1F502
+â–¶ï¸ Play Button U+25B6, U+FE0F
+â© Fast-Forward Button U+23E9
+â­ï¸ Next Track Button U+23ED, U+FE0F
+â¯ï¸ Play or Pause Button U+23EF, U+FE0F
+â—€ï¸ Reverse Button U+25C0, U+FE0F
+⪠Fast Reverse Button U+23EA
+â®ï¸ Last Track Button U+23EE, U+FE0F
+🔼 Upwards Button U+1F53C
+â« Fast Up Button U+23EB
+🔽 Downwards Button U+1F53D
+⬠Fast Down Button U+23EC
+â¸ï¸ Pause Button U+23F8, U+FE0F
+â¹ï¸ Stop Button U+23F9, U+FE0F
+âºï¸ Record Button U+23FA, U+FE0F
+âï¸ Eject Button U+23CF, U+FE0F
+🎦 Cinema U+1F3A6
+🔅 Dim Button U+1F505
+🔆 Bright Button U+1F506
+📶 Antenna Bars U+1F4F6
+📳 Vibration Mode U+1F4F3
+📴 Mobile Phone Off U+1F4F4
+â™€ï¸ Female Sign U+2640, U+FE0F
+â™‚ï¸ Male Sign U+2642, U+FE0F
+âš§ï¸ Transgender Symbol U+26A7, U+FE0F
+âœ–ï¸ Multiply U+2716, U+FE0F
+âž• Plus U+2795
+âž– Minus U+2796
+âž— Divide U+2797
+â™¾ï¸ Infinity U+267E, U+FE0F
+â€¼ï¸ Double Exclamation Mark U+203C, U+FE0F
+â‰ï¸ Exclamation Question Mark U+2049, U+FE0F
+â“ Question Mark U+2753
+â” White Question Mark U+2754
+â• White Exclamation Mark U+2755
+â— Exclamation Mark U+2757
+ã€°ï¸ Wavy Dash U+3030, U+FE0F
+💱 Currency Exchange U+1F4B1
+💲 Heavy Dollar Sign U+1F4B2
+âš•ï¸ Medical Symbol U+2695, U+FE0F
+â™»ï¸ Recycling Symbol U+267B, U+FE0F
+âšœï¸ Fleur-de-lis U+269C, U+FE0F
+🔱 Trident Emblem U+1F531
+📛 Name Badge U+1F4DB
+🔰 Japanese Symbol for Beginner U+1F530
+â­• Hollow Red Circle U+2B55
+✅ Check Mark Button U+2705
+â˜‘ï¸ Check Box with Check U+2611, U+FE0F
+âœ”ï¸ Check Mark U+2714, U+FE0F
+⌠Cross Mark U+274C
+⎠Cross Mark Button U+274E
+âž° Curly Loop U+27B0
+âž¿ Double Curly Loop U+27BF
+ã€½ï¸ Part Alternation Mark U+303D, U+FE0F
+âœ³ï¸ Eight-Spoked Asterisk U+2733, U+FE0F
+âœ´ï¸ Eight-Pointed Star U+2734, U+FE0F
+â‡ï¸ Sparkle U+2747, U+FE0F
+Â©ï¸ Copyright U+A9, U+FE0F
+Â®ï¸ Registered U+AE, U+FE0F
+â„¢ï¸ Trade Mark U+2122, U+FE0F
+#ï¸âƒ£ Keycap Number Sign U+23, U+FE0F, U+20E3
+*ï¸âƒ£ Keycap Asterisk U+2A, U+FE0F, U+20E3
+0ï¸âƒ£ Keycap Digit Zero U+30, U+FE0F, U+20E3
+1ï¸âƒ£ Keycap Digit One U+31, U+FE0F, U+20E3
+2ï¸âƒ£ Keycap Digit Two U+32, U+FE0F, U+20E3
+3ï¸âƒ£ Keycap Digit Three U+33, U+FE0F, U+20E3
+4ï¸âƒ£ Keycap Digit Four U+34, U+FE0F, U+20E3
+5ï¸âƒ£ Keycap Digit Five U+35, U+FE0F, U+20E3
+6ï¸âƒ£ Keycap Digit Six U+36, U+FE0F, U+20E3
+7ï¸âƒ£ Keycap Digit Seven U+37, U+FE0F, U+20E3
+8ï¸âƒ£ Keycap Digit Eight U+38, U+FE0F, U+20E3
+9ï¸âƒ£ Keycap Digit Nine U+39, U+FE0F, U+20E3
+🔟 Keycap: 10 U+1F51F
+🔠 Input Latin Uppercase U+1F520
+🔡 Input Latin Lowercase U+1F521
+🔢 Input Numbers U+1F522
+🔣 Input Symbols U+1F523
+🔤 Input Latin Letters U+1F524
+ðŸ…°ï¸ A Button (Blood Type) U+1F170, U+FE0F
+🆎 AB Button (Blood Type) U+1F18E
+ðŸ…±ï¸ B Button (Blood Type) U+1F171, U+FE0F
+🆑 CL Button U+1F191
+🆒 Cool Button U+1F192
+🆓 Free Button U+1F193
+â„¹ï¸ Information U+2139, U+FE0F
+🆔 ID Button U+1F194
+â“‚ï¸ Circled M U+24C2, U+FE0F
+🆕 New Button U+1F195
+🆖 NG Button U+1F196
+ðŸ…¾ï¸ O Button (Blood Type) U+1F17E, U+FE0F
+🆗 OK Button U+1F197
+ðŸ…¿ï¸ P Button U+1F17F, U+FE0F
+🆘 SOS Button U+1F198
+🆙 Up! Button U+1F199
+🆚 Vs Button U+1F19A
+🈠Japanese “Here†Button U+1F201
+ðŸˆ‚ï¸ Japanese “Service Charge†Button U+1F202, U+FE0F
+ðŸˆ·ï¸ Japanese “Monthly Amount†Button U+1F237, U+FE0F
+🈶 Japanese “Not Free of Charge†Button U+1F236
+🈯 Japanese “Reserved†Button U+1F22F
+🉠Japanese “Bargain†Button U+1F250
+🈹 Japanese “Discount†Button U+1F239
+🈚 Japanese “Free of Charge†Button U+1F21A
+🈲 Japanese “Prohibited†Button U+1F232
+🉑 Japanese “Acceptable†Button U+1F251
+🈸 Japanese “Application†Button U+1F238
+🈴 Japanese “Passing Grade†Button U+1F234
+🈳 Japanese “Vacancy†Button U+1F233
+ãŠ—ï¸ Japanese “Congratulations†Button U+3297, U+FE0F
+ãŠ™ï¸ Japanese “Secret†Button U+3299, U+FE0F
+🈺 Japanese “Open for Business†Button U+1F23A
+🈵 Japanese “No Vacancy†Button U+1F235
+🔴 Red Circle U+1F534
+🟠 Orange Circle U+1F7E0
+🟡 Yellow Circle U+1F7E1
+🟢 Green Circle U+1F7E2
+🔵 Blue Circle U+1F535
+🟣 Purple Circle U+1F7E3
+🟤 Brown Circle U+1F7E4
+âš« Black Circle U+26AB
+⚪ White Circle U+26AA
+🟥 Red Square U+1F7E5
+🟧 Orange Square U+1F7E7
+🟨 Yellow Square U+1F7E8
+🟩 Green Square U+1F7E9
+🟦 Blue Square U+1F7E6
+🟪 Purple Square U+1F7EA
+🟫 Brown Square U+1F7EB
+⬛ Black Large Square U+2B1B
+⬜ White Large Square U+2B1C
+â—¼ï¸ Black Medium Square U+25FC, U+FE0F
+â—»ï¸ White Medium Square U+25FB, U+FE0F
+â—¾ Black Medium-Small Square U+25FE
+â—½ White Medium-Small Square U+25FD
+â–ªï¸ Black Small Square U+25AA, U+FE0F
+â–«ï¸ White Small Square U+25AB, U+FE0F
+🔶 Large Orange Diamond U+1F536
+🔷 Large Blue Diamond U+1F537
+🔸 Small Orange Diamond U+1F538
+🔹 Small Blue Diamond U+1F539
+🔺 Red Triangle Pointed Up U+1F53A
+🔻 Red Triangle Pointed Down U+1F53B
+💠 Diamond with a Dot U+1F4A0
+🔘 Radio Button U+1F518
+🔳 White Square Button U+1F533
+🔲 Black Square Button U+1F532
+ðŸ Chequered Flag U+1F3C1
+🚩 Triangular Flag U+1F6A9
+🎌 Crossed Flags U+1F38C
+🴠Black Flag U+1F3F4
+ðŸ³ï¸ White Flag U+1F3F3, U+FE0F
+ðŸ³ï¸â€ðŸŒˆ Rainbow Flag U+1F3F3, U+FE0F, U+200D, U+1F308
+ðŸ³ï¸â€âš§ï¸ Transgender Flag U+1F3F3, U+FE0F, U+200D, U+26A7,
+ U+FE0F
+ðŸ´â€â˜ ï¸ Pirate Flag U+1F3F4, U+200D, U+2620, U+FE0F
+🇦🇨 Flag: Ascension Island U+1F1E6, U+1F1E8
+🇦🇩 Flag: Andorra U+1F1E6, U+1F1E9
+🇦🇪 Flag: United Arab Emirates U+1F1E6, U+1F1EA
+🇦🇫 Flag: Afghanistan U+1F1E6, U+1F1EB
+🇦🇬 Flag: Antigua & Barbuda U+1F1E6, U+1F1EC
+🇦🇮 Flag: Anguilla U+1F1E6, U+1F1EE
+🇦🇱 Flag: Albania U+1F1E6, U+1F1F1
+🇦🇲 Flag: Armenia U+1F1E6, U+1F1F2
+🇦🇴 Flag: Angola U+1F1E6, U+1F1F4
+🇦🇶 Flag: Antarctica U+1F1E6, U+1F1F6
+🇦🇷 Flag: Argentina U+1F1E6, U+1F1F7
+🇦🇸 Flag: American Samoa U+1F1E6, U+1F1F8
+🇦🇹 Flag: Austria U+1F1E6, U+1F1F9
+🇦🇺 Flag: Australia U+1F1E6, U+1F1FA
+🇦🇼 Flag: Aruba U+1F1E6, U+1F1FC
+🇦🇽 Flag: Åland Islands U+1F1E6, U+1F1FD
+🇦🇿 Flag: Azerbaijan U+1F1E6, U+1F1FF
+🇧🇦 Flag: Bosnia & Herzegovina U+1F1E7, U+1F1E6
+🇧🇧 Flag: Barbados U+1F1E7, U+1F1E7
+🇧🇩 Flag: Bangladesh U+1F1E7, U+1F1E9
+🇧🇪 Flag: Belgium U+1F1E7, U+1F1EA
+🇧🇫 Flag: Burkina Faso U+1F1E7, U+1F1EB
+🇧🇬 Flag: Bulgaria U+1F1E7, U+1F1EC
+🇧🇭 Flag: Bahrain U+1F1E7, U+1F1ED
+🇧🇮 Flag: Burundi U+1F1E7, U+1F1EE
+🇧🇯 Flag: Benin U+1F1E7, U+1F1EF
+🇧🇱 Flag: St. Barthélemy U+1F1E7, U+1F1F1
+🇧🇲 Flag: Bermuda U+1F1E7, U+1F1F2
+🇧🇳 Flag: Brunei U+1F1E7, U+1F1F3
+🇧🇴 Flag: Bolivia U+1F1E7, U+1F1F4
+🇧🇶 Flag: Caribbean Netherlands U+1F1E7, U+1F1F6
+🇧🇷 Flag: Brazil U+1F1E7, U+1F1F7
+🇧🇸 Flag: Bahamas U+1F1E7, U+1F1F8
+🇧🇹 Flag: Bhutan U+1F1E7, U+1F1F9
+🇧🇻 Flag: Bouvet Island U+1F1E7, U+1F1FB
+🇧🇼 Flag: Botswana U+1F1E7, U+1F1FC
+🇧🇾 Flag: Belarus U+1F1E7, U+1F1FE
+🇧🇿 Flag: Belize U+1F1E7, U+1F1FF
+🇨🇦 Flag: Canada U+1F1E8, U+1F1E6
+🇨🇨 Flag: Cocos (Keeling) Islands U+1F1E8, U+1F1E8
+🇨🇩 Flag: Congo - Kinshasa U+1F1E8, U+1F1E9
+🇨🇫 Flag: Central African Republic U+1F1E8, U+1F1EB
+🇨🇬 Flag: Congo - Brazzaville U+1F1E8, U+1F1EC
+🇨🇭 Flag: Switzerland U+1F1E8, U+1F1ED
+🇨🇮 Flag: Côte d’Ivoire U+1F1E8, U+1F1EE
+🇨🇰 Flag: Cook Islands U+1F1E8, U+1F1F0
+🇨🇱 Flag: Chile U+1F1E8, U+1F1F1
+🇨🇲 Flag: Cameroon U+1F1E8, U+1F1F2
+🇨🇳 Flag: China U+1F1E8, U+1F1F3
+🇨🇴 Flag: Colombia U+1F1E8, U+1F1F4
+🇨🇵 Flag: Clipperton Island U+1F1E8, U+1F1F5
+🇨🇷 Flag: Costa Rica U+1F1E8, U+1F1F7
+🇨🇺 Flag: Cuba U+1F1E8, U+1F1FA
+🇨🇻 Flag: Cape Verde U+1F1E8, U+1F1FB
+🇨🇼 Flag: Curaçao U+1F1E8, U+1F1FC
+🇨🇽 Flag: Christmas Island U+1F1E8, U+1F1FD
+🇨🇾 Flag: Cyprus U+1F1E8, U+1F1FE
+🇨🇿 Flag: Czechia U+1F1E8, U+1F1FF
+🇩🇪 Flag: Germany U+1F1E9, U+1F1EA
+🇩🇬 Flag: Diego Garcia U+1F1E9, U+1F1EC
+🇩🇯 Flag: Djibouti U+1F1E9, U+1F1EF
+🇩🇰 Flag: Denmark U+1F1E9, U+1F1F0
+🇩🇲 Flag: Dominica U+1F1E9, U+1F1F2
+🇩🇴 Flag: Dominican Republic U+1F1E9, U+1F1F4
+🇩🇿 Flag: Algeria U+1F1E9, U+1F1FF
+🇪🇦 Flag: Ceuta & Melilla U+1F1EA, U+1F1E6
+🇪🇨 Flag: Ecuador U+1F1EA, U+1F1E8
+🇪🇪 Flag: Estonia U+1F1EA, U+1F1EA
+🇪🇬 Flag: Egypt U+1F1EA, U+1F1EC
+🇪🇭 Flag: Western Sahara U+1F1EA, U+1F1ED
+🇪🇷 Flag: Eritrea U+1F1EA, U+1F1F7
+🇪🇸 Flag: Spain U+1F1EA, U+1F1F8
+🇪🇹 Flag: Ethiopia U+1F1EA, U+1F1F9
+🇪🇺 Flag: European Union U+1F1EA, U+1F1FA
+🇫🇮 Flag: Finland U+1F1EB, U+1F1EE
+🇫🇯 Flag: Fiji U+1F1EB, U+1F1EF
+🇫🇰 Flag: Falkland Islands U+1F1EB, U+1F1F0
+🇫🇲 Flag: Micronesia U+1F1EB, U+1F1F2
+🇫🇴 Flag: Faroe Islands U+1F1EB, U+1F1F4
+🇫🇷 Flag: France U+1F1EB, U+1F1F7
+🇬🇦 Flag: Gabon U+1F1EC, U+1F1E6
+🇬🇧 Flag: United Kingdom U+1F1EC, U+1F1E7
+🇬🇩 Flag: Grenada U+1F1EC, U+1F1E9
+🇬🇪 Flag: Georgia U+1F1EC, U+1F1EA
+🇬🇫 Flag: French Guiana U+1F1EC, U+1F1EB
+🇬🇬 Flag: Guernsey U+1F1EC, U+1F1EC
+🇬🇭 Flag: Ghana U+1F1EC, U+1F1ED
+🇬🇮 Flag: Gibraltar U+1F1EC, U+1F1EE
+🇬🇱 Flag: Greenland U+1F1EC, U+1F1F1
+🇬🇲 Flag: Gambia U+1F1EC, U+1F1F2
+🇬🇳 Flag: Guinea U+1F1EC, U+1F1F3
+🇬🇵 Flag: Guadeloupe U+1F1EC, U+1F1F5
+🇬🇶 Flag: Equatorial Guinea U+1F1EC, U+1F1F6
+🇬🇷 Flag: Greece U+1F1EC, U+1F1F7
+🇬🇸 Flag: South Georgia & South Sandwich U+1F1EC, U+1F1F8
+Islands
+🇬🇹 Flag: Guatemala U+1F1EC, U+1F1F9
+🇬🇺 Flag: Guam U+1F1EC, U+1F1FA
+🇬🇼 Flag: Guinea-Bissau U+1F1EC, U+1F1FC
+🇬🇾 Flag: Guyana U+1F1EC, U+1F1FE
+🇭🇰 Flag: Hong Kong SAR China U+1F1ED, U+1F1F0
+🇭🇲 Flag: Heard & McDonald Islands U+1F1ED, U+1F1F2
+🇭🇳 Flag: Honduras U+1F1ED, U+1F1F3
+🇭🇷 Flag: Croatia U+1F1ED, U+1F1F7
+🇭🇹 Flag: Haiti U+1F1ED, U+1F1F9
+🇭🇺 Flag: Hungary U+1F1ED, U+1F1FA
+🇮🇨 Flag: Canary Islands U+1F1EE, U+1F1E8
+🇮🇩 Flag: Indonesia U+1F1EE, U+1F1E9
+🇮🇪 Flag: Ireland U+1F1EE, U+1F1EA
+🇮🇱 Flag: Israel U+1F1EE, U+1F1F1
+🇮🇲 Flag: Isle of Man U+1F1EE, U+1F1F2
+🇮🇳 Flag: India U+1F1EE, U+1F1F3
+🇮🇴 Flag: British Indian Ocean Territory U+1F1EE, U+1F1F4
+🇮🇶 Flag: Iraq U+1F1EE, U+1F1F6
+🇮🇷 Flag: Iran U+1F1EE, U+1F1F7
+🇮🇸 Flag: Iceland U+1F1EE, U+1F1F8
+🇮🇹 Flag: Italy U+1F1EE, U+1F1F9
+🇯🇪 Flag: Jersey U+1F1EF, U+1F1EA
+🇯🇲 Flag: Jamaica U+1F1EF, U+1F1F2
+🇯🇴 Flag: Jordan U+1F1EF, U+1F1F4
+🇯🇵 Flag: Japan U+1F1EF, U+1F1F5
+🇰🇪 Flag: Kenya U+1F1F0, U+1F1EA
+🇰🇬 Flag: Kyrgyzstan U+1F1F0, U+1F1EC
+🇰🇭 Flag: Cambodia U+1F1F0, U+1F1ED
+🇰🇮 Flag: Kiribati U+1F1F0, U+1F1EE
+🇰🇲 Flag: Comoros U+1F1F0, U+1F1F2
+🇰🇳 Flag: St. Kitts & Nevis U+1F1F0, U+1F1F3
+🇰🇵 Flag: North Korea U+1F1F0, U+1F1F5
+🇰🇷 Flag: South Korea U+1F1F0, U+1F1F7
+🇰🇼 Flag: Kuwait U+1F1F0, U+1F1FC
+🇰🇾 Flag: Cayman Islands U+1F1F0, U+1F1FE
+🇰🇿 Flag: Kazakhstan U+1F1F0, U+1F1FF
+🇱🇦 Flag: Laos U+1F1F1, U+1F1E6
+🇱🇧 Flag: Lebanon U+1F1F1, U+1F1E7
+🇱🇨 Flag: St. Lucia U+1F1F1, U+1F1E8
+🇱🇮 Flag: Liechtenstein U+1F1F1, U+1F1EE
+🇱🇰 Flag: Sri Lanka U+1F1F1, U+1F1F0
+🇱🇷 Flag: Liberia U+1F1F1, U+1F1F7
+🇱🇸 Flag: Lesotho U+1F1F1, U+1F1F8
+🇱🇹 Flag: Lithuania U+1F1F1, U+1F1F9
+🇱🇺 Flag: Luxembourg U+1F1F1, U+1F1FA
+🇱🇻 Flag: Latvia U+1F1F1, U+1F1FB
+🇱🇾 Flag: Libya U+1F1F1, U+1F1FE
+🇲🇦 Flag: Morocco U+1F1F2, U+1F1E6
+🇲🇨 Flag: Monaco U+1F1F2, U+1F1E8
+🇲🇩 Flag: Moldova U+1F1F2, U+1F1E9
+🇲🇪 Flag: Montenegro U+1F1F2, U+1F1EA
+🇲🇫 Flag: St. Martin U+1F1F2, U+1F1EB
+🇲🇬 Flag: Madagascar U+1F1F2, U+1F1EC
+🇲🇭 Flag: Marshall Islands U+1F1F2, U+1F1ED
+🇲🇰 Flag: North Macedonia U+1F1F2, U+1F1F0
+🇲🇱 Flag: Mali U+1F1F2, U+1F1F1
+🇲🇲 Flag: Myanmar (Burma) U+1F1F2, U+1F1F2
+🇲🇳 Flag: Mongolia U+1F1F2, U+1F1F3
+🇲🇴 Flag: Macao Sar China U+1F1F2, U+1F1F4
+🇲🇵 Flag: Northern Mariana Islands U+1F1F2, U+1F1F5
+🇲🇶 Flag: Martinique U+1F1F2, U+1F1F6
+🇲🇷 Flag: Mauritania U+1F1F2, U+1F1F7
+🇲🇸 Flag: Montserrat U+1F1F2, U+1F1F8
+🇲🇹 Flag: Malta U+1F1F2, U+1F1F9
+🇲🇺 Flag: Mauritius U+1F1F2, U+1F1FA
+🇲🇻 Flag: Maldives U+1F1F2, U+1F1FB
+🇲🇼 Flag: Malawi U+1F1F2, U+1F1FC
+🇲🇽 Flag: Mexico U+1F1F2, U+1F1FD
+🇲🇾 Flag: Malaysia U+1F1F2, U+1F1FE
+🇲🇿 Flag: Mozambique U+1F1F2, U+1F1FF
+🇳🇦 Flag: Namibia U+1F1F3, U+1F1E6
+🇳🇨 Flag: New Caledonia U+1F1F3, U+1F1E8
+🇳🇪 Flag: Niger U+1F1F3, U+1F1EA
+🇳🇫 Flag: Norfolk Island U+1F1F3, U+1F1EB
+🇳🇬 Flag: Nigeria U+1F1F3, U+1F1EC
+🇳🇮 Flag: Nicaragua U+1F1F3, U+1F1EE
+🇳🇱 Flag: Netherlands U+1F1F3, U+1F1F1
+🇳🇴 Flag: Norway U+1F1F3, U+1F1F4
+🇳🇵 Flag: Nepal U+1F1F3, U+1F1F5
+🇳🇷 Flag: Nauru U+1F1F3, U+1F1F7
+🇳🇺 Flag: Niue U+1F1F3, U+1F1FA
+🇳🇿 Flag: New Zealand U+1F1F3, U+1F1FF
+🇴🇲 Flag: Oman U+1F1F4, U+1F1F2
+🇵🇦 Flag: Panama U+1F1F5, U+1F1E6
+🇵🇪 Flag: Peru U+1F1F5, U+1F1EA
+🇵🇫 Flag: French Polynesia U+1F1F5, U+1F1EB
+🇵🇬 Flag: Papua New Guinea U+1F1F5, U+1F1EC
+🇵🇭 Flag: Philippines U+1F1F5, U+1F1ED
+🇵🇰 Flag: Pakistan U+1F1F5, U+1F1F0
+🇵🇱 Flag: Poland U+1F1F5, U+1F1F1
+🇵🇲 Flag: St. Pierre & Miquelon U+1F1F5, U+1F1F2
+🇵🇳 Flag: Pitcairn Islands U+1F1F5, U+1F1F3
+🇵🇷 Flag: Puerto Rico U+1F1F5, U+1F1F7
+🇵🇸 Flag: Palestinian Territories U+1F1F5, U+1F1F8
+🇵🇹 Flag: Portugal U+1F1F5, U+1F1F9
+🇵🇼 Flag: Palau U+1F1F5, U+1F1FC
+🇵🇾 Flag: Paraguay U+1F1F5, U+1F1FE
+🇶🇦 Flag: Qatar U+1F1F6, U+1F1E6
+🇷🇪 Flag: Réunion U+1F1F7, U+1F1EA
+🇷🇴 Flag: Romania U+1F1F7, U+1F1F4
+🇷🇸 Flag: Serbia U+1F1F7, U+1F1F8
+🇷🇺 Flag: Russia U+1F1F7, U+1F1FA
+🇷🇼 Flag: Rwanda U+1F1F7, U+1F1FC
+🇸🇦 Flag: Saudi Arabia U+1F1F8, U+1F1E6
+🇸🇧 Flag: Solomon Islands U+1F1F8, U+1F1E7
+🇸🇨 Flag: Seychelles U+1F1F8, U+1F1E8
+🇸🇩 Flag: Sudan U+1F1F8, U+1F1E9
+🇸🇪 Flag: Sweden U+1F1F8, U+1F1EA
+🇸🇬 Flag: Singapore U+1F1F8, U+1F1EC
+🇸🇭 Flag: St. Helena U+1F1F8, U+1F1ED
+🇸🇮 Flag: Slovenia U+1F1F8, U+1F1EE
+🇸🇯 Flag: Svalbard & Jan Mayen U+1F1F8, U+1F1EF
+🇸🇰 Flag: Slovakia U+1F1F8, U+1F1F0
+🇸🇱 Flag: Sierra Leone U+1F1F8, U+1F1F1
+🇸🇲 Flag: San Marino U+1F1F8, U+1F1F2
+🇸🇳 Flag: Senegal U+1F1F8, U+1F1F3
+🇸🇴 Flag: Somalia U+1F1F8, U+1F1F4
+🇸🇷 Flag: Suriname U+1F1F8, U+1F1F7
+🇸🇸 Flag: South Sudan U+1F1F8, U+1F1F8
+🇸🇹 Flag: São Tomé & Príncipe U+1F1F8, U+1F1F9
+🇸🇻 Flag: El Salvador U+1F1F8, U+1F1FB
+🇸🇽 Flag: Sint Maarten U+1F1F8, U+1F1FD
+🇸🇾 Flag: Syria U+1F1F8, U+1F1FE
+🇸🇿 Flag: Eswatini U+1F1F8, U+1F1FF
+🇹🇦 Flag: Tristan Da Cunha U+1F1F9, U+1F1E6
+🇹🇨 Flag: Turks & Caicos Islands U+1F1F9, U+1F1E8
+🇹🇩 Flag: Chad U+1F1F9, U+1F1E9
+🇹🇫 Flag: French Southern Territories U+1F1F9, U+1F1EB
+🇹🇬 Flag: Togo U+1F1F9, U+1F1EC
+🇹🇭 Flag: Thailand U+1F1F9, U+1F1ED
+🇹🇯 Flag: Tajikistan U+1F1F9, U+1F1EF
+🇹🇰 Flag: Tokelau U+1F1F9, U+1F1F0
+🇹🇱 Flag: Timor-Leste U+1F1F9, U+1F1F1
+🇹🇲 Flag: Turkmenistan U+1F1F9, U+1F1F2
+🇹🇳 Flag: Tunisia U+1F1F9, U+1F1F3
+🇹🇴 Flag: Tonga U+1F1F9, U+1F1F4
+🇹🇷 Flag: Turkey U+1F1F9, U+1F1F7
+🇹🇹 Flag: Trinidad & Tobago U+1F1F9, U+1F1F9
+🇹🇻 Flag: Tuvalu U+1F1F9, U+1F1FB
+🇹🇼 Flag: Taiwan U+1F1F9, U+1F1FC
+🇹🇿 Flag: Tanzania U+1F1F9, U+1F1FF
+🇺🇦 Flag: Ukraine U+1F1FA, U+1F1E6
+🇺🇬 Flag: Uganda U+1F1FA, U+1F1EC
+🇺🇲 Flag: U.S. Outlying Islands U+1F1FA, U+1F1F2
+🇺🇳 Flag: United Nations U+1F1FA, U+1F1F3
+🇺🇸 Flag: United States U+1F1FA, U+1F1F8
+🇺🇾 Flag: Uruguay U+1F1FA, U+1F1FE
+🇺🇿 Flag: Uzbekistan U+1F1FA, U+1F1FF
+🇻🇦 Flag: Vatican City U+1F1FB, U+1F1E6
+🇻🇨 Flag: St. Vincent & Grenadines U+1F1FB, U+1F1E8
+🇻🇪 Flag: Venezuela U+1F1FB, U+1F1EA
+🇻🇬 Flag: British Virgin Islands U+1F1FB, U+1F1EC
+🇻🇮 Flag: U.S. Virgin Islands U+1F1FB, U+1F1EE
+🇻🇳 Flag: Vietnam U+1F1FB, U+1F1F3
+🇻🇺 Flag: Vanuatu U+1F1FB, U+1F1FA
+🇼🇫 Flag: Wallis & Futuna U+1F1FC, U+1F1EB
+🇼🇸 Flag: Samoa U+1F1FC, U+1F1F8
+🇽🇰 Flag: Kosovo U+1F1FD, U+1F1F0
+🇾🇪 Flag: Yemen U+1F1FE, U+1F1EA
+🇾🇹 Flag: Mayotte U+1F1FE, U+1F1F9
+🇿🇦 Flag: South Africa U+1F1FF, U+1F1E6
+🇿🇲 Flag: Zambia U+1F1FF, U+1F1F2
+🇿🇼 Flag: Zimbabwe U+1F1FF, U+1F1FC
+ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿ Flag: England U+1F3F4, U+E0067, U+E0062,
+ U+E0065, U+E006E, U+E0067, U+E007F
+ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿ Flag: Scotland U+1F3F4, U+E0067, U+E0062,
+ U+E0073, U+E0063, U+E0074, U+E007F
+ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿ Flag: Wales U+1F3F4, U+E0067, U+E0062,
+ U+E0077, U+E006C, U+E0073, U+E007F
+🇦 Regional Indicator Symbol Letter A U+1F1E6
+🇧 Regional Indicator Symbol Letter B U+1F1E7
+🇨 Regional Indicator Symbol Letter C U+1F1E8
+🇩 Regional Indicator Symbol Letter D U+1F1E9
+🇪 Regional Indicator Symbol Letter E U+1F1EA
+🇫 Regional Indicator Symbol Letter F U+1F1EB
+🇬 Regional Indicator Symbol Letter G U+1F1EC
+🇭 Regional Indicator Symbol Letter H U+1F1ED
+🇮 Regional Indicator Symbol Letter I U+1F1EE
+🇯 Regional Indicator Symbol Letter J U+1F1EF
+🇰 Regional Indicator Symbol Letter K U+1F1F0
+🇱 Regional Indicator Symbol Letter L U+1F1F1
+🇲 Regional Indicator Symbol Letter M U+1F1F2
+🇳 Regional Indicator Symbol Letter N U+1F1F3
+🇴 Regional Indicator Symbol Letter O U+1F1F4
+🇵 Regional Indicator Symbol Letter P U+1F1F5
+🇶 Regional Indicator Symbol Letter Q U+1F1F6
+🇷 Regional Indicator Symbol Letter R U+1F1F7
+🇸 Regional Indicator Symbol Letter S U+1F1F8
+🇹 Regional Indicator Symbol Letter T U+1F1F9
+🇺 Regional Indicator Symbol Letter U U+1F1FA
+🇻 Regional Indicator Symbol Letter V U+1F1FB
+🇼 Regional Indicator Symbol Letter W U+1F1FC
+🇽 Regional Indicator Symbol Letter X U+1F1FD
+🇾 Regional Indicator Symbol Letter Y U+1F1FE
+🇿 Regional Indicator Symbol Letter Z U+1F1FF
+Tag Space U+E0020
+Tag Left Square Bracket U+E005B
+Tag Vertical Line U+E007C
+4ï¸ Digit Four U+34, U+FE0F
+0ï¸ Digit Zero U+30, U+FE0F
+Tag Latin Capital Letter G U+E0047
+Tag Digit Eight U+E0038
+Tag Latin Capital Letter N U+E004E
+Tag Dollar Sign U+E0024
+*ï¸ Asterisk U+2A, U+FE0F
+Tag Right Square Bracket U+E005D
+Tag Left Parenthesis U+E0028
+#ï¸ Number Sign U+23, U+FE0F
+Tag Digit Two U+E0032
+Tag Latin Capital Letter K U+E004B
+Tag Latin Small Letter Y U+E0079
+Tag Tilde U+E007E
+Cancel Tag U+E007F
+Tag Asterisk U+E002A
+Tag Quotation Mark U+E0022
+Tag Colon U+E003A
+Tag Digit Zero U+E0030
+ï¸ Variation Selector-16 U+FE0F
+Tag Latin Capital Letter X U+E0058
+Tag Reverse Solidus U+E005C
+Tag Circumflex Accent U+E005E
+Tag Latin Capital Letter Q U+E0051
+Tag Left Curly Bracket U+E007B
+Tag Digit Three U+E0033
+Tag Right Parenthesis U+E0029
+Tag Latin Small Letter P U+E0070
+Tag Equals Sign U+E003D
+Tag Digit Four U+E0034
+Tag Comma U+E002C
+Tag Plus Sign U+E002B
+Tag Latin Small Letter J U+E006A
+Tag Latin Capital Letter F U+E0046
+Tag Digit Six U+E0036
+Tag Latin Capital Letter P U+E0050
+Tag Latin Small Letter O U+E006F
+Tag Latin Capital Letter C U+E0043
+Tag Latin Capital Letter Y U+E0059
+7ï¸ Digit Seven U+37, U+FE0F
+Tag Grave Accent U+E0060
+Tag Latin Capital Letter T U+E0054
+⃣Combining Enclosing Keycap U+20E3
+Tag Latin Small Letter R U+E0072
+Tag Number Sign U+E0023
+Tag Latin Small Letter H U+E0068
+Tag Latin Small Letter a U+E0061
+Tag Latin Small Letter U U+E0075
+Tag Ampersand U+E0026
+Tag Latin Small Letter T U+E0074
+Tag Latin Small Letter M U+E006D
+Tag Latin Capital Letter E U+E0045
+Tag Solidus U+E002F
+8ï¸ Digit Eight U+38, U+FE0F
+Tag Latin Small Letter X U+E0078
+Tag Latin Small Letter G U+E0067
+Tag Latin Small Letter Z U+E007A
+Tag Less-Than Sign U+E003C
+Tag Commercial at U+E0040
+Tag Latin Small Letter K U+E006B
+Tag Latin Small Letter V U+E0076
+Tag Digit One U+E0031
+Tag Digit Seven U+E0037
+Tag Latin Capital Letter B U+E0042
+Tag Latin Small Letter F U+E0066
+1ï¸ Digit One U+31, U+FE0F
+Tag Latin Small Letter D U+E0064
+Tag Latin Small Letter C U+E0063
+Tag Right Curly Bracket U+E007D
+5ï¸ Digit Five U+35, U+FE0F
+Tag Latin Capital Letter S U+E0053
+Tag Latin Capital Letter V U+E0056
+Tag Digit Five U+E0035
+Tag Full Stop U+E002E
+Tag Percent Sign U+E0025
+†Zero Width Joiner U+200D
+Tag Latin Small Letter N U+E006E
+Tag Hyphen-Minus U+E002D
+Tag Latin Small Letter B U+E0062
+Tag Latin Capital Letter R U+E0052
+Tag Digit Nine U+E0039
+Tag Latin Small Letter E U+E0065
+Tag Latin Capital Letter Z U+E005A
+Tag Latin Capital Letter I U+E0049
+Tag Latin Capital Letter W U+E0057
+Tag Greater-Than Sign U+E003E
+9ï¸ Digit Nine U+39, U+FE0F
+Tag Latin Small Letter I U+E0069
+Tag Latin Capital Letter J U+E004A
+Tag Latin Small Letter Q U+E0071
+Tag Semicolon U+E003B
+2ï¸ Digit Two U+32, U+FE0F
+Tag Latin Capital Letter M U+E004D
+Tag Low Line U+E005F
+Tag Apostrophe U+E0027
+Tag Exclamation Mark U+E0021
+Tag Latin Capital Letter O U+E004F
+Tag Latin Small Letter W U+E0077
+Tag Latin Capital Letter U U+E0055
+Tag Latin Capital Letter a U+E0041
+Tag Latin Capital Letter D U+E0044
+Tag Latin Small Letter S U+E0073
+Tag Question Mark U+E003F
+6ï¸ Digit Six U+36, U+FE0F
+Tag Latin Capital Letter L U+E004C
+3ï¸ Digit Three U+33, U+FE0F
+Tag Latin Capital Letter H U+E0048
+Tag Latin Small Letter L U+E006C
diff --git a/stow/i3/dot-config/i3/config b/stow/i3/dot-config/i3/config
new file mode 100644
index 0000000..00daed1
--- /dev/null
+++ b/stow/i3/dot-config/i3/config
@@ -0,0 +1,279 @@
+# This file has been auto-generated by i3-config-wizard(1).
+# It will not be overwritten, so edit it as you like.
+#
+# Should you change your keyboard layout some time, delete
+# this file and re-run i3-config-wizard(1).
+#
+
+# i3 config file (v4)
+#
+# Please see https://i3wm.org/docs/userguide.html for a complete reference!
+
+set $mod Mod4
+
+# Read from ~/.Xresources
+# Last value is color if not able to read it
+set_from_resource $black i3wm.color0 #000000
+set_from_resource $black2 i3wm.color8 #3f3f3f
+set_from_resource $red i3wm.color1 #ff0000
+set_from_resource $red2 i3wm.color9 #ff7f7f
+set_from_resource $green i3wm.color2 #00ff00
+set_from_resource $green2 i3wm.color10 #7fff7f
+set_from_resource $yellow i3wm.color3 #ffff00
+set_from_resource $yellow2 i3wm.color11 #ffff7f
+set_from_resource $blue i3wm.color4 #0000ff
+set_from_resource $blue2 i3wm.color12 #0000ff
+set_from_resource $magenta i3wm.color5 #ff00ff
+set_from_resource $magenta2 i3wm.color13 #ff7fff
+set_from_resource $cyan i3wm.color6 #00ffff
+set_from_resource $cyan2 i3wm.color14 #7fffff
+set_from_resource $white i3wm.color7 #ffffff
+set_from_resource $white2 i3wm.color15 #afafaf
+
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below.
+font pango:monospace 8
+
+# This font is widely installed, provides lots of unicode glyphs, right-to-left
+# text rendering and scalability on retina/hidpi displays (thanks to pango).
+#font pango:DejaVu Sans Mono 8
+
+# Before i3 v4.8, we used to recommend this one as the default:
+# font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+# The font above is very space-efficient, that is, it looks good, sharp and
+# clear in small sizes. However, its unicode glyph coverage is limited, the old
+# X core fonts rendering does not support right-to-left and this being a bitmap
+# font, it doesn’t scale on retina/hidpi displays.
+#
+# Use these keys for focus, movement, and resize directions when reaching for
+# the arrows is not convenient
+set $left h
+set $down j
+set $up k
+set $right l
+
+# define workspaces names
+set $ws1 1:💻
+set $ws2 2:ðŸŒ
+set $ws3 3:👾
+set $ws4 4:🧿
+set $ws5 5:💬
+set $ws6 6:🎵
+set $ws7 7
+set $ws8 8
+set $ws9 9
+set $ws10 10
+set $wst 🙈
+
+# use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# kill focused window
+bindsym $mod+Shift+q kill
+
+# change focus
+bindsym $mod+$left focus left
+bindsym $mod+$down focus down
+bindsym $mod+$up focus up
+bindsym $mod+$right focus right
+
+# alternatively, you can use the cursor keys:
+bindsym $mod+Left focus left
+bindsym $mod+Down focus down
+bindsym $mod+Up focus up
+bindsym $mod+Right focus right
+
+# move focused window
+bindsym $mod+Shift+$left move left
+bindsym $mod+Shift+$down move down
+bindsym $mod+Shift+$up move up
+bindsym $mod+Shift+$right move right
+
+# alternatively, you can use the cursor keys:
+bindsym $mod+Shift+Left move left
+bindsym $mod+Shift+Down move down
+bindsym $mod+Shift+Up move up
+bindsym $mod+Shift+Right move right
+
+# navigate workspaces
+bindsym $mod+Control+$left workspace prev
+bindsym $mod+Control+$right workspace next
+
+# navigate workspaces with cursor keys:
+bindsym $mod+Control+Left workspace prev
+bindsym $mod+Control+Right workspace next
+
+# split in horizontal orientation
+bindsym $mod+b split h
+
+# split in vertical orientation
+bindsym $mod+v split v
+
+# enter fullscreen mode for the focused container
+bindsym $mod+f fullscreen toggle
+
+# toggle bar
+#bindsym $mod+Control+b bar mode invisible
+#bindsym $mod+Shift+b bar mode dock
+bindsym $mod+Shift+b exec toggleBar.sh
+# change container layout (stacked, tabbed, toggle split)
+bindsym $mod+Control+w layout stacking
+bindsym $mod+Shift+w layout tabbed
+bindsym $mod+Shift+e layout toggle split
+
+# toggle tiling / floating
+bindsym $mod+Shift+space floating toggle
+
+# toggle sticky
+bindsym $mod+Shift+s sticky toggle
+
+# modify gaps
+bindsym $mod+Shift+g gaps inner current minus 2
+bindsym $mod+g gaps inner current plus 2
+bindsym $mod+Shift+o gaps outer current minus 2
+bindsym $mod+o gaps outer current plus 2
+
+# change focus between tiling / floating windows
+bindsym $mod+space focus mode_toggle
+
+# focus the parent container
+bindsym $mod+p focus parent
+
+# focus the child container
+bindsym $mod+Shift+p focus child
+
+# switch to workspace
+bindsym $mod+1 workspace $ws1
+bindsym $mod+2 workspace $ws2
+bindsym $mod+3 workspace $ws3
+bindsym $mod+4 workspace $ws4
+bindsym $mod+5 workspace $ws5
+bindsym $mod+6 workspace $ws6
+bindsym $mod+7 workspace $ws7
+bindsym $mod+8 workspace $ws8
+bindsym $mod+9 workspace $ws9
+bindsym $mod+0 workspace $ws10
+bindsym $mod+Control+t workspace $wst
+
+# move focused container to workspace
+bindsym $mod+Shift+1 move container to workspace $ws1
+bindsym $mod+Shift+2 move container to workspace $ws2
+bindsym $mod+Shift+3 move container to workspace $ws3
+bindsym $mod+Shift+4 move container to workspace $ws4
+bindsym $mod+Shift+5 move container to workspace $ws5
+bindsym $mod+Shift+6 move container to workspace $ws6
+bindsym $mod+Shift+7 move container to workspace $ws7
+bindsym $mod+Shift+8 move container to workspace $ws8
+bindsym $mod+Shift+9 move container to workspace $ws9
+bindsym $mod+Shift+0 move container to workspace $ws10
+bindsym $mod+Shift+Control+t move container to workspace $wst
+
+## Custom ##
+
+# colors
+#class #border #bg #text #indicator #child_border
+client.focused $blue2 $black2 $white2 $blue $blue
+client.focused_inactive $black2 $black $white $black2 $black2
+client.unfocused $black2 $black $blue2 $black $black
+client.urgent $red $black $white $red $red
+
+# for window config
+for_window [class="^.*"] border pixel 4
+
+# gaps config
+gaps inner 14
+gaps outer 0
+
+for_window [title="floating"] floating enable border pixel 1
+for_window [title="noborder"] border none
+for_window [title="invisible"] floating enable
+for_window [title="invisible"] border none
+for_window [title="mpvfloat"] floating enable
+for_window [title="mpvfloat"] sticky enable
+for_window [title="mpvfloat"] border pixel 1
+for_window [instance="sxivfloat"] floating enable
+for_window [instance="sxivfloat"] sticky enable
+for_window [instance="sxivfloat"] border pixel 1
+for_window [title="SFML"] floating enable
+for_window [title="Firefox"] floating enable
+
+# edge borders
+hide_edge_borders both
+#hide_edge_borders none
+
+# scratchpad
+bindsym $mod+minus move scratchpad
+bindsym $mod+plus scratchpad show
+
+# reload the configuration file
+bindsym $mod+Control+c reload
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym $mod+Control+r restart
+# exit i3 (logs you out of your X session)
+#bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
+
+# Modes
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+# These bindings trigger as soon as you enter the resize mode
+
+ set $smallSize 1
+ set $bigSize 10
+
+# Pressing left will shrink the window’s width.
+# Pressing right will grow the window’s width.
+# Pressing up will shrink the window’s height.
+# Pressing down will grow the window’s height.
+ bindsym $left resize shrink width $smallSize px or $smallSize ppt
+ bindsym $down resize grow height $smallSize px or $smallSize ppt
+ bindsym $up resize shrink height $smallSize px or $smallSize ppt
+ bindsym $right resize grow width $smallSize px or $smallSize ppt
+
+# same bindings, but for the arrow keys
+ bindsym Left resize shrink width $smallSize px or $smallSize ppt
+ bindsym Down resize grow height $smallSize px or $smallSize ppt
+ bindsym Up resize shrink height $smallSize px or $smallSize ppt
+ bindsym Right resize grow width $smallSize px or $smallSize ppt
+
+# bigger change
+ bindsym $mod+$left resize shrink width $bigSize px or $bigSize ppt
+ bindsym $mod+$down resize grow height $bigSize px or $bigSize ppt
+ bindsym $mod+$up resize shrink height $bigSize px or $bigSize ppt
+ bindsym $mod+$right resize grow width $bigSize px or $bigSize ppt
+
+ bindsym $mod+Left resize shrink width $bigSize px or $bigSize ppt
+ bindsym $mod+Down resize grow height $bigSize px or $bigSize ppt
+ bindsym $mod+Up resize shrink height $bigSize px or $bigSize ppt
+ bindsym $mod+Right resize grow width $bigSize px or $bigSize ppt
+
+# back to normal: Enter or Escape
+ bindsym Return mode "default"
+ bindsym Escape mode "default"
+}
+
+bindsym $mod+Shift+r mode "resize"
+
+# Start i3bar to display a workspace bar (plus the system information i3status
+# finds out, if available)
+bar {
+ position top
+ font pango:inconsolata 9
+ separator_symbol "·"
+strip_workspace_numbers yes
+ colors {
+ background #000000
+ statusline $white2
+ separator $white2
+ focused_workspace $blue $black $blue
+ inactive_workspace $black $black $blue
+ urgent_workspace $magenta $magenta $white2
+ }
+ #status_command i3status
+ status_command i3blocks
+}
+
+### Init
+exec i3-msg "gaps inner current set 0; gaps outer current set 0"
+exec $HOME/scripts/xinit.sh
+
diff --git a/stow/i3blocks/dot-config/i3blocks/config b/stow/i3blocks/dot-config/i3blocks/config
new file mode 100644
index 0000000..17d9fee
--- /dev/null
+++ b/stow/i3blocks/dot-config/i3blocks/config
@@ -0,0 +1,101 @@
+markup=pango
+#separator_block_width=0
+
+#[emojis]
+#command=shufEmoji.sh 1
+#interval=once
+
+#[corona]
+#command=~/scripts/i3blocks/i3corona.sh
+#interval=once
+
+[weather]
+command=~/scripts/i3blocks/i3weather.sh
+interval=once
+
+[moon]
+command=~/scripts/i3blocks/i3moon.sh
+interval=once
+
+#[fortune]
+#color=#ffffff
+#color=#bceebc
+##background=#6da48e
+#command=echo "🥠 $(fortune -n 55 -s | sed 's/\n/ \/ /g')"
+#interval=once
+
+[screenRec]
+command=~/scripts/i3blocks/i3rec.sh
+interval=once
+signal=13
+
+[music]
+color=#a5acff
+command=~/scripts/i3blocks/i3music.sh
+interval=2
+signal=12
+
+[vol]
+#background=#bceebc
+#color=#000000
+command=~/scripts/i3blocks/i3vol.sh
+interval=once
+signal=10
+
+[brightness]
+#background=#ffff7d
+#color=#000000
+command=~/scripts/i3blocks/i3brightness.sh
+interval=once
+signal=11
+
+[battery]
+command=~/scripts/i3blocks/i3battery.sh BAT1
+interval=20
+
+[mail]
+command=~/scripts/i3blocks/i3mailbox.sh
+interval=once
+signal=15
+
+[internet]
+#background=#009e60
+command=~/scripts/i3blocks/i3internet.sh
+interval=2
+
+[cpu]
+label=🌡ï¸
+command=~/scripts/i3blocks/i3cpu.sh
+interval=20
+
+[mem]
+label=🤔
+command=~/scripts/i3blocks/i3mem.sh
+interval=30
+
+[disk]
+label=💽
+command=~/scripts/i3blocks/i3disk.sh
+interval=30
+
+[flags]
+command=~/scripts/i3blocks/i3flags.sh
+interval=30
+
+[litter]
+label=🚮
+command=[ -z "$(ls $HOME/litter)" ] && echo "✅" || du -sh $HOME/litter | cut -d' ' -f1
+interval=once
+signal=14
+
+[appointments]
+label=📆
+command=~/scripts/i3blocks/i3calcurse.sh
+interval=once
+
+[date]
+#color=#000000
+#background=#eb87ff
+color=#eb87ff
+command=~/scripts/i3blocks/i3date.sh
+interval=1
diff --git a/stow/mpv/dot-config/mpv/input.conf b/stow/mpv/dot-config/mpv/input.conf
new file mode 100644
index 0000000..3acfb6c
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/input.conf
@@ -0,0 +1,213 @@
+# mpv keybindings
+#
+# Location of user-defined bindings: ~/.config/mpv/input.conf
+#
+# Lines starting with # are comments. Use SHARP to assign the # key.
+# Copy this file and uncomment and edit the bindings you want to change.
+#
+# List of commands and further details: DOCS/man/input.rst
+# List of special keys: --input-keylist
+# Keybindings testing mode: mpv --input-test --force-window --idle
+#
+# Use 'ignore' to unbind a key fully (e.g. 'ctrl+a ignore').
+#
+# Strings need to be quoted and escaped:
+# KEY show-text "This is a single backslash: \\ and a quote: \" !"
+#
+# You can use modifier-key combinations like Shift+Left or Ctrl+Alt+x with
+# the modifiers Shift, Ctrl, Alt and Meta (may not work on the terminal).
+#
+# The default keybindings are hardcoded into the mpv binary.
+# You can disable them completely with: --no-input-default-bindings
+
+# Developer note:
+# On compilation, this file is baked into the mpv binary, and all lines are
+# uncommented (unless '#' is followed by a space) - thus this file defines the
+# default key bindings.
+
+# If this is enabled, treat all the following bindings as default.
+#default-bindings start
+
+#MOUSE_BTN0 ignore # don't do anything
+#MOUSE_BTN0_DBL cycle fullscreen # toggle fullscreen on/off
+#MOUSE_BTN2 cycle pause # toggle pause on/off
+#MOUSE_BTN3 seek 10
+#MOUSE_BTN4 seek -10
+#MOUSE_BTN5 add volume -2
+#MOUSE_BTN6 add volume 2
+
+# Mouse wheels, touchpad or other input devices that have axes
+# if the input devices supports precise scrolling it will also scale the
+# numeric value accordingly
+#AXIS_UP seek 10
+#AXIS_DOWN seek -10
+#AXIS_LEFT seek 5
+#AXIS_RIGHT seek -5
+
+## Seek units are in seconds, but note that these are limited by keyframes
+#RIGHT seek 5
+#LEFT seek -5
+#UP seek 60
+#DOWN seek -60
+# Do smaller, always exact (non-keyframe-limited), seeks with shift.
+# Don't show them on the OSD (no-osd).
+#Shift+RIGHT no-osd seek 1 exact
+#Shift+LEFT no-osd seek -1 exact
+#Shift+UP no-osd seek 5 exact
+#Shift+DOWN no-osd seek -5 exact
+# Skip to previous/next subtitle (subject to some restrictions; see manpage)
+#Ctrl+LEFT no-osd sub-seek -1
+#Ctrl+RIGHT no-osd sub-seek 1
+#PGUP add chapter 1 # skip to next chapter
+#PGDWN add chapter -1 # skip to previous chapter
+#Shift+PGUP seek 600
+#Shift+PGDWN seek -600
+#[ multiply speed 0.9091 # scale playback speed
+#] multiply speed 1.1
+#{ multiply speed 0.5
+#} multiply speed 2.0
+#BS set speed 1.0 # reset speed to normal
+#q quit
+#Q quit-watch-later
+#q {encode} quit 4
+#ESC set fullscreen no
+#ESC {encode} quit 4
+#p cycle pause # toggle pause/playback mode
+#. frame-step # advance one frame and pause
+#, frame-back-step # go back by one frame and pause
+#SPACE cycle pause
+#> playlist-next # skip to next file
+#ENTER playlist-next # skip to next file
+#< playlist-prev # skip to previous file
+#O no-osd cycle-values osd-level 3 1 # cycle through OSD mode
+#o show-progress
+#P show-progress
+#I show-text "${filename}" # display filename in osd
+#z add sub-delay -0.1 # subtract 100 ms delay from subs
+#x add sub-delay +0.1 # add
+#ctrl++ add audio-delay 0.100 # this changes audio/video sync
+#ctrl+- add audio-delay -0.100
+#9 add volume -2
+#/ add volume -2
+#0 add volume 2
+#* add volume 2
+#m cycle mute
+#1 add contrast -1
+#2 add contrast 1
+#3 add brightness -1
+#4 add brightness 1
+#5 add gamma -1
+#6 add gamma 1
+#7 add saturation -1
+#8 add saturation 1
+#Alt+0 set window-scale 0.5
+#Alt+1 set window-scale 1.0
+#Alt+2 set window-scale 2.0
+# toggle deinterlacer (automatically inserts or removes required filter)
+#d cycle deinterlace
+#r add sub-pos -1 # move subtitles up
+#t add sub-pos +1 # down
+#v cycle sub-visibility
+# stretch SSA/ASS subtitles with anamorphic videos to match historical
+#V cycle sub-ass-vsfilter-aspect-compat
+# switch between applying no style overrides to SSA/ASS subtitles, and
+# overriding them almost completely with the normal subtitle style
+#u cycle-values sub-ass-style-override "force" "no"
+#j cycle sub # cycle through subtitles
+#J cycle sub down # ...backwards
+#SHARP cycle audio # switch audio streams
+#_ cycle video
+#T cycle ontop # toggle video window ontop of other windows
+#f cycle fullscreen # toggle fullscreen
+#s screenshot # take a screenshot
+#S screenshot video # ...without subtitles
+#Ctrl+s screenshot window # ...with subtitles and OSD, and scaled
+#Alt+s screenshot each-frame # automatically screenshot every frame
+#w add panscan -0.1 # zoom out with -panscan 0 -fs
+#e add panscan +0.1 # in
+# cycle video aspect ratios; "-1" is the container aspect
+#A cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"
+#POWER quit
+#PLAY cycle pause
+#PAUSE cycle pause
+#PLAYPAUSE cycle pause
+#STOP quit
+#FORWARD seek 60
+#REWIND seek -60
+#NEXT playlist-next
+#PREV playlist-prev
+#VOLUME_UP add volume 2
+#VOLUME_DOWN add volume -2
+#MUTE cycle mute
+#CLOSE_WIN quit
+#CLOSE_WIN {encode} quit 4
+#E cycle edition # next edition
+#l ab-loop # Set/clear A-B loop points
+#L cycle-values loop "inf" "no" # toggle infinite looping
+#ctrl+c quit 4
+
+# Apple Remote section
+#AR_PLAY cycle pause
+#AR_PLAY_HOLD quit
+#AR_CENTER cycle pause
+#AR_CENTER_HOLD quit
+#AR_NEXT seek 10
+#AR_NEXT_HOLD seek 120
+#AR_PREV seek -10
+#AR_PREV_HOLD seek -120
+#AR_MENU show-progress
+#AR_MENU_HOLD cycle mute
+#AR_VUP add volume 2
+#AR_VUP_HOLD add chapter 1
+#AR_VDOWN add volume -2
+#AR_VDOWN_HOLD add chapter -1
+
+# For tv://
+#h cycle tv-channel -1 # previous channel
+#k cycle tv-channel +1 # next channel
+
+# For dvb://
+#H cycle dvb-channel-name -1 # previous channel
+#K cycle dvb-channel-name +1 # next channel
+
+#
+# Legacy bindings (may or may not be removed in the future)
+#
+#! add chapter -1 # skip to previous chapter
+#@ add chapter 1 # next
+
+#
+# Not assigned by default
+# (not an exhaustive list of unbound commands)
+#
+
+B add sub-scale +0.1 # increase subtitle font size
+b add sub-scale -0.1 # decrease subtitle font size
+# ? sub-step -1 # immediately display next subtitle
+# ? sub-step +1 # previous
+# ? cycle angle # switch DVD/Bluray angle
+# ? add balance -0.1 # adjust audio balance in favor of left
+# ? add balance 0.1 # right
+# ? cycle sub-forced-only # toggle DVD forced subs
+# ? cycle program # cycle transport stream programs
+# ? stop # stop playback (quit or enter idle mode)
+
+l seek 5
+h seek -5
+j seek -60
+k seek 60
+Shift+l no-osd seek 1 exact
+Shift+h no-osd seek -1 exact
+Shift+k no-osd seek 5 exact
+Shift+j no-osd seek -5 exact
+Ctrl+h no-osd sub-seek -1
+Ctrl+l no-osd sub-seek 1
+Alt+j cycle sub
+Alt+J cycle sub down
+Alt+l ab-loop # Set/clear A-B loop points
+Alt+L cycle-values loop "inf" "no" # toggle infinite looping
+i show-text "${media-title}"
+I show-text "${filename}" # display filename in osd
+c show-text "${chapter-list}"
+M show-text "${metadata}"
+p show-text "${video-params}"
diff --git a/stow/mpv/dot-config/mpv/mpv.conf b/stow/mpv/dot-config/mpv/mpv.conf
new file mode 100644
index 0000000..9e5391f
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/mpv.conf
@@ -0,0 +1,21 @@
+# vim: syntax=config
+
+# OSD
+osd-level=1
+osd-font='Linux Libertine'
+osd-font-size=45
+osd-color='#FF00B0B0'
+osd-bar-w=80
+osd-bar-h=1.5
+osd-bar-align-x=0
+osd-bar-align-y=0.8
+
+# Subtitles
+sub-border-size=8
+
+# Terminal
+term-osd-bar
+msg-color
+
+# OSC
+osc=no
diff --git a/stow/mpv/dot-config/mpv/scripts-opts/mpv_thumbnail_script.conf b/stow/mpv/dot-config/mpv/scripts-opts/mpv_thumbnail_script.conf
new file mode 100644
index 0000000..7380feb
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts-opts/mpv_thumbnail_script.conf
@@ -0,0 +1,70 @@
+# The thumbnail cache directory.
+# On Windows this defaults to %TEMP%\mpv_thumbs_cache,
+# and on other platforms to /tmp/mpv_thumbs_cache.
+# The directory will be created automatically, but must be writeable!
+# Use absolute paths, and take note that environment variables like %TEMP% are unsupported (despite the default)!
+cache_directory=/tmp/my_mpv_thumbnails
+# THIS IS NOT A WINDOWS PATH. COMMENT IT OUT OR ADJUST IT YOURSELF.
+
+# Whether to generate thumbnails automatically on video load, without a keypress
+# Defaults to yes
+autogenerate=yes
+
+# Only automatically thumbnail videos shorter than this (in seconds)
+# You will have to press T (or your own keybind) to enable the thumbnail previews
+# Set to 0 to disable the check, ie. thumbnail videos no matter how long they are
+# Defaults to 3600 (one hour)
+autogenerate_max_duration=3600
+
+# Use mpv to generate thumbnail even if ffmpeg is found in PATH
+# ffmpeg is slightly faster than mpv but lacks support for ordered chapters in MKVs,
+# which can break the resulting thumbnails. You have been warned.
+# Defaults to yes (don't use ffmpeg)
+prefer_mpv=yes
+
+# Explicitly disable subtitles on the mpv sub-calls
+# mpv can and will by default render subtitles into the thumbnails.
+# If this is not what you wish, set mpv_no_sub to yes
+# Defaults to no
+mpv_no_sub=no
+
+# Enable to disable the built-in keybind ("T") to add your own, see after the block
+disable_keybinds=no
+
+# The maximum dimensions of the thumbnails, in pixels
+# Defaults to 200 and 200
+thumbnail_width=200
+thumbnail_height=200
+
+# The thumbnail count target
+# (This will result in a thumbnail every ~10 seconds for a 25 minute video)
+thumbnail_count=150
+
+# The above target count will be adjusted by the minimum and
+# maximum time difference between thumbnails.
+# The thumbnail_count will be used to calculate a target separation,
+# and min/max_delta will be used to constrict it.
+
+# In other words, thumbnails will be:
+# - at least min_delta seconds apart (limiting the amount)
+# - at most max_delta seconds apart (raising the amount if needed)
+# Defaults to 5 and 90, values are seconds
+min_delta=5
+max_delta=90
+# 120 seconds aka 2 minutes will add more thumbnails only when the video is over 5 hours long!
+
+# Below are overrides for remote urls (you generally want less thumbnails, because it's slow!)
+# Thumbnailing network paths will be done with mpv (leveraging youtube-dl)
+
+# Allow thumbnailing network paths (naive check for "://")
+# Defaults to no
+thumbnail_network=yes
+# Override thumbnail count, min/max delta, as above
+remote_thumbnail_count=60
+remote_min_delta=15
+remote_max_delta=120
+
+# Try to grab the raw stream and disable ytdl for the mpv subcalls
+# Much faster than passing the url to ytdl again, but may cause problems with some sites
+# Defaults to yes
+remote_direct_stream=yes
diff --git a/stow/mpv/dot-config/mpv/scripts-opts/youtube-quality.conf b/stow/mpv/dot-config/mpv/scripts-opts/youtube-quality.conf
new file mode 100644
index 0000000..9c1a8c9
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts-opts/youtube-quality.conf
@@ -0,0 +1,41 @@
+# KEY BINDINGS
+
+# invoke or dismiss the quality menu
+toggle_menu_binding=ctrl+f
+# move the menu cursor up
+up_binding=UP
+# move the menu cursor down
+down_binding=DOWN
+# select menu entry
+select_binding=ENTER
+
+# formatting / cursors
+selected_and_active=â–¶ -
+selected_and_inactive=â— -
+unselected_and_active=â–· -
+unselected_and_inactive=â—‹ -
+
+# font size scales by window, if false requires larger font and padding sizes
+scale_playlist_by_window=no
+
+# playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
+# example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
+# read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
+# undeclared tags will use default osd settings
+# these styles will be used for the whole playlist. More specific styling will need to be hacked in
+#
+# (a monospaced font is recommended but not required)
+style_ass_tags={\\fnmonospace}
+
+# paddings for top left corner
+text_padding_x=5
+text_padding_y=5
+
+# how many seconds until the quality menu times out
+menu_timeout=10
+
+#use youtube-dl to fetch a list of available formats (overrides quality_strings)
+fetch_formats=yes
+
+# list of ytdl-format strings to choose from
+quality_strings=[ {"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"}, {"2160p" : "bestvideo[height<=?2160]+bestaudio/best"}, {"1440p" : "bestvideo[height<=?1440]+bestaudio/best"}, {"1080p" : "bestvideo[height<=?1080]+bestaudio/best"}, {"720p" : "bestvideo[height<=?720]+bestaudio/best"}, {"480p" : "bestvideo[height<=?480]+bestaudio/best"}, {"360p" : "bestvideo[height<=?360]+bestaudio/best"}, {"240p" : "bestvideo[height<=?240]+bestaudio/best"}, {"144p" : "bestvideo[height<=?144]+bestaudio/best"} ]
diff --git a/stow/mpv/dot-config/mpv/scripts/mpv-splice.lua b/stow/mpv/dot-config/mpv/scripts/mpv-splice.lua
new file mode 100644
index 0000000..b121dc4
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts/mpv-splice.lua
@@ -0,0 +1,328 @@
+-- -----------------------------------------------------------------------------
+--
+-- MPV Splice
+-- URL: https://github.com/pvpscript/mpv-video-splice
+--
+-- Requires: ffmpeg
+--
+-- Description:
+--
+-- This script provides the hability to create video slices by grabbing two
+-- timestamps, which generate a slice from timestamp A[i] to timestamp B[i],
+-- e.g.:
+-- -> Slice 1: 00:10:34.25 -> 00:15:00.00.
+-- -> Slice 2: 00:23:00.84 -> 00:24:10.00.
+-- ...
+-- -> Slice n: 01:44:22.47 -> 01:56:00.00.
+--
+-- Then, all the slices from 1 to n are joined together, creating a new
+-- video.
+--
+-- The output file will appear at the directory that the mpv command was ran,
+-- or in the environment variable set for it (see Environment variables below)
+--
+-- Note: This script prevents the mpv player from closing when the video ends,
+-- so that the slices don't get lost. Keep this in mind if there's the option
+-- 'keep-open=no' in the current config file.
+--
+-- Note: This script will also silence the terminal, so the script messages
+-- can be seen more clearly.
+--
+-- -----------------------------------------------------------------------------
+--
+--
+-- Usage:
+--
+-- In the video screen, press Alt + T to grab the first timestamp and then
+-- press Alt + T again to get the second timestamp. This process will generate
+-- a time range, which represents a video slice. Repeat this process to create
+-- more slices.
+--
+-- To see all the slices made, press Alt + P. All of the slices will appear
+-- in the terminal in order of creation, with their corresponding timestamps.
+-- Incomplete slices will show up as 'Slice N in progress', where N is the
+-- slice number.
+--
+-- To reset an incomplete slice, press Alt + R. If the first part of a slice
+-- was created at the wrong time, this will reset the current slice.
+--
+-- To delete a whole slice, start the slice deletion mode by pressing Alt + D.
+-- When in this mode, it's possible to press Alt + NUM, where NUM is any
+-- number between 0 inclusive and 9 inclusive. For each Alt + NUM pressed, a
+-- number will be concatenated to make the final number referring to the slice
+-- to be removed, then press Alt + D again to stop the slicing deletion mode
+-- and delete the slice corresponding to the formed number.
+--
+-- Example 1: Deleting slice number 3
+-- -> Alt + D # Start slice deletion mode
+-- -> Alt + 3 # Concatenate number 3
+-- -> Alt + D # Exit slice deletion mode
+--
+-- Example 2> Deleting slice number 76
+-- -> Alt + D # Start slice deletion mode
+-- -> Alt + 7 # Concatenate number 7
+-- -> Alt + 6 # Concatenate number 6
+-- -> Alt + D # Exit slice deletion mode
+--
+-- To fire up ffmpeg, which will slice up the video and concatenate the slices
+-- together, press Alt + C. It's important that there are at least one
+-- slice, otherwise no video will be created.
+--
+-- Note: No cut will be made unless the user presses Alt + C.
+-- Also, the original video file won't be affected by the cutting.
+--
+--
+-- -----------------------------------------------------------------------------
+--
+--
+-- Log level:
+--
+-- Everytime a timestamp is grabbed, a text will appear on the screen showing
+-- the selected time.
+-- When Alt + P is pressed, besides showing the slices in the terminal,
+-- it will also show on the screen the total number of cuts (or slices)
+-- that were made.
+-- When the actual cutting and joining process begins, a message will be shown
+-- on the screen and the terminal telling that it began. When the process ends,
+-- a message will appear on the screen and the terminal displaying the full path
+-- of the generated video. It will also appear a message in the terminal telling
+-- that the process ended.
+--
+-- Note: Every message that appears on the terminal has the log level of 'info'.
+--
+--
+-- -----------------------------------------------------------------------------
+--
+--
+-- Environment Variables:
+--
+-- This script uses environment variables to allow the user to
+-- set the temporary location of the video cuts and for setting the location for
+-- the resulting video.
+--
+-- To set the temporary directory, set the variable MPV_SPLICE_TEMP;
+-- e.g.: export MPV_SPLICE_TEMP="$HOME/temporary_location"
+--
+-- To set the video output directory, set the variable MPV_SPLICE_OUTPUT;
+-- e.g.: export MPV_SPLICE_OUTPUT="$HOME/output_location"
+--
+-- Make sure the directories set in the variables really exist, or else the
+-- script might fail.
+--
+-- -----------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+-- Importing the mpv libraries
+
+local mp = require 'mp'
+local msg = require 'mp.msg'
+
+--------------------------------------------------------------------------------
+-- Default variables
+
+local default_tmp_location = "/tmp"
+local default_output_location = mp.get_property("working-directory")
+
+--------------------------------------------------------------------------------
+
+local concat_name = "concat.txt"
+
+local ffmpeg = "ffmpeg -hide_banner -loglevel warning"
+
+local tmp_location = os.getenv("MPV_SPLICE_TEMP")
+ and os.getenv("MPV_SPLICE_TEMP")
+ or default_tmp_location
+local output_location = os.getenv("MPV_SPLICE_OUTPUT")
+ and os.getenv("MPV_SPLICE_OUTPUT")
+ or default_output_location
+
+local times = {}
+local start_time = nil
+local remove_val = ""
+
+local exit_time = 0
+
+--------------------------------------------------------------------------------
+
+function notify(duration, ...)
+ local args = {...}
+ local text = ""
+
+ for i, v in ipairs(args) do
+ text = text .. tostring(v)
+ end
+
+ msg.info(text)
+ mp.command(string.format("show-text \"%s\" %d 1",
+ text, duration))
+end
+
+local function get_time()
+ local time_in_secs = mp.get_property_number('time-pos')
+
+ local hours = math.floor(time_in_secs / 3600)
+ local mins = math.floor((time_in_secs - hours * 3600) / 60)
+ local secs = time_in_secs - hours * 3600 - mins * 60
+
+ local fmt_time = string.format('%02d:%02d:%05.2f', hours, mins, secs)
+
+ return fmt_time
+end
+
+function put_time()
+ local time = get_time()
+ local message = ""
+
+ if not start_time then
+ start_time = time
+ message = "[START TIMESTAMP]"
+ else
+ --times[#times+1] = {
+ table.insert(times, {
+ t_start = start_time,
+ t_end = time
+ })
+ start_time = nil
+
+ message = "[END TIMESTAMP]"
+ end
+
+ notify(2000, message, ": ", time)
+end
+
+function show_times()
+ notify(2000, "Total cuts: ", #times)
+
+ for i, obj in ipairs(times) do
+ msg.info("Slice", i, ": ", obj.t_start, " -> ", obj.t_end)
+ end
+ if start_time then
+ notify(2000, "Slice ", #times+1, " in progress.")
+ end
+end
+
+function reset_current_slice()
+ if start_time then
+ notify(2000, "Slice ", #times+1, " reseted.")
+
+ start_time = nil
+ end
+end
+
+function delete_slice()
+ if remove_val == "" then
+ notify(2000, "Entered slice deletion mode.")
+
+ -- Add shortcut keys to the interval {0..9}.
+ for i=0,9,1 do
+ mp.add_key_binding("Alt+" .. i, "num_key_" .. i,
+ function()
+ remove_val = remove_val .. i
+ notify(1000, "Slice to remove: "
+ .. remove_val)
+ end
+ )
+ end
+ else
+ -- Remove previously added shortcut keys.
+ for i=0,9,1 do
+ mp.remove_key_binding("num_key_" .. i)
+ end
+
+ remove_num = tonumber(remove_val)
+ if #times >= remove_num and remove_num > 0 then
+ table.remove(times, remove_num)
+ notify(2000, "Removed slice ", remove_num)
+ end
+
+ remove_val = ""
+
+ msg.info("Exited slice deletion mode.")
+ end
+end
+
+function prevent_quit(name)
+ if start_time then
+ if os.time() - exit_time <= 2 then
+ mp.command(name)
+ else
+ exit_time = os.time()
+ end
+ notify(3000, "Slice has been marked. Press again to quit")
+ else
+ mp.command(name)
+ end
+end
+
+function process_video()
+ local alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ local rnd_size = 10
+
+ local pieces = {}
+
+ -- Better seed randomization
+ math.randomseed(os.time())
+ math.random(); math.random(); math.random()
+
+ if times[#times] then
+ local tmp_dir = io.popen(string.format("mktemp -d -p %s",
+ tmp_location)):read("*l")
+ local input_file = mp.get_property("path")
+ local ext = string.gmatch(input_file, ".*%.(.*)$")()
+
+ local rnd_str = ""
+ for i=1,rnd_size,1 do
+ local rnd_index = math.floor(math.random() * #alphabet + 0.5)
+ rnd_str = rnd_str .. alphabet:sub(rnd_index, rnd_index)
+ end
+
+ local output_file = string.format("%s/%s_%s_cut.%s",
+ output_location,
+ mp.get_property("filename/no-ext"),
+ rnd_str, ext)
+
+ local cat_file_name = string.format("%s/%s", tmp_dir, "concat.txt")
+ local cat_file_ptr = io.open(cat_file_name, "w")
+
+ notify(2000, "Process started!")
+
+ for i, obj in ipairs(times) do
+ local path = string.format("%s/%s_%d.%s",
+ tmp_dir, rnd_str, i, ext)
+ cat_file_ptr:write(string.format("file '%s'\n", path))
+ os.execute(string.format("%s -ss %s -i \"%s\" -to %s " ..
+ "-c copy -copyts -avoid_negative_ts make_zero \"%s\"",
+ ffmpeg, obj.t_start, input_file, obj.t_end,
+ path))
+ end
+
+ cat_file_ptr:close()
+
+ cmd = string.format("%s -f concat -safe 0 -i \"%s\" " ..
+ "-c copy \"%s\"",
+ ffmpeg, cat_file_name, output_file)
+ os.execute(cmd)
+
+ notify(10000, "File saved as: ", output_file)
+ msg.info("Process ended!")
+
+ os.execute(string.format("rm -rf %s", tmp_dir))
+ msg.info("Temporary directory removed!")
+ end
+end
+
+mp.set_property("keep-open", "yes") -- Prevent mpv from exiting when the video ends
+mp.set_property("quiet", "yes") -- Silence terminal.
+
+mp.add_key_binding('q', "quit", function()
+ prevent_quit("quit")
+end)
+mp.add_key_binding('Shift+q', "quit-watch-later", function()
+ prevent_quit("quit-watch-later")
+end)
+
+mp.add_key_binding('Alt+t', "put_time", put_time)
+mp.add_key_binding('Alt+p', "show_times", show_times)
+mp.add_key_binding('Alt+c', "process_video", process_video)
+mp.add_key_binding('Alt+r', "reset_current_slice", reset_current_slice)
+mp.add_key_binding('Alt+d', "delete_slice", delete_slice)
diff --git a/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_client_osc.lua b/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_client_osc.lua
new file mode 100644
index 0000000..e822846
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_client_osc.lua
@@ -0,0 +1,3886 @@
+--[[
+ Copyright (C) 2017 AMM
+
+ 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/>.
+]]--
+--[[
+ mpv_thumbnail_script.lua 0.4.2 - commit a2de250 (branch master)
+ https://github.com/TheAMM/mpv_thumbnail_script
+ Built on 2018-02-07 20:36:55
+]]--
+local assdraw = require 'mp.assdraw'
+local msg = require 'mp.msg'
+local opt = require 'mp.options'
+local utils = require 'mp.utils'
+
+-- Determine platform --
+ON_WINDOWS = (package.config:sub(1,1) ~= '/')
+
+-- Some helper functions needed to parse the options --
+function isempty(v) return (v == false) or (v == nil) or (v == "") or (v == 0) or (type(v) == "table" and next(v) == nil) end
+
+function divmod (a, b)
+ return math.floor(a / b), a % b
+end
+
+-- Better modulo
+function bmod( i, N )
+ return (i % N + N) % N
+end
+
+function join_paths(...)
+ local sep = ON_WINDOWS and "\\" or "/"
+ local result = "";
+ for i, p in pairs({...}) do
+ if p ~= "" then
+ if is_absolute_path(p) then
+ result = p
+ else
+ result = (result ~= "") and (result:gsub("[\\"..sep.."]*$", "") .. sep .. p) or p
+ end
+ end
+ end
+ return result:gsub("[\\"..sep.."]*$", "")
+end
+
+-- /some/path/file.ext -> /some/path, file.ext
+function split_path( path )
+ local sep = ON_WINDOWS and "\\" or "/"
+ local first_index, last_index = path:find('^.*' .. sep)
+
+ if last_index == nil then
+ return "", path
+ else
+ local dir = path:sub(0, last_index-1)
+ local file = path:sub(last_index+1, -1)
+
+ return dir, file
+ end
+end
+
+function is_absolute_path( path )
+ local tmp, is_win = path:gsub("^[A-Z]:\\", "")
+ local tmp, is_unix = path:gsub("^/", "")
+ return (is_win > 0) or (is_unix > 0)
+end
+
+function Set(source)
+ local set = {}
+ for _, l in ipairs(source) do set[l] = true end
+ return set
+end
+
+---------------------------
+-- More helper functions --
+---------------------------
+
+-- Removes all keys from a table, without destroying the reference to it
+function clear_table(target)
+ for key, value in pairs(target) do
+ target[key] = nil
+ end
+end
+function shallow_copy(target)
+ local copy = {}
+ for k, v in pairs(target) do
+ copy[k] = v
+ end
+ return copy
+end
+
+-- Rounds to given decimals. eg. round_dec(3.145, 0) => 3
+function round_dec(num, idp)
+ local mult = 10^(idp or 0)
+ return math.floor(num * mult + 0.5) / mult
+end
+
+function file_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ local ok, err, code = f:read(1)
+ io.close(f)
+ return code == nil
+ else
+ return false
+ end
+end
+
+function path_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function create_directories(path)
+ local cmd
+ if ON_WINDOWS then
+ cmd = { args = {"cmd", "/c", "mkdir", path} }
+ else
+ cmd = { args = {"mkdir", "-p", path} }
+ end
+ utils.subprocess(cmd)
+end
+
+-- Find an executable in PATH or CWD with the given name
+function find_executable(name)
+ local delim = ON_WINDOWS and ";" or ":"
+
+ local pwd = os.getenv("PWD") or utils.getcwd()
+ local path = os.getenv("PATH")
+
+ local env_path = pwd .. delim .. path -- Check CWD first
+
+ local result, filename
+ for path_dir in env_path:gmatch("[^"..delim.."]+") do
+ filename = join_paths(path_dir, name)
+ if file_exists(filename) then
+ result = filename
+ break
+ end
+ end
+
+ return result
+end
+
+local ExecutableFinder = { path_cache = {} }
+-- Searches for an executable and caches the result if any
+function ExecutableFinder:get_executable_path( name, raw_name )
+ name = ON_WINDOWS and not raw_name and (name .. ".exe") or name
+
+ if self.path_cache[name] == nil then
+ self.path_cache[name] = find_executable(name) or false
+ end
+ return self.path_cache[name]
+end
+
+-- Format seconds to HH.MM.SS.sss
+function format_time(seconds, sep, decimals)
+ decimals = decimals == nil and 3 or decimals
+ sep = sep and sep or "."
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
+
+ return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
+end
+
+-- Format seconds to 1h 2m 3.4s
+function format_time_hms(seconds, sep, decimals, force_full)
+ decimals = decimals == nil and 1 or decimals
+ sep = sep ~= nil and sep or " "
+
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ if force_full or h > 0 then
+ return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
+ elseif m > 0 then
+ return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
+ else
+ return string.format("%." .. tostring(decimals) .. "fs", s)
+ end
+end
+
+-- Writes text on OSD and console
+function log_info(txt, timeout)
+ timeout = timeout or 1.5
+ msg.info(txt)
+ mp.osd_message(txt, timeout)
+end
+
+-- Join table items, ala ({"a", "b", "c"}, "=", "-", ", ") => "=a-, =b-, =c-"
+function join_table(source, before, after, sep)
+ before = before or ""
+ after = after or ""
+ sep = sep or ", "
+ local result = ""
+ for i, v in pairs(source) do
+ if not isempty(v) then
+ local part = before .. v .. after
+ if i == 1 then
+ result = part
+ else
+ result = result .. sep .. part
+ end
+ end
+ end
+ return result
+end
+
+function wrap(s, char)
+ char = char or "'"
+ return char .. s .. char
+end
+-- Wraps given string into 'string' and escapes any 's in it
+function escape_and_wrap(s, char, replacement)
+ char = char or "'"
+ replacement = replacement or "\\" .. char
+ return wrap(string.gsub(s, char, replacement), char)
+end
+-- Escapes single quotes in a string and wraps the input in single quotes
+function escape_single_bash(s)
+ return escape_and_wrap(s, "'", "'\\''")
+end
+
+-- Returns (a .. b) if b is not empty or nil
+function joined_or_nil(a, b)
+ return not isempty(b) and (a .. b) or nil
+end
+
+-- Put items from one table into another
+function extend_table(target, source)
+ for i, v in pairs(source) do
+ table.insert(target, v)
+ end
+end
+
+-- Creates a handle and filename for a temporary random file (in current directory)
+function create_temporary_file(base, mode, suffix)
+ local handle, filename
+ suffix = suffix or ""
+ while true do
+ filename = base .. tostring(math.random(1, 5000)) .. suffix
+ handle = io.open(filename, "r")
+ if not handle then
+ handle = io.open(filename, mode)
+ break
+ end
+ io.close(handle)
+ end
+ return handle, filename
+end
+
+
+function get_processor_count()
+ local proc_count
+
+ if ON_WINDOWS then
+ proc_count = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
+ else
+ local cpuinfo_handle = io.open("/proc/cpuinfo")
+ if cpuinfo_handle ~= nil then
+ local cpuinfo_contents = cpuinfo_handle:read("*a")
+ local _, replace_count = cpuinfo_contents:gsub('processor', '')
+ proc_count = replace_count
+ end
+ end
+
+ if proc_count and proc_count > 0 then
+ return proc_count
+ else
+ return nil
+ end
+end
+
+function substitute_values(string, values)
+ local substitutor = function(match)
+ if match == "%" then
+ return "%"
+ else
+ -- nil is discarded by gsub
+ return values[match]
+ end
+ end
+
+ local substituted = string:gsub('%%(.)', substitutor)
+ return substituted
+end
+
+-- ASS HELPERS --
+function round_rect_top( ass, x0, y0, x1, y1, r )
+ local c = 0.551915024494 * r -- circle approximation
+ ass:move_to(x0 + r, y0)
+ ass:line_to(x1 - r, y0) -- top line
+ if r > 0 then
+ ass:bezier_curve(x1 - r + c, y0, x1, y0 + r - c, x1, y0 + r) -- top right corner
+ end
+ ass:line_to(x1, y1) -- right line
+ ass:line_to(x0, y1) -- bottom line
+ ass:line_to(x0, y0 + r) -- left line
+ if r > 0 then
+ ass:bezier_curve(x0, y0 + r - c, x0 + r - c, y0, x0 + r, y0) -- top left corner
+ end
+end
+
+function round_rect(ass, x0, y0, x1, y1, rtl, rtr, rbr, rbl)
+ local c = 0.551915024494
+ ass:move_to(x0 + rtl, y0)
+ ass:line_to(x1 - rtr, y0) -- top line
+ if rtr > 0 then
+ ass:bezier_curve(x1 - rtr + rtr*c, y0, x1, y0 + rtr - rtr*c, x1, y0 + rtr) -- top right corner
+ end
+ ass:line_to(x1, y1 - rbr) -- right line
+ if rbr > 0 then
+ ass:bezier_curve(x1, y1 - rbr + rbr*c, x1 - rbr + rbr*c, y1, x1 - rbr, y1) -- bottom right corner
+ end
+ ass:line_to(x0 + rbl, y1) -- bottom line
+ if rbl > 0 then
+ ass:bezier_curve(x0 + rbl - rbl*c, y1, x0, y1 - rbl + rbl*c, x0, y1 - rbl) -- bottom left corner
+ end
+ ass:line_to(x0, y0 + rtl) -- left line
+ if rtl > 0 then
+ ass:bezier_curve(x0, y0 + rtl - rtl*c, x0 + rtl - rtl*c, y0, x0 + rtl, y0) -- top left corner
+ end
+end
+-- $Revision: 1.5 $
+-- $Date: 2014-09-10 16:54:25 $
+
+-- This module was originally taken from http://cube3d.de/uploads/Main/sha1.txt.
+
+-------------------------------------------------------------------------------
+-- SHA-1 secure hash computation, and HMAC-SHA1 signature computation,
+-- in pure Lua (tested on Lua 5.1)
+-- License: MIT
+--
+-- Usage:
+-- local hashAsHex = sha1.hex(message) -- returns a hex string
+-- local hashAsData = sha1.bin(message) -- returns raw bytes
+--
+-- local hmacAsHex = sha1.hmacHex(key, message) -- hex string
+-- local hmacAsData = sha1.hmacBin(key, message) -- raw bytes
+--
+--
+-- Pass sha1.hex() a string, and it returns a hash as a 40-character hex string.
+-- For example, the call
+--
+-- local hash = sha1.hex("iNTERFACEWARE")
+--
+-- puts the 40-character string
+--
+-- "e76705ffb88a291a0d2f9710a5471936791b4819"
+--
+-- into the variable 'hash'
+--
+-- Pass sha1.hmacHex() a key and a message, and it returns the signature as a
+-- 40-byte hex string.
+--
+--
+-- The two "bin" versions do the same, but return the 20-byte string of raw
+-- data that the 40-byte hex strings represent.
+--
+-------------------------------------------------------------------------------
+--
+-- Description
+-- Due to the lack of bitwise operations in 5.1, this version uses numbers to
+-- represents the 32bit words that we combine with binary operations. The basic
+-- operations of byte based "xor", "or", "and" are all cached in a combination
+-- table (several 64k large tables are built on startup, which
+-- consumes some memory and time). The caching can be switched off through
+-- setting the local cfg_caching variable to false.
+-- For all binary operations, the 32 bit numbers are split into 8 bit values
+-- that are combined and then merged again.
+--
+-- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+--
+-------------------------------------------------------------------------------
+
+local sha1 = (function()
+local sha1 = {}
+
+-- set this to false if you don't want to build several 64k sized tables when
+-- loading this file (takes a while but grants a boost of factor 13)
+local cfg_caching = false
+-- local storing of global functions (minor speedup)
+local floor,modf = math.floor,math.modf
+local char,format,rep = string.char,string.format,string.rep
+
+-- merge 4 bytes to an 32 bit word
+local function bytes_to_w32 (a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end
+-- split a 32 bit word into four 8 bit numbers
+local function w32_to_bytes (i)
+ return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100
+end
+
+-- shift the bits of a 32 bit word. Don't use negative values for "bits"
+local function w32_rot (bits,a)
+ local b2 = 2^(32-bits)
+ local a,b = modf(a/b2)
+ return a+b*b2*(2^(bits))
+end
+
+-- caching function for functions that accept 2 arguments, both of values between
+-- 0 and 255. The function to be cached is passed, all values are calculated
+-- during loading and a function is returned that returns the cached values (only)
+local function cache2arg (fn)
+ if not cfg_caching then return fn end
+ local lut = {}
+ for i=0,0xffff do
+ local a,b = floor(i/0x100),i%0x100
+ lut[i] = fn(a,b)
+ end
+ return function (a,b)
+ return lut[a*0x100+b]
+ end
+end
+
+-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
+local function byte_to_bits (b)
+ local b = function (n)
+ local b = floor(b/n)
+ return b%2==1
+ end
+ return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
+end
+
+-- builds an 8bit number from 8 booleans
+local function bits_to_byte (a,b,c,d,e,f,g,h)
+ local function n(b,x) return b and x or 0 end
+ return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)
+end
+
+-- debug function for visualizing bits in a string
+local function bits_to_string (a,b,c,d,e,f,g,h)
+ local function x(b) return b and "1" or "0" end
+ return ("%s%s%s%s %s%s%s%s"):format(x(a),x(b),x(c),x(d),x(e),x(f),x(g),x(h))
+end
+
+-- debug function for converting a 8-bit number as bit string
+local function byte_to_bit_string (b)
+ return bits_to_string(byte_to_bits(b))
+end
+
+-- debug function for converting a 32 bit number as bit string
+local function w32_to_bit_string(a)
+ if type(a) == "string" then return a end
+ local aa,ab,ac,ad = w32_to_bytes(a)
+ local s = byte_to_bit_string
+ return ("%s %s %s %s"):format(s(aa):reverse(),s(ab):reverse(),s(ac):reverse(),s(ad):reverse()):reverse()
+end
+
+-- bitwise "and" function for 2 8bit number
+local band = cache2arg (function(a,b)
+ local A,B,C,D,E,F,G,H = byte_to_bits(b)
+ local a,b,c,d,e,f,g,h = byte_to_bits(a)
+ return bits_to_byte(
+ A and a, B and b, C and c, D and d,
+ E and e, F and f, G and g, H and h)
+ end)
+
+-- bitwise "or" function for 2 8bit numbers
+local bor = cache2arg(function(a,b)
+ local A,B,C,D,E,F,G,H = byte_to_bits(b)
+ local a,b,c,d,e,f,g,h = byte_to_bits(a)
+ return bits_to_byte(
+ A or a, B or b, C or c, D or d,
+ E or e, F or f, G or g, H or h)
+ end)
+
+-- bitwise "xor" function for 2 8bit numbers
+local bxor = cache2arg(function(a,b)
+ local A,B,C,D,E,F,G,H = byte_to_bits(b)
+ local a,b,c,d,e,f,g,h = byte_to_bits(a)
+ return bits_to_byte(
+ A ~= a, B ~= b, C ~= c, D ~= d,
+ E ~= e, F ~= f, G ~= g, H ~= h)
+ end)
+
+-- bitwise complement for one 8bit number
+local function bnot (x)
+ return 255-(x % 256)
+end
+
+-- creates a function to combine to 32bit numbers using an 8bit combination function
+local function w32_comb(fn)
+ return function (a,b)
+ local aa,ab,ac,ad = w32_to_bytes(a)
+ local ba,bb,bc,bd = w32_to_bytes(b)
+ return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
+ end
+end
+
+-- create functions for and, xor and or, all for 2 32bit numbers
+local w32_and = w32_comb(band)
+local w32_xor = w32_comb(bxor)
+local w32_or = w32_comb(bor)
+
+-- xor function that may receive a variable number of arguments
+local function w32_xor_n (a,...)
+ local aa,ab,ac,ad = w32_to_bytes(a)
+ for i=1,select('#',...) do
+ local ba,bb,bc,bd = w32_to_bytes(select(i,...))
+ aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
+ end
+ return bytes_to_w32(aa,ab,ac,ad)
+end
+
+-- combining 3 32bit numbers through binary "or" operation
+local function w32_or3 (a,b,c)
+ local aa,ab,ac,ad = w32_to_bytes(a)
+ local ba,bb,bc,bd = w32_to_bytes(b)
+ local ca,cb,cc,cd = w32_to_bytes(c)
+ return bytes_to_w32(
+ bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
+ )
+end
+
+-- binary complement for 32bit numbers
+local function w32_not (a)
+ return 4294967295-(a % 4294967296)
+end
+
+-- adding 2 32bit numbers, cutting off the remainder on 33th bit
+local function w32_add (a,b) return (a+b) % 4294967296 end
+
+-- adding n 32bit numbers, cutting off the remainder (again)
+local function w32_add_n (a,...)
+ for i=1,select('#',...) do
+ a = (a+select(i,...)) % 4294967296
+ end
+ return a
+end
+-- converting the number to a hexadecimal string
+local function w32_to_hexstring (w) return format("%08x",w) end
+
+-- calculating the SHA1 for some text
+function sha1.hex(msg)
+ local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
+ local msg_len_in_bits = #msg * 8
+
+ local first_append = char(0x80) -- append a '1' bit plus seven '0' bits
+
+ local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
+ local current_mod = non_zero_message_bytes % 64
+ local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""
+
+ -- now to append the length as a 64-bit number.
+ local B1, R1 = modf(msg_len_in_bits / 0x01000000)
+ local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
+ local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
+ local B4 = 0x00000100 * R3
+
+ local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits
+ .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits
+
+ msg = msg .. first_append .. second_append .. L64
+
+ assert(#msg % 64 == 0)
+
+ local chunks = #msg / 64
+
+ local W = { }
+ local start, A, B, C, D, E, f, K, TEMP
+ local chunk = 0
+
+ while chunk < chunks do
+ --
+ -- break chunk up into W[0] through W[15]
+ --
+ start,chunk = chunk * 64 + 1,chunk + 1
+
+ for t = 0, 15 do
+ W[t] = bytes_to_w32(msg:byte(start, start + 3))
+ start = start + 4
+ end
+
+ --
+ -- build W[16] through W[79]
+ --
+ for t = 16, 79 do
+ -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
+ W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
+ end
+
+ A,B,C,D,E = H0,H1,H2,H3,H4
+
+ for t = 0, 79 do
+ if t <= 19 then
+ -- (B AND C) OR ((NOT B) AND D)
+ f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
+ K = 0x5A827999
+ elseif t <= 39 then
+ -- B XOR C XOR D
+ f = w32_xor_n(B, C, D)
+ K = 0x6ED9EBA1
+ elseif t <= 59 then
+ -- (B AND C) OR (B AND D) OR (C AND D
+ f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
+ K = 0x8F1BBCDC
+ else
+ -- B XOR C XOR D
+ f = w32_xor_n(B, C, D)
+ K = 0xCA62C1D6
+ end
+
+ -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
+ A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
+ A, w32_rot(30, B), C, D
+ end
+ -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
+ H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)
+ end
+ local f = w32_to_hexstring
+ return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
+end
+
+local function hex_to_binary(hex)
+ return hex:gsub('..', function(hexval)
+ return string.char(tonumber(hexval, 16))
+ end)
+end
+
+function sha1.bin(msg)
+ return hex_to_binary(sha1.hex(msg))
+end
+
+local xor_with_0x5c = {}
+local xor_with_0x36 = {}
+-- building the lookuptables ahead of time (instead of littering the source code
+-- with precalculated values)
+for i=0,0xff do
+ xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
+ xor_with_0x36[char(i)] = char(bxor(i,0x36))
+end
+
+local blocksize = 64 -- 512 bits
+
+function sha1.hmacHex(key, text)
+ assert(type(key) == 'string', "key passed to hmacHex should be a string")
+ assert(type(text) == 'string', "text passed to hmacHex should be a string")
+
+ if #key > blocksize then
+ key = sha1.bin(key)
+ end
+
+ local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key)
+ local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key)
+
+ return sha1.hex(key_xord_with_0x5c .. sha1.bin(key_xord_with_0x36 .. text))
+end
+
+function sha1.hmacBin(key, text)
+ return hex_to_binary(sha1.hmacHex(key, text))
+end
+
+return sha1
+end)()
+
+local SCRIPT_NAME = "mpv_thumbnail_script"
+
+local default_cache_base = ON_WINDOWS and os.getenv("TEMP") or "/tmp/"
+
+local thumbnailer_options = {
+ -- The thumbnail directory
+ cache_directory = join_paths(default_cache_base, "mpv_thumbs_cache"),
+
+ ------------------------
+ -- Generation options --
+ ------------------------
+
+ -- Automatically generate the thumbnails on video load, without a keypress
+ autogenerate = true,
+
+ -- Only automatically thumbnail videos shorter than this (seconds)
+ autogenerate_max_duration = 3600, -- 1 hour
+
+ -- SHA1-sum filenames over this length
+ -- It's nice to know what files the thumbnails are (hence directory names)
+ -- but long URLs may approach filesystem limits.
+ hash_filename_length = 128,
+
+ -- Use mpv to generate thumbnail even if ffmpeg is found in PATH
+ -- ffmpeg does not handle ordered chapters (MKVs which rely on other MKVs)!
+ -- mpv is a bit slower, but has better support overall (eg. subtitles in the previews)
+ prefer_mpv = true,
+
+ -- Explicitly disable subtitles on the mpv sub-calls
+ mpv_no_sub = false,
+ -- Add a "--no-config" to the mpv sub-call arguments
+ mpv_no_config = false,
+ -- Add a "--profile=<mpv_profile>" to the mpv sub-call arguments
+ -- Use "" to disable
+ mpv_profile = "",
+ -- Output debug logs to <thumbnail_path>.log, ala <cache_directory>/<video_filename>/000000.bgra.log
+ -- The logs are removed after successful encodes, unless you set mpv_keep_logs below
+ mpv_logs = true,
+ -- Keep all mpv logs, even the succesfull ones
+ mpv_keep_logs = false,
+
+ -- Disable the built-in keybind ("T") to add your own
+ disable_keybinds = false,
+
+ ---------------------
+ -- Display options --
+ ---------------------
+
+ -- Move the thumbnail up or down
+ -- For example:
+ -- topbar/bottombar: 24
+ -- rest: 0
+ vertical_offset = 24,
+
+ -- Adjust background padding
+ -- Examples:
+ -- topbar: 0, 10, 10, 10
+ -- bottombar: 10, 0, 10, 10
+ -- slimbox/box: 10, 10, 10, 10
+ pad_top = 10,
+ pad_bot = 0,
+ pad_left = 10,
+ pad_right = 10,
+
+ -- If true, pad values are screen-pixels. If false, video-pixels.
+ pad_in_screenspace = true,
+ -- Calculate pad into the offset
+ offset_by_pad = true,
+
+ -- Background color in BBGGRR
+ background_color = "000000",
+ -- Alpha: 0 - fully opaque, 255 - transparent
+ background_alpha = 80,
+
+ -- Keep thumbnail on the screen near left or right side
+ constrain_to_screen = true,
+
+ -- Do not display the thumbnailing progress
+ hide_progress = false,
+
+ -----------------------
+ -- Thumbnail options --
+ -----------------------
+
+ -- The maximum dimensions of the thumbnails (pixels)
+ thumbnail_width = 200,
+ thumbnail_height = 200,
+
+ -- The thumbnail count target
+ -- (This will result in a thumbnail every ~10 seconds for a 25 minute video)
+ thumbnail_count = 150,
+
+ -- The above target count will be adjusted by the minimum and
+ -- maximum time difference between thumbnails.
+ -- The thumbnail_count will be used to calculate a target separation,
+ -- and min/max_delta will be used to constrict it.
+
+ -- In other words, thumbnails will be:
+ -- at least min_delta seconds apart (limiting the amount)
+ -- at most max_delta seconds apart (raising the amount if needed)
+ min_delta = 5,
+ -- 120 seconds aka 2 minutes will add more thumbnails when the video is over 5 hours!
+ max_delta = 90,
+
+
+ -- Overrides for remote urls (you generally want less thumbnails!)
+ -- Thumbnailing network paths will be done with mpv
+
+ -- Allow thumbnailing network paths (naive check for "://")
+ thumbnail_network = false,
+ -- Override thumbnail count, min/max delta
+ remote_thumbnail_count = 60,
+ remote_min_delta = 15,
+ remote_max_delta = 120,
+
+ -- Try to grab the raw stream and disable ytdl for the mpv subcalls
+ -- Much faster than passing the url to ytdl again, but may cause problems with some sites
+ remote_direct_stream = true,
+}
+
+read_options(thumbnailer_options, SCRIPT_NAME)
+local Thumbnailer = {
+ cache_directory = thumbnailer_options.cache_directory,
+
+ state = {
+ ready = false,
+ available = false,
+ enabled = false,
+
+ thumbnail_template = nil,
+
+ thumbnail_delta = nil,
+ thumbnail_count = 0,
+
+ thumbnail_size = nil,
+
+ finished_thumbnails = 0,
+
+ -- List of thumbnail states (from 1 to thumbnail_count)
+ -- ready: 1
+ -- in progress: 0
+ -- not ready: -1
+ thumbnails = {},
+
+ worker_input_path = nil,
+ -- Extra options for the workers
+ worker_extra = {},
+ },
+ -- Set in register_client
+ worker_register_timeout = nil,
+ -- A timer used to wait for more workers in case we have none
+ worker_wait_timer = nil,
+ workers = {}
+}
+
+function Thumbnailer:clear_state()
+ clear_table(self.state)
+ self.state.ready = false
+ self.state.available = false
+ self.state.finished_thumbnails = 0
+ self.state.thumbnails = {}
+ self.state.worker_extra = {}
+end
+
+
+function Thumbnailer:on_file_loaded()
+ self:clear_state()
+end
+
+function Thumbnailer:on_thumb_ready(index)
+ self.state.thumbnails[index] = 1
+
+ -- Full recount instead of a naive increment (let's be safe!)
+ self.state.finished_thumbnails = 0
+ for i, v in pairs(self.state.thumbnails) do
+ if v > 0 then
+ self.state.finished_thumbnails = self.state.finished_thumbnails + 1
+ end
+ end
+end
+
+function Thumbnailer:on_thumb_progress(index)
+ self.state.thumbnails[index] = math.max(self.state.thumbnails[index], 0)
+end
+
+function Thumbnailer:on_start_file()
+ -- Clear state when a new file is being loaded
+ self:clear_state()
+end
+
+function Thumbnailer:on_video_change(params)
+ -- Gather a new state when we get proper video-dec-params and our state is empty
+ if params ~= nil then
+ if not self.state.ready then
+ self:update_state()
+ end
+ end
+end
+
+
+function Thumbnailer:update_state()
+ msg.debug("Gathering video/thumbnail state")
+
+ self.state.thumbnail_delta = self:get_delta()
+ self.state.thumbnail_count = self:get_thumbnail_count(self.state.thumbnail_delta)
+
+ -- Prefill individual thumbnail states
+ for i = 1, self.state.thumbnail_count do
+ self.state.thumbnails[i] = -1
+ end
+
+ self.state.thumbnail_template, self.state.thumbnail_directory = self:get_thumbnail_template()
+ self.state.thumbnail_size = self:get_thumbnail_size()
+
+ self.state.ready = true
+
+ local file_path = mp.get_property_native("path")
+ self.state.is_remote = file_path:find("://") ~= nil
+
+ self.state.available = false
+
+ -- Make sure the file has video (and not just albumart)
+ local track_list = mp.get_property_native("track-list")
+ local has_video = false
+ for i, track in pairs(track_list) do
+ if track.type == "video" and not track.external and not track.albumart then
+ has_video = true
+ break
+ end
+ end
+
+ if has_video and self.state.thumbnail_delta ~= nil and self.state.thumbnail_size ~= nil and self.state.thumbnail_count > 0 then
+ self.state.available = true
+ end
+
+ msg.debug("Thumbnailer.state:", utils.to_string(self.state))
+
+end
+
+
+function Thumbnailer:get_thumbnail_template()
+ local file_path = mp.get_property_native("path")
+ local is_remote = file_path:find("://") ~= nil
+
+ local filename = mp.get_property_native("filename/no-ext")
+ local filesize = mp.get_property_native("file-size", 0)
+
+ if is_remote then
+ filesize = 0
+ end
+
+ filename = filename:gsub('[^a-zA-Z0-9_.%-\' ]', '')
+ -- Hash overly long filenames (most likely URLs)
+ if #filename > thumbnailer_options.hash_filename_length then
+ filename = sha1.hex(filename)
+ end
+
+ local file_key = ("%s-%d"):format(filename, filesize)
+
+ local thumbnail_directory = join_paths(self.cache_directory, file_key)
+ local file_template = join_paths(thumbnail_directory, "%06d.bgra")
+ return file_template, thumbnail_directory
+end
+
+
+function Thumbnailer:get_thumbnail_size()
+ local video_dec_params = mp.get_property_native("video-dec-params")
+ local video_width = video_dec_params.dw
+ local video_height = video_dec_params.dh
+ if not (video_width and video_height) then
+ return nil
+ end
+
+ local w, h
+ if video_width > video_height then
+ w = thumbnailer_options.thumbnail_width
+ h = math.floor(video_height * (w / video_width))
+ else
+ h = thumbnailer_options.thumbnail_height
+ w = math.floor(video_width * (h / video_height))
+ end
+ return { w=w, h=h }
+end
+
+
+function Thumbnailer:get_delta()
+ local file_path = mp.get_property_native("path")
+ local file_duration = mp.get_property_native("duration")
+ local is_seekable = mp.get_property_native("seekable")
+
+ -- Naive url check
+ local is_remote = file_path:find("://") ~= nil
+
+ local remote_and_disallowed = is_remote
+ if is_remote and thumbnailer_options.thumbnail_network then
+ remote_and_disallowed = false
+ end
+
+ if remote_and_disallowed or not is_seekable or not file_duration then
+ -- Not a local path (or remote thumbnails allowed), not seekable or lacks duration
+ return nil
+ end
+
+ local thumbnail_count = thumbnailer_options.thumbnail_count
+ local min_delta = thumbnailer_options.min_delta
+ local max_delta = thumbnailer_options.max_delta
+
+ if is_remote then
+ thumbnail_count = thumbnailer_options.remote_thumbnail_count
+ min_delta = thumbnailer_options.remote_min_delta
+ max_delta = thumbnailer_options.remote_max_delta
+ end
+
+ local target_delta = (file_duration / thumbnail_count)
+ local delta = math.max(min_delta, math.min(max_delta, target_delta))
+
+ return delta
+end
+
+
+function Thumbnailer:get_thumbnail_count(delta)
+ if delta == nil then
+ return 0
+ end
+ local file_duration = mp.get_property_native("duration")
+
+ return math.ceil(file_duration / delta)
+end
+
+function Thumbnailer:get_closest(thumbnail_index)
+ -- Given a 1-based index, find the closest available thumbnail and return it's 1-based index
+
+ -- Check the direct thumbnail index first
+ if self.state.thumbnails[thumbnail_index] > 0 then
+ return thumbnail_index
+ end
+
+ local min_distance = self.state.thumbnail_count + 1
+ local closest = nil
+
+ -- Naive, inefficient, lazy. But functional.
+ for index, value in pairs(self.state.thumbnails) do
+ local distance = math.abs(index - thumbnail_index)
+ if distance < min_distance and value > 0 then
+ min_distance = distance
+ closest = index
+ end
+ end
+ return closest
+end
+
+function Thumbnailer:get_thumbnail_index(time_position)
+ -- Returns a 1-based thumbnail index for the given timestamp (between 1 and thumbnail_count, inclusive)
+ if self.state.thumbnail_delta and (self.state.thumbnail_count and self.state.thumbnail_count > 0) then
+ return math.min(math.floor(time_position / self.state.thumbnail_delta) + 1, self.state.thumbnail_count)
+ else
+ return nil
+ end
+end
+
+function Thumbnailer:get_thumbnail_path(time_position)
+ -- Given a timestamp, return:
+ -- the closest available thumbnail path (if any)
+ -- the 1-based thumbnail index calculated from the timestamp
+ -- the 1-based thumbnail index of the closest available (and used) thumbnail
+ -- OR nil if thumbnails are not available.
+
+ local thumbnail_index = self:get_thumbnail_index(time_position)
+ if not thumbnail_index then return nil end
+
+ local closest = self:get_closest(thumbnail_index)
+
+ if closest ~= nil then
+ return self.state.thumbnail_template:format(closest-1), thumbnail_index, closest
+ else
+ return nil, thumbnail_index, nil
+ end
+end
+
+function Thumbnailer:register_client()
+ self.worker_register_timeout = mp.get_time() + 2
+
+ mp.register_script_message("mpv_thumbnail_script-ready", function(index, path)
+ self:on_thumb_ready(tonumber(index), path)
+ end)
+ mp.register_script_message("mpv_thumbnail_script-progress", function(index, path)
+ self:on_thumb_progress(tonumber(index), path)
+ end)
+
+ mp.register_script_message("mpv_thumbnail_script-worker", function(worker_name)
+ if not self.workers[worker_name] then
+ msg.debug("Registered worker", worker_name)
+ self.workers[worker_name] = true
+ mp.commandv("script-message-to", worker_name, "mpv_thumbnail_script-slaved")
+ end
+ end)
+
+ -- Notify workers to generate thumbnails when video loads/changes
+ -- This will be executed after the on_video_change (because it's registered after it)
+ mp.observe_property("video-dec-params", "native", function()
+ local duration = mp.get_property_native("duration")
+ local max_duration = thumbnailer_options.autogenerate_max_duration
+
+ if self.state.available and thumbnailer_options.autogenerate then
+ -- Notify if autogenerate is on and video is not too long
+ if duration < max_duration or max_duration == 0 then
+ self:start_worker_jobs()
+ end
+ end
+ end)
+
+ local thumb_script_key = not thumbnailer_options.disable_keybinds and "T" or nil
+ mp.add_key_binding(thumb_script_key, "generate-thumbnails", function()
+ if self.state.available then
+ mp.osd_message("Started thumbnailer jobs")
+ self:start_worker_jobs()
+ else
+ mp.osd_message("Thumbnailing unavailabe")
+ end
+ end)
+end
+
+function Thumbnailer:_create_thumbnail_job_order()
+ -- Returns a list of 1-based thumbnail indices in a job order
+ local used_frames = {}
+ local work_frames = {}
+
+ -- Pick frames in increasing frequency.
+ -- This way we can do a quick few passes over the video and then fill in the gaps.
+ for x = 6, 0, -1 do
+ local nth = (2^x)
+
+ for thi = 1, self.state.thumbnail_count, nth do
+ if not used_frames[thi] then
+ table.insert(work_frames, thi)
+ used_frames[thi] = true
+ end
+ end
+ end
+ return work_frames
+end
+
+function Thumbnailer:prepare_source_path()
+ local file_path = mp.get_property_native("path")
+
+ if self.state.is_remote and thumbnailer_options.remote_direct_stream then
+ -- Use the direct stream (possibly) provided by ytdl
+ -- This skips ytdl on the sub-calls, making the thumbnailing faster
+ -- Works well on YouTube, rest not really tested
+ file_path = mp.get_property_native("stream-path")
+
+ -- edl:// urls can get LONG. In which case, save the path (URL)
+ -- to a temporary file and use that instead.
+ local playlist_filename = join_paths(self.state.thumbnail_directory, "playlist.txt")
+
+ if #file_path > 8000 then
+ -- Path is too long for a playlist - just pass the original URL to
+ -- workers and allow ytdl
+ self.state.worker_extra.enable_ytdl = true
+ file_path = mp.get_property_native("path")
+ msg.warn("Falling back to original URL and ytdl due to LONG source path. This will be slow.")
+
+ elseif #file_path > 1024 then
+ local playlist_file = io.open(playlist_filename, "wb")
+ if not playlist_file then
+ msg.error(("Tried to write a playlist to %s but couldn't!"):format(playlist_file))
+ return false
+ end
+
+ playlist_file:write(file_path .. "\n")
+ playlist_file:close()
+
+ file_path = "--playlist=" .. playlist_filename
+ msg.warn("Using playlist workaround due to long source path")
+ end
+ end
+
+ self.state.worker_input_path = file_path
+ return true
+end
+
+function Thumbnailer:start_worker_jobs()
+ -- Create directory for the thumbnails, if needed
+ local l, err = utils.readdir(self.state.thumbnail_directory)
+ if err then
+ msg.debug("Creating thumbnail directory", self.state.thumbnail_directory)
+ create_directories(self.state.thumbnail_directory)
+ end
+
+ -- Try to prepare the source path for workers, and bail if unable to do so
+ if not self:prepare_source_path() then
+ return
+ end
+
+ local worker_list = {}
+ for worker_name in pairs(self.workers) do table.insert(worker_list, worker_name) end
+
+ local worker_count = #worker_list
+
+ -- In case we have a worker timer created already, clear it
+ -- (For example, if the video-dec-params change in quick succession or the user pressed T, etc)
+ if self.worker_wait_timer then
+ self.worker_wait_timer:stop()
+ end
+
+ if worker_count == 0 then
+ local now = mp.get_time()
+ if mp.get_time() > self.worker_register_timeout then
+ -- Workers have had their time to register but we have none!
+ local err = "No thumbnail workers found. Make sure you are not missing a script!"
+ msg.error(err)
+ mp.osd_message(err, 3)
+
+ else
+ -- We may be too early. Delay the work start a bit to try again.
+ msg.warn("No workers found. Waiting a bit more for them.")
+ -- Wait at least half a second
+ local wait_time = math.max(self.worker_register_timeout - now, 0.5)
+ self.worker_wait_timer = mp.add_timeout(wait_time, function() self:start_worker_jobs() end)
+ end
+
+ else
+ -- We have at least one worker. This may not be all of them, but they have had
+ -- their time to register; we've done our best waiting for them.
+ self.state.enabled = true
+
+ msg.debug( ("Splitting %d thumbnails amongst %d worker(s)"):format(self.state.thumbnail_count, worker_count) )
+
+ local frame_job_order = self:_create_thumbnail_job_order()
+ local worker_jobs = {}
+ for i = 1, worker_count do worker_jobs[worker_list[i]] = {} end
+
+ -- Split frames amongst the workers
+ for i, thumbnail_index in ipairs(frame_job_order) do
+ local worker_id = worker_list[ ((i-1) % worker_count) + 1 ]
+ table.insert(worker_jobs[worker_id], thumbnail_index)
+ end
+
+ local state_json_string = utils.format_json(self.state)
+ msg.debug("Giving workers state:", state_json_string)
+
+ for worker_name, worker_frames in pairs(worker_jobs) do
+ if #worker_frames > 0 then
+ local frames_json_string = utils.format_json(worker_frames)
+ msg.debug("Assigning job to", worker_name, frames_json_string)
+ mp.commandv("script-message-to", worker_name, "mpv_thumbnail_script-job", state_json_string, frames_json_string)
+ end
+ end
+ end
+end
+
+mp.register_event("start-file", function() Thumbnailer:on_start_file() end)
+mp.observe_property("video-dec-params", "native", function(name, params) Thumbnailer:on_video_change(params) end)
+--[[
+This is mpv's original player/lua/osc.lua patched to display thumbnails
+
+Sections are denoted with -- mpv_thumbnail_script.lua --
+Current osc.lua version: 97816bbef0f97cfda7abdbe560707481d5f68ccd
+]]--
+
+local assdraw = require 'mp.assdraw'
+local msg = require 'mp.msg'
+local opt = require 'mp.options'
+local utils = require 'mp.utils'
+
+
+--
+-- Parameters
+--
+
+-- default user option values
+-- do not touch, change them in osc.conf
+local user_opts = {
+ showwindowed = true, -- show OSC when windowed?
+ showfullscreen = true, -- show OSC when fullscreen?
+ scalewindowed = 1, -- scaling of the controller when windowed
+ scalefullscreen = 1, -- scaling of the controller when fullscreen
+ scaleforcedwindow = 2, -- scaling when rendered on a forced window
+ vidscale = true, -- scale the controller with the video?
+ valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom)
+ halign = 0, -- horizontal alignment, -1 (left) to 1 (right)
+ barmargin = 0, -- vertical margin of top/bottombar
+ boxalpha = 80, -- alpha of the background box,
+ -- 0 (opaque) to 255 (fully transparent)
+ hidetimeout = 500, -- duration in ms until the OSC hides if no
+ -- mouse movement. enforced non-negative for the
+ -- user, but internally negative is "always-on".
+ fadeduration = 200, -- duration of fade out in ms, 0 = no fade
+ deadzonesize = 0.5, -- size of deadzone
+ minmousemove = 0, -- minimum amount of pixels the mouse has to
+ -- move between ticks to make the OSC show up
+ iamaprogrammer = false, -- use native mpv values and disable OSC
+ -- internal track list management (and some
+ -- functions that depend on it)
+ layout = "bottombar",
+ seekbarstyle = "bar", -- slider (diamond marker), knob (circle
+ -- marker with guide), or bar (fill)
+ title = "${media-title}", -- string compatible with property-expansion
+ -- to be shown as OSC title
+ tooltipborder = 1, -- border of tooltip in bottom/topbar
+ timetotal = false, -- display total time instead of remaining time?
+ timems = false, -- display timecodes with milliseconds?
+ seekranges = true, -- display seek ranges?
+ visibility = "auto", -- only used at init to set visibility_mode(...)
+ boxmaxchars = 80, -- title crop threshold for box layout
+}
+
+-- read_options may modify hidetimeout, so save the original default value in
+-- case the user set hidetimeout < 0 and we need the default instead.
+local hidetimeout_def = user_opts.hidetimeout
+-- read options from config and command-line
+opt.read_options(user_opts, "osc")
+if user_opts.hidetimeout < 0 then
+ user_opts.hidetimeout = hidetimeout_def
+ msg.warn("hidetimeout cannot be negative. Using " .. user_opts.hidetimeout)
+end
+
+
+-- mpv_thumbnail_script.lua --
+
+-- Patch in msg.trace
+if not msg.trace then
+ msg.trace = function(...) return mp.log("trace", ...) end
+end
+
+-- Patch in utils.format_bytes_humanized
+if not utils.format_bytes_humanized then
+ utils.format_bytes_humanized = function(b)
+ local d = {"Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"}
+ local i = 1
+ while b >= 1024 do
+ b = b / 1024
+ i = i + 1
+ end
+ return string.format("%0.2f %s", b, d[i] and d[i] or "*1024^" .. (i-1))
+ end
+end
+
+Thumbnailer:register_client()
+
+function get_thumbnail_y_offset(thumb_size, msy)
+ local layout = user_opts.layout
+ local offset = 0
+
+ if layout == "bottombar" then
+ offset = 15 --+ margin
+ elseif layout == "topbar" then
+ offset = -(thumb_size.h * msy + 15)
+ elseif layout == "box" then
+ offset = 15
+ elseif layout == "slimbox" then
+ offset = 12
+ end
+
+ return offset / msy
+end
+
+
+local osc_thumb_state = {
+ visible = false,
+ overlay_id = 1,
+
+ last_path = nil,
+ last_x = nil,
+ last_y = nil,
+}
+
+function hide_thumbnail()
+ osc_thumb_state.visible = false
+ osc_thumb_state.last_path = nil
+ mp.command_native({ "overlay-remove", osc_thumb_state.overlay_id })
+end
+
+function display_thumbnail(pos, value, ass)
+ -- If thumbnails are not available, bail
+ if not (Thumbnailer.state.enabled and Thumbnailer.state.available) then
+ return
+ end
+
+ local duration = mp.get_property_number("duration", nil)
+ if not ((duration == nil) or (value == nil)) then
+ target_position = duration * (value / 100)
+
+ local msx, msy = get_virt_scale_factor()
+ local osd_w, osd_h = mp.get_osd_size()
+
+ local thumb_size = Thumbnailer.state.thumbnail_size
+ local thumb_path, thumb_index, closest_index = Thumbnailer:get_thumbnail_path(target_position)
+
+ local thumbs_ready = Thumbnailer.state.finished_thumbnails
+ local thumbs_total = Thumbnailer.state.thumbnail_count
+ local perc = math.floor((thumbs_ready / thumbs_total) * 100)
+
+ local display_progress = thumbs_ready ~= thumbs_total and not thumbnailer_options.hide_progress
+
+ local vertical_offset = thumbnailer_options.vertical_offset
+ local padding = thumbnailer_options.background_padding
+
+ local pad = {
+ l = thumbnailer_options.pad_left, r = thumbnailer_options.pad_right,
+ t = thumbnailer_options.pad_top, b = thumbnailer_options.pad_bot
+ }
+ if thumbnailer_options.pad_in_screenspace then
+ pad.l = pad.l * msx
+ pad.r = pad.r * msx
+ pad.t = pad.t * msy
+ pad.b = pad.b * msy
+ end
+
+ if thumbnailer_options.offset_by_pad then
+ vertical_offset = vertical_offset + (user_opts.layout == "topbar" and pad.t or pad.b)
+ end
+
+ local ass_w = thumb_size.w * msx
+ local ass_h = thumb_size.h * msy
+ local y_offset = get_thumbnail_y_offset(thumb_size, 1)
+
+ -- Constrain thumbnail display to window
+ -- (ie. don't let it go off-screen left/right)
+ if thumbnailer_options.constrain_to_screen and osd_w > (ass_w + pad.l + pad.r)/msx then
+ local padded_left = (pad.l + (ass_w / 2))
+ local padded_right = (pad.r + (ass_w / 2))
+ if pos.x - padded_left < 0 then
+ pos.x = padded_left
+ elseif pos.x + padded_right > osd_w*msx then
+ pos.x = osd_w*msx - padded_right
+ end
+ end
+
+ local text_h = 30 * msy
+ local bg_h = ass_h + (display_progress and text_h or 0)
+ local bg_left = pos.x - ass_w/2
+ local framegraph_h = 10 * msy
+
+ local bg_top = nil
+ local text_top = nil
+ local framegraph_top = nil
+
+ if user_opts.layout == "topbar" then
+ bg_top = pos.y - ( y_offset + thumb_size.h ) + vertical_offset
+ text_top = bg_top + ass_h + framegraph_h
+ framegraph_top = bg_top + ass_h
+ vertical_offset = -vertical_offset
+ else
+ bg_top = pos.y - y_offset - bg_h - vertical_offset
+ text_top = bg_top
+ framegraph_top = bg_top + 20 * msy
+ end
+
+ if display_progress then
+ if user_opts.layout == "topbar" then
+ pad.b = math.max(0, pad.b - 30)
+ else
+ pad.t = math.max(0, pad.t - 30)
+ end
+ end
+
+
+
+ -- Draw background
+ ass:new_event()
+ ass:pos(bg_left, bg_top)
+ ass:append(("{\\bord0\\1c&H%s&\\1a&H%X&}"):format(thumbnailer_options.background_color, thumbnailer_options.background_alpha))
+ ass:draw_start()
+ ass:rect_cw(-pad.l, -pad.t, ass_w+pad.r, bg_h+pad.b)
+ ass:draw_stop()
+
+ if display_progress then
+
+ ass:new_event()
+ ass:pos(pos.x, text_top)
+ ass:an(8)
+ -- Scale text to correct size
+ ass:append(("{\\fs20\\bord0\\fscx%f\\fscy%f}"):format(100*msx, 100*msy))
+ ass:append(("%d%% - %d/%d"):format(perc, thumbs_ready, thumbs_total))
+
+ -- Draw the generation progress
+ local block_w = thumb_size.w * (Thumbnailer.state.thumbnail_delta / duration) * msy
+ local block_max_x = thumb_size.w * msy
+
+ -- Draw finished thumbnail blocks (white)
+ ass:new_event()
+ ass:pos(bg_left, framegraph_top)
+ ass:append(("{\\bord0\\1c&HFFFFFF&\\1a&H%X&"):format(0))
+ ass:draw_start(2)
+ for i, v in pairs(Thumbnailer.state.thumbnails) do
+ if i ~= closest_index and v > 0 then
+ ass:rect_cw((i-1)*block_w, 0, math.min(block_max_x, i*block_w), framegraph_h)
+ end
+ end
+ ass:draw_stop()
+
+ -- Draw in-progress thumbnail blocks (grayish green)
+ ass:new_event()
+ ass:pos(bg_left, framegraph_top)
+ ass:append(("{\\bord0\\1c&H44AA44&\\1a&H%X&"):format(0))
+ ass:draw_start(2)
+ for i, v in pairs(Thumbnailer.state.thumbnails) do
+ if i ~= closest_index and v == 0 then
+ ass:rect_cw((i-1)*block_w, 0, math.min(block_max_x, i*block_w), framegraph_h)
+ end
+ end
+ ass:draw_stop()
+
+ if closest_index ~= nil then
+ ass:new_event()
+ ass:pos(bg_left, framegraph_top)
+ ass:append(("{\\bord0\\1c&H4444FF&\\1a&H%X&"):format(0))
+ ass:draw_start(2)
+ ass:rect_cw((closest_index-1)*block_w, 0, math.min(block_max_x, closest_index*block_w), framegraph_h)
+ ass:draw_stop()
+ end
+ end
+
+ if thumb_path then
+ local overlay_y_offset = get_thumbnail_y_offset(thumb_size, msy)
+
+ local thumb_x = math.floor(pos.x / msx - thumb_size.w/2)
+ local thumb_y = math.floor(pos.y / msy - thumb_size.h - overlay_y_offset - vertical_offset/msy)
+
+ osc_thumb_state.visible = true
+ if not (osc_thumb_state.last_path == thumb_path and osc_thumb_state.last_x == thumb_x and osc_thumb_state.last_y == thumb_y) then
+ local overlay_add_args = {
+ "overlay-add", osc_thumb_state.overlay_id,
+ thumb_x, thumb_y,
+ thumb_path,
+ 0,
+ "bgra",
+ thumb_size.w, thumb_size.h,
+ 4 * thumb_size.w
+ }
+ mp.command_native(overlay_add_args)
+
+ osc_thumb_state.last_path = thumb_path
+ osc_thumb_state.last_x = thumb_x
+ osc_thumb_state.last_y = thumb_y
+ end
+ end
+ end
+end
+
+-- // mpv_thumbnail_script.lua // --
+
+
+local osc_param = { -- calculated by osc_init()
+ playresy = 0, -- canvas size Y
+ playresx = 0, -- canvas size X
+ display_aspect = 1,
+ unscaled_y = 0,
+ areas = {},
+}
+
+local osc_styles = {
+ bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}",
+ smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}",
+ smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" .. mp.get_property("options/osd-font") .. "}",
+ smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}",
+ topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}",
+
+ elementDown = "{\\1c&H999999}",
+ timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}",
+ vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\q2}",
+ box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
+
+ topButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}",
+ smallButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs28\\fnmpv-osd-symbols}",
+ timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}",
+ timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}",
+ vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}",
+}
+
+-- internal states, do not touch
+local state = {
+ showtime, -- time of last invocation (last mouse move)
+ osc_visible = false,
+ anistart, -- time when the animation started
+ anitype, -- current type of animation
+ animation, -- current animation alpha
+ mouse_down_counter = 0, -- used for softrepeat
+ active_element = nil, -- nil = none, 0 = background, 1+ = see elements[]
+ active_event_source = nil, -- the "button" that issued the current event
+ rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
+ tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
+ mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
+ initREQ = false, -- is a re-init request pending?
+ last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement
+ message_text,
+ message_timeout,
+ fullscreen = false,
+ timer = nil,
+ cache_idle = false,
+ idle = false,
+ enabled = true,
+ input_enabled = true,
+ showhide_enabled = false,
+}
+
+
+
+
+--
+-- Helperfunctions
+--
+
+-- scale factor for translating between real and virtual ASS coordinates
+function get_virt_scale_factor()
+ local w, h = mp.get_osd_size()
+ if w <= 0 or h <= 0 then
+ return 0, 0
+ end
+ return osc_param.playresx / w, osc_param.playresy / h
+end
+
+-- return mouse position in virtual ASS coordinates (playresx/y)
+function get_virt_mouse_pos()
+ local sx, sy = get_virt_scale_factor()
+ local x, y = mp.get_mouse_pos()
+ return x * sx, y * sy
+end
+
+function set_virt_mouse_area(x0, y0, x1, y1, name)
+ local sx, sy = get_virt_scale_factor()
+ mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name)
+end
+
+function scale_value(x0, x1, y0, y1, val)
+ local m = (y1 - y0) / (x1 - x0)
+ local b = y0 - (m * x0)
+ return (m * val) + b
+end
+
+-- returns hitbox spanning coordinates (top left, bottom right corner)
+-- according to alignment
+function get_hitbox_coords(x, y, an, w, h)
+
+ local alignments = {
+ [1] = function () return x, y-h, x+w, y end,
+ [2] = function () return x-(w/2), y-h, x+(w/2), y end,
+ [3] = function () return x-w, y-h, x, y end,
+
+ [4] = function () return x, y-(h/2), x+w, y+(h/2) end,
+ [5] = function () return x-(w/2), y-(h/2), x+(w/2), y+(h/2) end,
+ [6] = function () return x-w, y-(h/2), x, y+(h/2) end,
+
+ [7] = function () return x, y, x+w, y+h end,
+ [8] = function () return x-(w/2), y, x+(w/2), y+h end,
+ [9] = function () return x-w, y, x, y+h end,
+ }
+
+ return alignments[an]()
+end
+
+function get_hitbox_coords_geo(geometry)
+ return get_hitbox_coords(geometry.x, geometry.y, geometry.an,
+ geometry.w, geometry.h)
+end
+
+function get_element_hitbox(element)
+ return element.hitbox.x1, element.hitbox.y1,
+ element.hitbox.x2, element.hitbox.y2
+end
+
+function mouse_hit(element)
+ return mouse_hit_coords(get_element_hitbox(element))
+end
+
+function mouse_hit_coords(bX1, bY1, bX2, bY2)
+ local mX, mY = get_virt_mouse_pos()
+ return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
+end
+
+function limit_range(min, max, val)
+ if val > max then
+ val = max
+ elseif val < min then
+ val = min
+ end
+ return val
+end
+
+-- translate value into element coordinates
+function get_slider_ele_pos_for(element, val)
+
+ local ele_pos = scale_value(
+ element.slider.min.value, element.slider.max.value,
+ element.slider.min.ele_pos, element.slider.max.ele_pos,
+ val)
+
+ return limit_range(
+ element.slider.min.ele_pos, element.slider.max.ele_pos,
+ ele_pos)
+end
+
+-- translates global (mouse) coordinates to value
+function get_slider_value_at(element, glob_pos)
+
+ local val = scale_value(
+ element.slider.min.glob_pos, element.slider.max.glob_pos,
+ element.slider.min.value, element.slider.max.value,
+ glob_pos)
+
+ return limit_range(
+ element.slider.min.value, element.slider.max.value,
+ val)
+end
+
+-- get value at current mouse position
+function get_slider_value(element)
+ return get_slider_value_at(element, get_virt_mouse_pos())
+end
+
+function countone(val)
+ if not (user_opts.iamaprogrammer) then
+ val = val + 1
+ end
+ return val
+end
+
+-- align: -1 .. +1
+-- frame: size of the containing area
+-- obj: size of the object that should be positioned inside the area
+-- margin: min. distance from object to frame (as long as -1 <= align <= +1)
+function get_align(align, frame, obj, margin)
+ return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align)
+end
+
+-- multiplies two alpha values, formular can probably be improved
+function mult_alpha(alphaA, alphaB)
+ return 255 - (((1-(alphaA/255)) * (1-(alphaB/255))) * 255)
+end
+
+function add_area(name, x1, y1, x2, y2)
+ -- create area if needed
+ if (osc_param.areas[name] == nil) then
+ osc_param.areas[name] = {}
+ end
+ table.insert(osc_param.areas[name], {x1=x1, y1=y1, x2=x2, y2=y2})
+end
+
+
+--
+-- Tracklist Management
+--
+
+local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
+
+-- updates the OSC internal playlists, should be run each time the track-layout changes
+function update_tracklist()
+ local tracktable = mp.get_property_native("track-list", {})
+
+ -- by osc_id
+ tracks_osc = {}
+ tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {}
+ -- by mpv_id
+ tracks_mpv = {}
+ tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {}
+ for n = 1, #tracktable do
+ if not (tracktable[n].type == "unknown") then
+ local type = tracktable[n].type
+ local mpv_id = tonumber(tracktable[n].id)
+
+ -- by osc_id
+ table.insert(tracks_osc[type], tracktable[n])
+
+ -- by mpv_id
+ tracks_mpv[type][mpv_id] = tracktable[n]
+ tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type]
+ end
+ end
+end
+
+-- return a nice list of tracks of the given type (video, audio, sub)
+function get_tracklist(type)
+ local msg = "Available " .. nicetypes[type] .. " Tracks: "
+ if #tracks_osc[type] == 0 then
+ msg = msg .. "none"
+ else
+ for n = 1, #tracks_osc[type] do
+ local track = tracks_osc[type][n]
+ local lang, title, selected = "unknown", "", "â—‹"
+ if not(track.lang == nil) then lang = track.lang end
+ if not(track.title == nil) then title = track.title end
+ if (track.id == tonumber(mp.get_property(type))) then
+ selected = "â—"
+ end
+ msg = msg.."\n"..selected.." "..n..": ["..lang.."] "..title
+ end
+ end
+ return msg
+end
+
+-- relatively change the track of given <type> by <next> tracks
+ --(+1 -> next, -1 -> previous)
+function set_track(type, next)
+ local current_track_mpv, current_track_osc
+ if (mp.get_property(type) == "no") then
+ current_track_osc = 0
+ else
+ current_track_mpv = tonumber(mp.get_property(type))
+ current_track_osc = tracks_mpv[type][current_track_mpv].osc_id
+ end
+ local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1)
+ local new_track_mpv
+ if new_track_osc == 0 then
+ new_track_mpv = "no"
+ else
+ new_track_mpv = tracks_osc[type][new_track_osc].id
+ end
+
+ mp.commandv("set", type, new_track_mpv)
+
+ if (new_track_osc == 0) then
+ show_message(nicetypes[type] .. " Track: none")
+ else
+ show_message(nicetypes[type] .. " Track: "
+ .. new_track_osc .. "/" .. #tracks_osc[type]
+ .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] "
+ .. (tracks_osc[type][new_track_osc].title or ""))
+ end
+end
+
+-- get the currently selected track of <type>, OSC-style counted
+function get_track(type)
+ local track = mp.get_property(type)
+ if track ~= "no" and track ~= nil then
+ local tr = tracks_mpv[type][tonumber(track)]
+ if tr then
+ return tr.osc_id
+ end
+ end
+ return 0
+end
+
+
+--
+-- Element Management
+--
+
+local elements = {}
+
+function prepare_elements()
+
+ -- remove elements without layout or invisble
+ local elements2 = {}
+ for n, element in pairs(elements) do
+ if not (element.layout == nil) and (element.visible) then
+ table.insert(elements2, element)
+ end
+ end
+ elements = elements2
+
+ function elem_compare (a, b)
+ return a.layout.layer < b.layout.layer
+ end
+
+ table.sort(elements, elem_compare)
+
+
+ for _,element in pairs(elements) do
+
+ local elem_geo = element.layout.geometry
+
+ -- Calculate the hitbox
+ local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo)
+ element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2}
+
+ local style_ass = assdraw.ass_new()
+
+ -- prepare static elements
+ style_ass:append("{}") -- hack to troll new_event into inserting a \n
+ style_ass:new_event()
+ style_ass:pos(elem_geo.x, elem_geo.y)
+ style_ass:an(elem_geo.an)
+ style_ass:append(element.layout.style)
+
+ element.style_ass = style_ass
+
+ local static_ass = assdraw.ass_new()
+
+
+ if (element.type == "box") then
+ --draw box
+ static_ass:draw_start()
+ static_ass:round_rect_cw(0, 0, elem_geo.w, elem_geo.h,
+ element.layout.box.radius)
+ static_ass:draw_stop()
+
+
+ elseif (element.type == "slider") then
+ --draw static slider parts
+
+ local slider_lo = element.layout.slider
+ -- offset between element outline and drag-area
+ local foV = slider_lo.border + slider_lo.gap
+
+ -- calculate positions of min and max points
+ if (slider_lo.stype == "slider") or
+ (slider_lo.stype == "knob") then
+ element.slider.min.ele_pos = elem_geo.h / 2
+ element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
+
+ elseif (slider_lo.stype == "bar") then
+ element.slider.min.ele_pos =
+ slider_lo.border + slider_lo.gap
+ element.slider.max.ele_pos =
+ elem_geo.w - (slider_lo.border + slider_lo.gap)
+ end
+
+ element.slider.min.glob_pos =
+ element.hitbox.x1 + element.slider.min.ele_pos
+ element.slider.max.glob_pos =
+ element.hitbox.x1 + element.slider.max.ele_pos
+
+ -- -- --
+
+ static_ass:draw_start()
+
+ -- the box
+ static_ass:rect_cw(0, 0, elem_geo.w, elem_geo.h);
+
+ -- the "hole"
+ static_ass:rect_ccw(slider_lo.border, slider_lo.border,
+ elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border)
+
+ -- marker nibbles
+ if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then
+ local markers = element.slider.markerF()
+ for _,marker in pairs(markers) do
+ if (marker > element.slider.min.value) and
+ (marker < element.slider.max.value) then
+
+ local s = get_slider_ele_pos_for(element, marker)
+
+ if (slider_lo.gap > 1) then -- draw triangles
+
+ local a = slider_lo.gap / 0.5 --0.866
+
+ --top
+ if (slider_lo.nibbles_top) then
+ static_ass:move_to(s - (a/2), slider_lo.border)
+ static_ass:line_to(s + (a/2), slider_lo.border)
+ static_ass:line_to(s, foV)
+ end
+
+ --bottom
+ if (slider_lo.nibbles_bottom) then
+ static_ass:move_to(s - (a/2),
+ elem_geo.h - slider_lo.border)
+ static_ass:line_to(s,
+ elem_geo.h - foV)
+ static_ass:line_to(s + (a/2),
+ elem_geo.h - slider_lo.border)
+ end
+
+ else -- draw 2x1px nibbles
+
+ --top
+ if (slider_lo.nibbles_top) then
+ static_ass:rect_cw(s - 1, slider_lo.border,
+ s + 1, slider_lo.border + slider_lo.gap);
+ end
+
+ --bottom
+ if (slider_lo.nibbles_bottom) then
+ static_ass:rect_cw(s - 1,
+ elem_geo.h -slider_lo.border -slider_lo.gap,
+ s + 1, elem_geo.h - slider_lo.border);
+ end
+ end
+ end
+ end
+ end
+ end
+
+ element.static_ass = static_ass
+
+
+ -- if the element is supposed to be disabled,
+ -- style it accordingly and kill the eventresponders
+ if not (element.enabled) then
+ element.layout.alpha[1] = 136
+ element.eventresponder = nil
+ end
+ end
+end
+
+
+--
+-- Element Rendering
+--
+
+function render_elements(master_ass)
+
+ for n=1, #elements do
+ local element = elements[n]
+
+ local style_ass = assdraw.ass_new()
+ style_ass:merge(element.style_ass)
+
+ --alpha
+ local ar = element.layout.alpha
+ if not (state.animation == nil) then
+ ar = {}
+ for ai, av in pairs(element.layout.alpha) do
+ ar[ai] = mult_alpha(av, state.animation)
+ end
+ end
+
+ style_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
+ ar[1], ar[2], ar[3], ar[4]))
+
+ if element.eventresponder and (state.active_element == n) then
+
+ -- run render event functions
+ if not (element.eventresponder.render == nil) then
+ element.eventresponder.render(element)
+ end
+
+ if mouse_hit(element) then
+ -- mouse down styling
+ if (element.styledown) then
+ style_ass:append(osc_styles.elementDown)
+ end
+
+ if (element.softrepeat) and (state.mouse_down_counter >= 15
+ and state.mouse_down_counter % 5 == 0) then
+
+ element.eventresponder[state.active_event_source.."_down"](element)
+ end
+ state.mouse_down_counter = state.mouse_down_counter + 1
+ end
+
+ end
+
+ local elem_ass = assdraw.ass_new()
+
+ elem_ass:merge(style_ass)
+
+ if not (element.type == "button") then
+ elem_ass:merge(element.static_ass)
+ end
+
+ if (element.type == "slider") then
+
+ local slider_lo = element.layout.slider
+ local elem_geo = element.layout.geometry
+ local s_min = element.slider.min.value
+ local s_max = element.slider.max.value
+
+ -- draw pos marker
+ local pos = element.slider.posF()
+
+ if not (pos == nil) then
+
+ local foV = slider_lo.border + slider_lo.gap
+ local foH = 0
+ if (slider_lo.stype == "slider") or
+ (slider_lo.stype == "knob") then
+ foH = elem_geo.h / 2
+ elseif (slider_lo.stype == "bar") then
+ foH = slider_lo.border + slider_lo.gap
+ end
+
+ local xp = get_slider_ele_pos_for(element, pos)
+
+ -- the filling
+ local innerH = elem_geo.h - (2*foV)
+
+ if (slider_lo.stype == "bar") then
+ elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV)
+ elseif (slider_lo.stype == "slider") then
+ elem_ass:move_to(xp, foV)
+ elem_ass:line_to(xp+(innerH/2), (innerH/2)+foV)
+ elem_ass:line_to(xp, (innerH)+foV)
+ elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV)
+ elseif (slider_lo.stype == "knob") then
+ elem_ass:rect_cw(xp, (9*innerH/20) + foV,
+ elem_geo.w - foH, (11*innerH/20) + foV)
+ elem_ass:rect_cw(foH, (3*innerH/8) + foV,
+ xp, (5*innerH/8) + foV)
+ elem_ass:round_rect_cw(xp - innerH/2, foV,
+ xp + innerH/2, foV + innerH, innerH/2.0)
+ end
+ end
+
+ -- seek ranges
+ local seekRanges = element.slider.seekRangesF()
+ if not (seekRanges == nil) then
+ for _,range in pairs(seekRanges) do
+ local pstart = get_slider_ele_pos_for(element, range["start"])
+ local pend = get_slider_ele_pos_for(element, range["end"])
+ elem_ass:rect_ccw(pstart, (elem_geo.h/2)-1, pend, (elem_geo.h/2) + 1)
+ end
+ end
+
+ elem_ass:draw_stop()
+
+ -- add tooltip
+ if not (element.slider.tooltipF == nil) then
+
+ if mouse_hit(element) then
+ local sliderpos = get_slider_value(element)
+ local tooltiplabel = element.slider.tooltipF(sliderpos)
+
+ local an = slider_lo.tooltip_an
+
+ local ty
+
+ if (an == 2) then
+ ty = element.hitbox.y1 - slider_lo.border
+ else
+ ty = element.hitbox.y1 + elem_geo.h/2
+ end
+
+ local tx = get_virt_mouse_pos()
+ if (slider_lo.adjust_tooltip) then
+ if (an == 2) then
+ if (sliderpos < (s_min + 3)) then
+ an = an - 1
+ elseif (sliderpos > (s_max - 3)) then
+ an = an + 1
+ end
+ elseif (sliderpos > (s_max-s_min)/2) then
+ an = an + 1
+ tx = tx - 5
+ else
+ an = an - 1
+ tx = tx + 10
+ end
+ end
+
+ -- tooltip label
+ elem_ass:new_event()
+ elem_ass:pos(tx, ty)
+ elem_ass:an(an)
+ elem_ass:append(slider_lo.tooltip_style)
+
+ --alpha
+ local ar = slider_lo.alpha
+ if not (state.animation == nil) then
+ ar = {}
+ for ai, av in pairs(slider_lo.alpha) do
+ ar[ai] = mult_alpha(av, state.animation)
+ end
+ end
+ elem_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
+ ar[1], ar[2], ar[3], ar[4]))
+
+ elem_ass:append(tooltiplabel)
+
+ -- mpv_thumbnail_script.lua --
+ display_thumbnail({x=get_virt_mouse_pos(), y=ty, a=an}, sliderpos, elem_ass)
+ -- // mpv_thumbnail_script.lua // --
+
+ end
+ end
+
+ elseif (element.type == "button") then
+
+ local buttontext
+ if type(element.content) == "function" then
+ buttontext = element.content() -- function objects
+ elseif not (element.content == nil) then
+ buttontext = element.content -- text objects
+ end
+
+ local maxchars = element.layout.button.maxchars
+ if not (maxchars == nil) and (#buttontext > maxchars) then
+ local max_ratio = 1.25 -- up to 25% more chars while shrinking
+ local limit = math.max(0, math.floor(maxchars * max_ratio) - 3)
+ if (#buttontext > limit) then
+ while (#buttontext > limit) do
+ buttontext = buttontext:gsub(".[\128-\191]*$", "")
+ end
+ buttontext = buttontext .. "..."
+ end
+ local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
+ local stretch = (maxchars/#buttontext)*100
+ buttontext = string.format("{\\fscx%f}",
+ (maxchars/#buttontext)*100) .. buttontext
+ end
+
+ elem_ass:append(buttontext)
+ end
+
+ master_ass:merge(elem_ass)
+ end
+end
+
+--
+-- Message display
+--
+
+-- pos is 1 based
+function limited_list(prop, pos)
+ local proplist = mp.get_property_native(prop, {})
+ local count = #proplist
+ if count == 0 then
+ return count, proplist
+ end
+
+ local fs = tonumber(mp.get_property('options/osd-font-size'))
+ local max = math.ceil(osc_param.unscaled_y*0.75 / fs)
+ if max % 2 == 0 then
+ max = max - 1
+ end
+ local delta = math.ceil(max / 2) - 1
+ local begi = math.max(math.min(pos - delta, count - max + 1), 1)
+ local endi = math.min(begi + max - 1, count)
+
+ local reslist = {}
+ for i=begi, endi do
+ local item = proplist[i]
+ item.current = (i == pos) and true or nil
+ table.insert(reslist, item)
+ end
+ return count, reslist
+end
+
+function get_playlist()
+ local pos = mp.get_property_number('playlist-pos', 0) + 1
+ local count, limlist = limited_list('playlist', pos)
+ if count == 0 then
+ return 'Empty playlist.'
+ end
+
+ local message = string.format('Playlist [%d/%d]:\n', pos, count)
+ for i, v in ipairs(limlist) do
+ local title = v.title
+ local _, filename = utils.split_path(v.filename)
+ if title == nil then
+ title = filename
+ end
+ message = string.format('%s %s %s\n', message,
+ (v.current and 'â—' or 'â—‹'), title)
+ end
+ return message
+end
+
+function get_chapterlist()
+ local pos = mp.get_property_number('chapter', 0) + 1
+ local count, limlist = limited_list('chapter-list', pos)
+ if count == 0 then
+ return 'No chapters.'
+ end
+
+ local message = string.format('Chapters [%d/%d]:\n', pos, count)
+ for i, v in ipairs(limlist) do
+ local time = mp.format_time(v.time)
+ local title = v.title
+ if title == nil then
+ title = string.format('Chapter %02d', i)
+ end
+ message = string.format('%s[%s] %s %s\n', message, time,
+ (v.current and 'â—' or 'â—‹'), title)
+ end
+ return message
+end
+
+function show_message(text, duration)
+
+ --print("text: "..text.." duration: " .. duration)
+ if duration == nil then
+ duration = tonumber(mp.get_property("options/osd-duration")) / 1000
+ elseif not type(duration) == "number" then
+ print("duration: " .. duration)
+ end
+
+ -- cut the text short, otherwise the following functions
+ -- may slow down massively on huge input
+ text = string.sub(text, 0, 4000)
+
+ -- replace actual linebreaks with ASS linebreaks
+ text = string.gsub(text, "\n", "\\N")
+
+ state.message_text = text
+ state.message_timeout = mp.get_time() + duration
+end
+
+function render_message(ass)
+ if not(state.message_timeout == nil) and not(state.message_text == nil)
+ and state.message_timeout > mp.get_time() then
+ local _, lines = string.gsub(state.message_text, "\\N", "")
+
+ local fontsize = tonumber(mp.get_property("options/osd-font-size"))
+ local outline = tonumber(mp.get_property("options/osd-border-size"))
+ local maxlines = math.ceil(osc_param.unscaled_y*0.75 / fontsize)
+ local counterscale = osc_param.playresy / osc_param.unscaled_y
+
+ fontsize = fontsize * counterscale / math.max(0.65 + math.min(lines/maxlines, 1), 1)
+ outline = outline * counterscale / math.max(0.75 + math.min(lines/maxlines, 1)/2, 1)
+
+ local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}"
+
+
+ ass:new_event()
+ ass:append(style .. state.message_text)
+ else
+ state.message_text = nil
+ state.message_timeout = nil
+ end
+end
+
+--
+-- Initialisation and Layout
+--
+
+function new_element(name, type)
+ elements[name] = {}
+ elements[name].type = type
+
+ -- add default stuff
+ elements[name].eventresponder = {}
+ elements[name].visible = true
+ elements[name].enabled = true
+ elements[name].softrepeat = false
+ elements[name].styledown = (type == "button")
+ elements[name].state = {}
+
+ if (type == "slider") then
+ elements[name].slider = {min = {value = 0}, max = {value = 100}}
+ end
+
+
+ return elements[name]
+end
+
+function add_layout(name)
+ if not (elements[name] == nil) then
+ -- new layout
+ elements[name].layout = {}
+
+ -- set layout defaults
+ elements[name].layout.layer = 50
+ elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255}
+
+ if (elements[name].type == "button") then
+ elements[name].layout.button = {
+ maxchars = nil,
+ }
+ elseif (elements[name].type == "slider") then
+ -- slider defaults
+ elements[name].layout.slider = {
+ border = 1,
+ gap = 1,
+ nibbles_top = true,
+ nibbles_bottom = true,
+ stype = "slider",
+ adjust_tooltip = true,
+ tooltip_style = "",
+ tooltip_an = 2,
+ alpha = {[1] = 0, [2] = 255, [3] = 88, [4] = 255},
+ }
+ elseif (elements[name].type == "box") then
+ elements[name].layout.box = {radius = 0}
+ end
+
+ return elements[name].layout
+ else
+ msg.error("Can't add_layout to element \""..name.."\", doesn't exist.")
+ end
+end
+
+--
+-- Layouts
+--
+
+local layouts = {}
+
+-- Classic box layout
+layouts["box"] = function ()
+
+ local osc_geo = {
+ w = 550, -- width
+ h = 138, -- height
+ r = 10, -- corner-radius
+ p = 15, -- padding
+ }
+
+ -- make sure the OSC actually fits into the video
+ if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then
+ osc_param.playresy = (osc_geo.w+(2*osc_geo.p))/osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ end
+
+ -- position of the controller according to video aspect and valignment
+ local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
+ osc_geo.w, 0))
+ local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
+ osc_geo.h, 0))
+
+ -- position offset for contents aligned at the borders of the box
+ local pos_offsetX = (osc_geo.w - (2*osc_geo.p)) / 2
+ local pos_offsetY = (osc_geo.h - (2*osc_geo.p)) / 2
+
+ osc_param.areas = {} -- delete areas
+
+ -- area for active mouse input
+ add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
+
+ -- area for show/hide
+ local sh_area_y0, sh_area_y1
+ if user_opts.valign > 0 then
+ -- deadzone above OSC
+ sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
+ posY - (osc_geo.h / 2), 0, 0)
+ sh_area_y1 = osc_param.playresy
+ else
+ -- deadzone below OSC
+ sh_area_y0 = 0
+ sh_area_y1 = (posY + (osc_geo.h / 2)) +
+ get_align(1 - (2*user_opts.deadzonesize),
+ osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
+ end
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ -- fetch values
+ local osc_w, osc_h, osc_r, osc_p =
+ osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p
+
+ local lo
+
+ --
+ -- Background box
+ --
+
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h}
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+ lo.alpha[3] = user_opts.boxalpha
+ lo.box.radius = osc_r
+
+ --
+ -- Title row
+ --
+
+ local titlerowY = posY - pos_offsetY - 10
+
+ lo = add_layout("title")
+ lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
+ lo.style = osc_styles.vidtitle
+ lo.button.maxchars = user_opts.boxmaxchars
+
+ lo = add_layout("pl_prev")
+ lo.geometry =
+ {x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12}
+ lo.style = osc_styles.topButtons
+
+ lo = add_layout("pl_next")
+ lo.geometry =
+ {x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12}
+ lo.style = osc_styles.topButtons
+
+ --
+ -- Big buttons
+ --
+
+ local bigbtnrowY = posY - pos_offsetY + 35
+ local bigbtndist = 60
+
+ lo = add_layout("playpause")
+ lo.geometry =
+ {x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("skipback")
+ lo.geometry =
+ {x = posX - bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("skipfrwd")
+ lo.geometry =
+ {x = posX + bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("ch_prev")
+ lo.geometry =
+ {x = posX - (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("ch_next")
+ lo.geometry =
+ {x = posX + (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
+ lo.style = osc_styles.bigButtons
+
+ lo = add_layout("cy_audio")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bigbtnrowY, an = 1, w = 70, h = 18}
+ lo.style = osc_styles.smallButtonsL
+
+ lo = add_layout("cy_sub")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bigbtnrowY, an = 7, w = 70, h = 18}
+ lo.style = osc_styles.smallButtonsL
+
+ lo = add_layout("tog_fs")
+ lo.geometry =
+ {x = posX+pos_offsetX - 25, y = bigbtnrowY, an = 4, w = 25, h = 25}
+ lo.style = osc_styles.smallButtonsR
+
+ lo = add_layout("volume")
+ lo.geometry =
+ {x = posX+pos_offsetX - (25 * 2) - osc_geo.p,
+ y = bigbtnrowY, an = 4, w = 25, h = 25}
+ lo.style = osc_styles.smallButtonsR
+
+ --
+ -- Seekbar
+ --
+
+ lo = add_layout("seekbar")
+ lo.geometry =
+ {x = posX, y = posY+pos_offsetY-22, an = 2, w = pos_offsetX*2, h = 15}
+ lo.style = osc_styles.timecodes
+ lo.slider.tooltip_style = osc_styles.vidtitle
+ lo.slider.stype = user_opts["seekbarstyle"]
+ if lo.slider.stype == "knob" then
+ lo.slider.border = 0
+ end
+
+ --
+ -- Timecodes + Cache
+ --
+
+ local bottomrowY = posY + pos_offsetY - 5
+
+ lo = add_layout("tc_left")
+ lo.geometry =
+ {x = posX - pos_offsetX, y = bottomrowY, an = 4, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+ lo = add_layout("tc_right")
+ lo.geometry =
+ {x = posX + pos_offsetX, y = bottomrowY, an = 6, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+ lo = add_layout("cache")
+ lo.geometry =
+ {x = posX, y = bottomrowY, an = 5, w = 110, h = 18}
+ lo.style = osc_styles.timecodes
+
+end
+
+-- slim box layout
+layouts["slimbox"] = function ()
+
+ local osc_geo = {
+ w = 660, -- width
+ h = 70, -- height
+ r = 10, -- corner-radius
+ }
+
+ -- make sure the OSC actually fits into the video
+ if (osc_param.playresx < (osc_geo.w)) then
+ osc_param.playresy = (osc_geo.w)/osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ end
+
+ -- position of the controller according to video aspect and valignment
+ local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
+ osc_geo.w, 0))
+ local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
+ osc_geo.h, 0))
+
+ osc_param.areas = {} -- delete areas
+
+ -- area for active mouse input
+ add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
+
+ -- area for show/hide
+ local sh_area_y0, sh_area_y1
+ if user_opts.valign > 0 then
+ -- deadzone above OSC
+ sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
+ posY - (osc_geo.h / 2), 0, 0)
+ sh_area_y1 = osc_param.playresy
+ else
+ -- deadzone below OSC
+ sh_area_y0 = 0
+ sh_area_y1 = (posY + (osc_geo.h / 2)) +
+ get_align(1 - (2*user_opts.deadzonesize),
+ osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
+ end
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ local lo
+
+ local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100
+
+ -- styles
+ local styles = {
+ box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
+ timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}",
+ tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}",
+ }
+
+
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+ lo.alpha[3] = 0
+ if not (user_opts["seekbarstyle"] == "bar") then
+ lo.box.radius = osc_geo.r
+ end
+
+
+ lo = add_layout("seekbar")
+ lo.geometry =
+ {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
+ lo.style = osc_styles.timecodes
+ lo.slider.border = 0
+ lo.slider.gap = 1.5
+ lo.slider.tooltip_style = styles.tooltip
+ lo.slider.stype = user_opts["seekbarstyle"]
+ lo.slider.adjust_tooltip = false
+
+ --
+ -- Timecodes
+ --
+
+ lo = add_layout("tc_left")
+ lo.geometry =
+ {x = posX - (inner_w/2) + osc_geo.r, y = posY + 1,
+ an = 7, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+ lo = add_layout("tc_right")
+ lo.geometry =
+ {x = posX + (inner_w/2) - osc_geo.r, y = posY + 1,
+ an = 9, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+ -- Cache
+
+ lo = add_layout("cache")
+ lo.geometry =
+ {x = posX, y = posY + 1,
+ an = 8, w = tc_w, h = ele_h}
+ lo.style = styles.timecodes
+ lo.alpha[3] = user_opts.boxalpha
+
+
+end
+
+layouts["bottombar"] = function()
+ local osc_geo = {
+ x = -2,
+ y = osc_param.playresy - 54 - user_opts.barmargin,
+ an = 7,
+ w = osc_param.playresx + 4,
+ h = 56,
+ }
+
+ local padX = 9
+ local padY = 3
+ local buttonW = 27
+ local tcW = (state.tc_ms) and 170 or 110
+ local tsW = 90
+ local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2
+
+ if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
+ osc_param.playresy = minW / osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ osc_geo.y = osc_param.playresy - 54 - user_opts.barmargin
+ osc_geo.w = osc_param.playresx + 4
+ end
+
+ local line1 = osc_geo.y + 9 + padY
+ local line2 = osc_geo.y + 36 + padY
+
+ osc_param.areas = {}
+
+ add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
+ osc_geo.w, osc_geo.h))
+
+ local sh_area_y0, sh_area_y1
+ sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
+ osc_geo.y - (osc_geo.h / 2), 0, 0)
+ sh_area_y1 = osc_param.playresy - user_opts.barmargin
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ local lo, geo
+
+ -- Background bar
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = osc_geo
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+
+
+ -- Playlist prev/next
+ geo = { x = osc_geo.x + padX, y = line1,
+ an = 4, w = 18, h = 18 - padY }
+ lo = add_layout("pl_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("pl_next")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ local t_l = geo.x + geo.w + padX
+
+ -- Cache
+ geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y,
+ an = 6, w = 150, h = geo.h }
+ lo = add_layout("cache")
+ lo.geometry = geo
+ lo.style = osc_styles.vidtitleBar
+
+ local t_r = geo.x - geo.w - padX*2
+
+ -- Title
+ geo = { x = t_l, y = geo.y, an = 4,
+ w = t_r - t_l, h = geo.h }
+ lo = add_layout("title")
+ lo.geometry = geo
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.vidtitleBar,
+ geo.x, geo.y-geo.h, geo.w, geo.y+geo.h)
+
+
+ -- Playback control buttons
+ geo = { x = osc_geo.x + padX, y = line2, an = 4,
+ w = buttonW, h = 36 - padY*2}
+ lo = add_layout("playpause")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_next")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Left timecode
+ geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_left")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_l = geo.x + padX
+
+ -- Fullscreen button
+ geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4,
+ w = buttonW, h = geo.h }
+ lo = add_layout("tog_fs")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Volume
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("volume")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Track selection buttons
+ geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
+ lo = add_layout("cy_sub")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("cy_audio")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+
+ -- Right timecode
+ geo = { x = geo.x - padX - tcW - 10, y = geo.y, an = geo.an,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_right")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_r = geo.x - padX
+
+
+ -- Seekbar
+ geo = { x = sb_l, y = geo.y, an = geo.an,
+ w = math.max(0, sb_r - sb_l), h = geo.h }
+ new_element("bgbar1", "box")
+ lo = add_layout("bgbar1")
+
+ lo.geometry = geo
+ lo.layer = 15
+ lo.style = osc_styles.timecodesBar
+ lo.alpha[1] =
+ math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.8)
+
+ lo = add_layout("seekbar")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodes
+ lo.slider.border = 0
+ lo.slider.gap = 2
+ lo.slider.tooltip_style = osc_styles.timePosBar
+ lo.slider.tooltip_an = 5
+ lo.slider.stype = user_opts["seekbarstyle"]
+end
+
+layouts["topbar"] = function()
+ local osc_geo = {
+ x = -2,
+ y = 54 + user_opts.barmargin,
+ an = 1,
+ w = osc_param.playresx + 4,
+ h = 56,
+ }
+
+ local padX = 9
+ local padY = 3
+ local buttonW = 27
+ local tcW = (state.tc_ms) and 170 or 110
+ local tsW = 90
+ local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2
+
+ if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
+ osc_param.playresy = minW / osc_param.display_aspect
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+ osc_geo.y = 54 + user_opts.barmargin
+ osc_geo.w = osc_param.playresx + 4
+ end
+
+ local line1 = osc_geo.y - 36 - padY
+ local line2 = osc_geo.y - 9 - padY
+
+ osc_param.areas = {}
+
+ add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
+ osc_geo.w, osc_geo.h))
+
+ local sh_area_y0, sh_area_y1
+ sh_area_y0 = user_opts.barmargin
+ sh_area_y1 = (osc_geo.y + (osc_geo.h / 2)) +
+ get_align(1 - (2*user_opts.deadzonesize),
+ osc_param.playresy - (osc_geo.y + (osc_geo.h / 2)), 0, 0)
+ add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
+
+ local lo, geo
+
+ -- Background bar
+ new_element("bgbox", "box")
+ lo = add_layout("bgbox")
+
+ lo.geometry = osc_geo
+ lo.layer = 10
+ lo.style = osc_styles.box
+ lo.alpha[1] = user_opts.boxalpha
+
+
+ -- Playback control buttons
+ geo = { x = osc_geo.x + padX, y = line1, an = 4,
+ w = buttonW, h = 36 - padY*2 }
+ lo = add_layout("playpause")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("ch_next")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+
+ -- Left timecode
+ geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_left")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_l = geo.x + padX
+
+ -- Fullscreen button
+ geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4,
+ w = buttonW, h = geo.h }
+ lo = add_layout("tog_fs")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Volume
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("volume")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ -- Track selection buttons
+ geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
+ lo = add_layout("cy_sub")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+ geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("cy_audio")
+ lo.geometry = geo
+ lo.style = osc_styles.smallButtonsBar
+
+
+ -- Right timecode
+ geo = { x = geo.x - geo.w - padX - tcW - 10, y = geo.y, an = 4,
+ w = tcW, h = geo.h }
+ lo = add_layout("tc_right")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+
+ local sb_r = geo.x - padX
+
+
+ -- Seekbar
+ geo = { x = sb_l, y = user_opts.barmargin, an = 7,
+ w = math.max(0, sb_r - sb_l), h = geo.h }
+ new_element("bgbar1", "box")
+ lo = add_layout("bgbar1")
+
+ lo.geometry = geo
+ lo.layer = 15
+ lo.style = osc_styles.timecodesBar
+ lo.alpha[1] =
+ math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.8)
+
+ lo = add_layout("seekbar")
+ lo.geometry = geo
+ lo.style = osc_styles.timecodesBar
+ lo.slider.border = 0
+ lo.slider.gap = 2
+ lo.slider.tooltip_style = osc_styles.timePosBar
+ lo.slider.stype = user_opts["seekbarstyle"]
+ lo.slider.tooltip_an = 5
+
+
+ -- Playlist prev/next
+ geo = { x = osc_geo.x + padX, y = line2, an = 4, w = 18, h = 18 - padY }
+ lo = add_layout("pl_prev")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
+ lo = add_layout("pl_next")
+ lo.geometry = geo
+ lo.style = osc_styles.topButtonsBar
+
+ local t_l = geo.x + geo.w + padX
+
+ -- Cache
+ geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y,
+ an = 6, w = 150, h = geo.h }
+ lo = add_layout("cache")
+ lo.geometry = geo
+ lo.style = osc_styles.vidtitleBar
+
+ local t_r = geo.x - geo.w - padX*2
+
+ -- Title
+ geo = { x = t_l, y = geo.y, an = 4,
+ w = t_r - t_l, h = geo.h }
+ lo = add_layout("title")
+ lo.geometry = geo
+ lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
+ osc_styles.vidtitleBar,
+ geo.x, geo.y-geo.h, geo.w, geo.y+geo.h)
+end
+
+-- Validate string type user options
+function validate_user_opts()
+ if layouts[user_opts.layout] == nil then
+ msg.warn("Invalid setting \""..user_opts.layout.."\" for layout")
+ user_opts.layout = "box"
+ end
+
+ if user_opts.seekbarstyle ~= "slider" and
+ user_opts.seekbarstyle ~= "bar" and
+ user_opts.seekbarstyle ~= "knob" then
+ msg.warn("Invalid setting \"" .. user_opts.seekbarstyle
+ .. "\" for seekbarstyle")
+ user_opts.seekbarstyle = "slider"
+ end
+end
+
+
+-- OSC INIT
+function osc_init()
+ msg.debug("osc_init")
+
+ -- set canvas resolution according to display aspect and scaling setting
+ local baseResY = 720
+ local display_w, display_h, display_aspect = mp.get_osd_size()
+ local scale = 1
+
+ if (mp.get_property("video") == "no") then -- dummy/forced window
+ scale = user_opts.scaleforcedwindow
+ elseif state.fullscreen then
+ scale = user_opts.scalefullscreen
+ else
+ scale = user_opts.scalewindowed
+ end
+
+ if user_opts.vidscale then
+ osc_param.unscaled_y = baseResY
+ else
+ osc_param.unscaled_y = display_h
+ end
+ osc_param.playresy = osc_param.unscaled_y / scale
+ if (display_aspect > 0) then
+ osc_param.display_aspect = display_aspect
+ end
+ osc_param.playresx = osc_param.playresy * osc_param.display_aspect
+
+
+
+
+
+ elements = {}
+
+ -- some often needed stuff
+ local pl_count = mp.get_property_number("playlist-count", 0)
+ local have_pl = (pl_count > 1)
+ local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
+ local have_ch = (mp.get_property_number("chapters", 0) > 0)
+ local loop = mp.get_property("loop-playlist", "no")
+
+ local ne
+
+ -- title
+ ne = new_element("title", "button")
+
+ ne.content = function ()
+ local title = mp.command_native({"expand-text", user_opts.title})
+ -- escape ASS, and strip newlines and trailing slashes
+ title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
+ return not (title == "") and title or "mpv"
+ end
+
+ ne.eventresponder["mbtn_left_up"] = function ()
+ local title = mp.get_property_osd("media-title")
+ if (have_pl) then
+ title = string.format("[%d/%d] %s", countone(pl_pos - 1),
+ pl_count, title)
+ end
+ show_message(title)
+ end
+
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(mp.get_property_osd("filename")) end
+
+ -- playlist buttons
+
+ -- prev
+ ne = new_element("pl_prev", "button")
+
+ ne.content = "\238\132\144"
+ ne.enabled = (pl_pos > 1) or (loop ~= "no")
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("playlist-prev", "weak")
+ show_message(get_playlist(), 3)
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_playlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_playlist(), 3) end
+
+ --next
+ ne = new_element("pl_next", "button")
+
+ ne.content = "\238\132\129"
+ ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("playlist-next", "weak")
+ show_message(get_playlist(), 3)
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_playlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_playlist(), 3) end
+
+
+ -- big buttons
+
+ --playpause
+ ne = new_element("playpause", "button")
+
+ ne.content = function ()
+ if mp.get_property("pause") == "yes" then
+ return ("\238\132\129")
+ else
+ return ("\238\128\130")
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "pause") end
+
+ --skipback
+ ne = new_element("skipback", "button")
+
+ ne.softrepeat = true
+ ne.content = "\238\128\132"
+ ne.eventresponder["mbtn_left_down"] =
+ function () mp.commandv("seek", -5, "relative", "keyframes") end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () mp.commandv("frame-back-step") end
+ ne.eventresponder["mbtn_right_down"] =
+ function () mp.commandv("seek", -30, "relative", "keyframes") end
+
+ --skipfrwd
+ ne = new_element("skipfrwd", "button")
+
+ ne.softrepeat = true
+ ne.content = "\238\128\133"
+ ne.eventresponder["mbtn_left_down"] =
+ function () mp.commandv("seek", 10, "relative", "keyframes") end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () mp.commandv("frame-step") end
+ ne.eventresponder["mbtn_right_down"] =
+ function () mp.commandv("seek", 60, "relative", "keyframes") end
+
+ --ch_prev
+ ne = new_element("ch_prev", "button")
+
+ ne.enabled = have_ch
+ ne.content = "\238\132\132"
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("add", "chapter", -1)
+ show_message(get_chapterlist(), 3)
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_chapterlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_chapterlist(), 3) end
+
+ --ch_next
+ ne = new_element("ch_next", "button")
+
+ ne.enabled = have_ch
+ ne.content = "\238\132\133"
+ ne.eventresponder["mbtn_left_up"] =
+ function ()
+ mp.commandv("add", "chapter", 1)
+ show_message(get_chapterlist(), 3)
+ end
+ ne.eventresponder["shift+mbtn_left_up"] =
+ function () show_message(get_chapterlist(), 3) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () show_message(get_chapterlist(), 3) end
+
+ --
+ update_tracklist()
+
+ --cy_audio
+ ne = new_element("cy_audio", "button")
+
+ ne.enabled = (#tracks_osc.audio > 0)
+ ne.content = function ()
+ local aid = "–"
+ if not (get_track("audio") == 0) then
+ aid = get_track("audio")
+ end
+ return ("\238\132\134" .. osc_styles.smallButtonsLlabel
+ .. " " .. aid .. "/" .. #tracks_osc.audio)
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () set_track("audio", 1) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () set_track("audio", -1) end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () show_message(get_tracklist("audio"), 2) end
+
+ --cy_sub
+ ne = new_element("cy_sub", "button")
+
+ ne.enabled = (#tracks_osc.sub > 0)
+ ne.content = function ()
+ local sid = "–"
+ if not (get_track("sub") == 0) then
+ sid = get_track("sub")
+ end
+ return ("\238\132\135" .. osc_styles.smallButtonsLlabel
+ .. " " .. sid .. "/" .. #tracks_osc.sub)
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () set_track("sub", 1) end
+ ne.eventresponder["mbtn_right_up"] =
+ function () set_track("sub", -1) end
+ ne.eventresponder["shift+mbtn_left_down"] =
+ function () show_message(get_tracklist("sub"), 2) end
+
+ --tog_fs
+ ne = new_element("tog_fs", "button")
+ ne.content = function ()
+ if (state.fullscreen) then
+ return ("\238\132\137")
+ else
+ return ("\238\132\136")
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "fullscreen") end
+
+
+ --seekbar
+ ne = new_element("seekbar", "slider")
+
+ ne.enabled = not (mp.get_property("percent-pos") == nil)
+ ne.slider.markerF = function ()
+ local duration = mp.get_property_number("duration", nil)
+ if not (duration == nil) then
+ local chapters = mp.get_property_native("chapter-list", {})
+ local markers = {}
+ for n = 1, #chapters do
+ markers[n] = (chapters[n].time / duration * 100)
+ end
+ return markers
+ else
+ return {}
+ end
+ end
+ ne.slider.posF =
+ function () return mp.get_property_number("percent-pos", nil) end
+ ne.slider.tooltipF = function (pos)
+ local duration = mp.get_property_number("duration", nil)
+ if not ((duration == nil) or (pos == nil)) then
+ possec = duration * (pos / 100)
+ return mp.format_time(possec)
+ else
+ return ""
+ end
+ end
+ ne.slider.seekRangesF = function()
+ if not (user_opts.seekranges) then
+ return nil
+ end
+ local cache_state = mp.get_property_native("demuxer-cache-state", nil)
+ if not cache_state then
+ return nil
+ end
+ local duration = mp.get_property_number("duration", nil)
+ if (duration == nil) or duration <= 0 then
+ return nil
+ end
+ local ranges = cache_state["seekable-ranges"]
+ for _, range in pairs(ranges) do
+ range["start"] = 100 * range["start"] / duration
+ range["end"] = 100 * range["end"] / duration
+ end
+ return ranges
+ end
+ ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged
+ function (element)
+ -- mouse move events may pile up during seeking and may still get
+ -- sent when the user is done seeking, so we need to throw away
+ -- identical seeks
+ local seekto = get_slider_value(element)
+ if (element.state.lastseek == nil) or
+ (not (element.state.lastseek == seekto)) then
+ mp.commandv("seek", seekto,
+ "absolute-percent", "keyframes")
+ element.state.lastseek = seekto
+ end
+
+ end
+ ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks
+ function (element) mp.commandv("seek", get_slider_value(element),
+ "absolute-percent", "exact") end
+ ne.eventresponder["reset"] =
+ function (element) element.state.lastseek = nil end
+
+
+ -- tc_left (current pos)
+ ne = new_element("tc_left", "button")
+
+ ne.content = function ()
+ if (state.tc_ms) then
+ return (mp.get_property_osd("playback-time/full"))
+ else
+ return (mp.get_property_osd("playback-time"))
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] = function ()
+ state.tc_ms = not state.tc_ms
+ request_init()
+ end
+
+ -- tc_right (total/remaining time)
+ ne = new_element("tc_right", "button")
+
+ ne.visible = (mp.get_property_number("duration", 0) > 0)
+ ne.content = function ()
+ if (state.rightTC_trem) then
+ if state.tc_ms then
+ return ("-"..mp.get_property_osd("playtime-remaining/full"))
+ else
+ return ("-"..mp.get_property_osd("playtime-remaining"))
+ end
+ else
+ if state.tc_ms then
+ return (mp.get_property_osd("duration/full"))
+ else
+ return (mp.get_property_osd("duration"))
+ end
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () state.rightTC_trem = not state.rightTC_trem end
+
+ -- cache
+ ne = new_element("cache", "button")
+
+ ne.content = function ()
+ local dmx_cache = mp.get_property_number("demuxer-cache-duration")
+ local cache_used = mp.get_property_number("cache-used")
+ local dmx_cache_state = mp.get_property_native("demuxer-cache-state", {})
+ local is_network = mp.get_property_native("demuxer-via-network")
+ local show_cache = cache_used and not dmx_cache_state["eof"]
+ if dmx_cache then
+ dmx_cache = string.format("%3.0fs", dmx_cache)
+ end
+ if dmx_cache_state["fw-bytes"] then
+ cache_used = (cache_used or 0)*1024 + dmx_cache_state["fw-bytes"]
+ end
+ if (is_network and dmx_cache) or show_cache then
+ -- Only show dmx-cache-duration by itself if it's a network file.
+ -- Cache can be forced even for local files, so always show that.
+ return string.format("Cache: %s%s%s",
+ (dmx_cache and dmx_cache or ""),
+ ((dmx_cache and show_cache) and " | " or ""),
+ (show_cache and
+ utils.format_bytes_humanized(cache_used) or ""))
+ else
+ return ""
+ end
+ end
+
+ -- volume
+ ne = new_element("volume", "button")
+
+ ne.content = function()
+ local volume = mp.get_property_number("volume", 0)
+ local mute = mp.get_property_native("mute")
+ local volicon = {"\238\132\139", "\238\132\140",
+ "\238\132\141", "\238\132\142"}
+ if volume == 0 or mute then
+ return "\238\132\138"
+ else
+ return volicon[math.min(4,math.ceil(volume / (100/3)))]
+ end
+ end
+ ne.eventresponder["mbtn_left_up"] =
+ function () mp.commandv("cycle", "mute") end
+
+ ne.eventresponder["wheel_up_press"] =
+ function () mp.commandv("osd-auto", "add", "volume", 5) end
+ ne.eventresponder["wheel_down_press"] =
+ function () mp.commandv("osd-auto", "add", "volume", -5) end
+
+
+ -- load layout
+ layouts[user_opts.layout]()
+
+ --do something with the elements
+ prepare_elements()
+
+end
+
+
+
+--
+-- Other important stuff
+--
+
+
+function show_osc()
+ -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding
+ if not state.enabled then return end
+
+ msg.trace("show_osc")
+ --remember last time of invocation (mouse move)
+ state.showtime = mp.get_time()
+
+ osc_visible(true)
+
+ if (user_opts.fadeduration > 0) then
+ state.anitype = nil
+ end
+end
+
+function hide_osc()
+ msg.trace("hide_osc")
+ if not state.enabled then
+ -- typically hide happens at render() from tick(), but now tick() is
+ -- no-op and won't render again to remove the osc, so do that manually.
+ state.osc_visible = false
+ timer_stop()
+ render_wipe()
+ elseif (user_opts.fadeduration > 0) then
+ if not(state.osc_visible == false) then
+ state.anitype = "out"
+ control_timer()
+ end
+ else
+ osc_visible(false)
+ end
+end
+
+function osc_visible(visible)
+ state.osc_visible = visible
+ control_timer()
+end
+
+function pause_state(name, enabled)
+ state.paused = enabled
+ control_timer()
+end
+
+function cache_state(name, idle)
+ state.cache_idle = idle
+ control_timer()
+end
+
+function control_timer()
+ if (state.paused) and (state.osc_visible) and
+ ( not(state.cache_idle) or not (state.anitype == nil) ) then
+
+ timer_start()
+ else
+ timer_stop()
+ end
+end
+
+function timer_start()
+ if not (state.timer_active) then
+ msg.trace("timer start")
+
+ if (state.timer == nil) then
+ -- create new timer
+ state.timer = mp.add_periodic_timer(0.03, tick)
+ else
+ -- resume existing one
+ state.timer:resume()
+ end
+
+ state.timer_active = true
+ end
+end
+
+function timer_stop()
+ if (state.timer_active) then
+ msg.trace("timer stop")
+
+ if not (state.timer == nil) then
+ -- kill timer
+ state.timer:kill()
+ end
+
+ state.timer_active = false
+ end
+end
+
+
+
+function mouse_leave()
+ if user_opts.hidetimeout >= 0 then
+ hide_osc()
+ end
+ -- reset mouse position
+ state.last_mouseX, state.last_mouseY = nil, nil
+end
+
+function request_init()
+ state.initREQ = true
+end
+
+function render_wipe()
+ msg.trace("render_wipe()")
+ mp.set_osd_ass(0, 0, "{}")
+end
+
+function render()
+ msg.trace("rendering")
+ local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
+ local mouseX, mouseY = get_virt_mouse_pos()
+ local now = mp.get_time()
+
+ -- check if display changed, if so request reinit
+ if not (state.mp_screen_sizeX == current_screen_sizeX
+ and state.mp_screen_sizeY == current_screen_sizeY) then
+
+ request_init()
+
+ state.mp_screen_sizeX = current_screen_sizeX
+ state.mp_screen_sizeY = current_screen_sizeY
+ end
+
+ -- init management
+ if state.initREQ then
+ osc_init()
+ state.initREQ = false
+
+ -- store initial mouse position
+ if (state.last_mouseX == nil or state.last_mouseY == nil)
+ and not (mouseX == nil or mouseY == nil) then
+
+ state.last_mouseX, state.last_mouseY = mouseX, mouseY
+ end
+ end
+
+
+ -- fade animation
+ if not(state.anitype == nil) then
+
+ if (state.anistart == nil) then
+ state.anistart = now
+ end
+
+ if (now < state.anistart + (user_opts.fadeduration/1000)) then
+
+ if (state.anitype == "in") then --fade in
+ osc_visible(true)
+ state.animation = scale_value(state.anistart,
+ (state.anistart + (user_opts.fadeduration/1000)),
+ 255, 0, now)
+ elseif (state.anitype == "out") then --fade out
+ state.animation = scale_value(state.anistart,
+ (state.anistart + (user_opts.fadeduration/1000)),
+ 0, 255, now)
+ end
+
+ else
+ if (state.anitype == "out") then
+ osc_visible(false)
+ end
+ state.anistart = nil
+ state.animation = nil
+ state.anitype = nil
+ end
+ else
+ state.anistart = nil
+ state.animation = nil
+ state.anitype = nil
+ end
+
+ --mouse show/hide area
+ for k,cords in pairs(osc_param.areas["showhide"]) do
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
+ end
+ do_enable_keybindings()
+
+ --mouse input area
+ local mouse_over_osc = false
+
+ for _,cords in ipairs(osc_param.areas["input"]) do
+ if state.osc_visible then -- activate only when OSC is actually visible
+ set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
+ end
+ if state.osc_visible ~= state.input_enabled then
+ if state.osc_visible then
+ mp.enable_key_bindings("input")
+ else
+ mp.disable_key_bindings("input")
+ end
+ state.input_enabled = state.osc_visible
+ end
+
+ if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
+ mouse_over_osc = true
+ end
+ end
+
+ -- autohide
+ if not (state.showtime == nil) and (user_opts.hidetimeout >= 0)
+ and (state.showtime + (user_opts.hidetimeout/1000) < now)
+ and (state.active_element == nil) and not (mouse_over_osc) then
+
+ hide_osc()
+ end
+
+
+ -- actual rendering
+ local ass = assdraw.ass_new()
+
+ -- Messages
+ render_message(ass)
+
+ -- mpv_thumbnail_script.lua --
+ local thumb_was_visible = osc_thumb_state.visible
+ osc_thumb_state.visible = false
+ -- // mpv_thumbnail_script.lua // --
+
+ -- actual OSC
+ if state.osc_visible then
+ render_elements(ass)
+ end
+
+ -- mpv_thumbnail_script.lua --
+ if not osc_thumb_state.visible and thumb_was_visible then
+ hide_thumbnail()
+ end
+ -- // mpv_thumbnail_script.lua // --
+
+ -- submit
+ mp.set_osd_ass(osc_param.playresy * osc_param.display_aspect,
+ osc_param.playresy, ass.text)
+
+
+
+
+end
+
+--
+-- Eventhandling
+--
+
+local function element_has_action(element, action)
+ return element and element.eventresponder and
+ element.eventresponder[action]
+end
+
+function process_event(source, what)
+ local action = string.format("%s%s", source,
+ what and ("_" .. what) or "")
+
+ if what == "down" or what == "press" then
+
+ for n = 1, #elements do
+
+ if mouse_hit(elements[n]) and
+ elements[n].eventresponder and
+ (elements[n].eventresponder[source .. "_up"] or
+ elements[n].eventresponder[action]) then
+
+ if what == "down" then
+ state.active_element = n
+ state.active_event_source = source
+ end
+ -- fire the down or press event if the element has one
+ if element_has_action(elements[n], action) then
+ elements[n].eventresponder[action](elements[n])
+ end
+
+ end
+ end
+
+ elseif what == "up" then
+
+ if elements[state.active_element] then
+ local n = state.active_element
+
+ if n == 0 then
+ --click on background (does not work)
+ elseif element_has_action(elements[n], action) and
+ mouse_hit(elements[n]) then
+
+ elements[n].eventresponder[action](elements[n])
+ end
+
+ --reset active element
+ if element_has_action(elements[n], "reset") then
+ elements[n].eventresponder["reset"](elements[n])
+ end
+
+ end
+ state.active_element = nil
+ state.mouse_down_counter = 0
+
+ elseif source == "mouse_move" then
+
+ local mouseX, mouseY = get_virt_mouse_pos()
+ if (user_opts.minmousemove == 0) or
+ (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
+ ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove)
+ or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove)
+ )
+ ) then
+ show_osc()
+ end
+ state.last_mouseX, state.last_mouseY = mouseX, mouseY
+
+ local n = state.active_element
+ if element_has_action(elements[n], action) then
+ elements[n].eventresponder[action](elements[n])
+ end
+ tick()
+ end
+end
+
+-- called by mpv on every frame
+function tick()
+ if (not state.enabled) then return end
+
+ if (state.idle) then
+
+ -- render idle message
+ msg.trace("idle message")
+ local icon_x, icon_y = 320 - 26, 140
+
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&H430142&\\1a&H00&\\bord0\\shad0\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}")
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&HDDDBDD&\\1a&H00&\\bord0\\shad0\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}")
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&H691F69&\\1a&H00&\\bord0\\shad0\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}")
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&H682167&\\1a&H00&\\bord0\\shad0\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42 m 925 42 m 977 200 b 1324 200 1605 482 1605 828 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200{\\p0}")
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&H753074&\\1a&H00&\\bord0\\shad0\\p6}m 977 198 b 630 198 348 480 348 828 348 1176 630 1458 977 1458 1325 1458 1607 1176 1607 828 1607 480 1325 198 977 198 m 977 198 m 977 202 b 1323 202 1604 483 1604 828 1604 1174 1323 1454 977 1454 632 1454 351 1174 351 828 351 483 632 202 977 202{\\p0}")
+ ass:new_event()
+ ass:pos(icon_x, icon_y)
+ ass:append("{\\rDefault\\an7\\c&HE5E5E5&\\1a&H00&\\bord0\\shad0\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 m 895 10 m 925 42 b 1388 42 1763 418 1763 880 1763 1343 1388 1718 925 1718 463 1718 87 1343 87 880 87 418 463 42 925 42{\\p0}")
+ ass:new_event()
+ ass:pos(320, icon_y+65)
+ ass:an(8)
+ ass:append("Drop files or URLs to play here.")
+ mp.set_osd_ass(640, 360, ass.text)
+
+ if state.showhide_enabled then
+ mp.disable_key_bindings("showhide")
+ state.showhide_enabled = false
+ end
+
+
+ elseif (state.fullscreen and user_opts.showfullscreen)
+ or (not state.fullscreen and user_opts.showwindowed) then
+
+ -- render the OSC
+ render()
+ else
+ -- Flush OSD
+ mp.set_osd_ass(osc_param.playresy, osc_param.playresy, "")
+ end
+end
+
+function do_enable_keybindings()
+ if state.enabled then
+ if not state.showhide_enabled then
+ mp.enable_key_bindings("showhide", "allow-vo-dragging+allow-hide-cursor")
+ end
+ state.showhide_enabled = true
+ end
+end
+
+function enable_osc(enable)
+ state.enabled = enable
+ if enable then
+ do_enable_keybindings()
+ else
+ hide_osc() -- acts immediately when state.enabled == false
+ if state.showhide_enabled then
+ mp.disable_key_bindings("showhide")
+ end
+ state.showhide_enabled = false
+ end
+end
+
+-- mpv_thumbnail_script.lua --
+
+local builtin_osc_enabled = mp.get_property_native('osc')
+if builtin_osc_enabled then
+ local err = "You must disable the built-in OSC with osc=no in your configuration!"
+ mp.osd_message(err, 5)
+ msg.error(err)
+
+ -- This may break, but since we can, let's try to just disable the builtin OSC.
+ mp.set_property_native('osc', false)
+end
+
+-- // mpv_thumbnail_script.lua // --
+
+
+validate_user_opts()
+
+mp.register_event("start-file", request_init)
+mp.register_event("tracks-changed", request_init)
+mp.observe_property("playlist", nil, request_init)
+
+mp.register_script_message("osc-message", show_message)
+mp.register_script_message("osc-chapterlist", function(dur)
+ show_message(get_chapterlist(), dur)
+end)
+mp.register_script_message("osc-playlist", function(dur)
+ show_message(get_playlist(), dur)
+end)
+mp.register_script_message("osc-tracklist", function(dur)
+ local msg = {}
+ for k,v in pairs(nicetypes) do
+ table.insert(msg, get_tracklist(k))
+ end
+ show_message(table.concat(msg, '\n\n'), dur)
+end)
+
+mp.observe_property("fullscreen", "bool",
+ function(name, val)
+ state.fullscreen = val
+ request_init()
+ end
+)
+mp.observe_property("idle-active", "bool",
+ function(name, val)
+ state.idle = val
+ tick()
+ end
+)
+mp.observe_property("pause", "bool", pause_state)
+mp.observe_property("cache-idle", "bool", cache_state)
+mp.observe_property("vo-configured", "bool", function(name, val)
+ if val then
+ mp.register_event("tick", tick)
+ else
+ mp.unregister_event(tick)
+ end
+end)
+
+-- mouse show/hide bindings
+mp.set_key_bindings({
+ {"mouse_move", function(e) process_event("mouse_move", nil) end},
+ {"mouse_leave", mouse_leave},
+}, "showhide", "force")
+do_enable_keybindings()
+
+--mouse input bindings
+mp.set_key_bindings({
+ {"mbtn_left", function(e) process_event("mbtn_left", "up") end,
+ function(e) process_event("mbtn_left", "down") end},
+ {"shift+mbtn_left", function(e) process_event("shift+mbtn_left", "up") end,
+ function(e) process_event("shift+mbtn_left", "down") end},
+ {"mbtn_right", function(e) process_event("mbtn_right", "up") end,
+ function(e) process_event("mbtn_right", "down") end},
+ {"wheel_up", function(e) process_event("wheel_up", "press") end},
+ {"wheel_down", function(e) process_event("wheel_down", "press") end},
+ {"mbtn_left_dbl", "ignore"},
+ {"shift+mbtn_left_dbl", "ignore"},
+ {"mbtn_right_dbl", "ignore"},
+}, "input", "force")
+mp.enable_key_bindings("input")
+
+
+user_opts.hidetimeout_orig = user_opts.hidetimeout
+
+function always_on(val)
+ if val then
+ user_opts.hidetimeout = -1 -- disable autohide
+ if state.enabled then show_osc() end
+ else
+ user_opts.hidetimeout = user_opts.hidetimeout_orig
+ if state.enabled then hide_osc() end
+ end
+end
+
+-- mode can be auto/always/never/cycle
+-- the modes only affect internal variables and not stored on its own.
+function visibility_mode(mode, no_osd)
+ if mode == "cycle" then
+ if not state.enabled then
+ mode = "auto"
+ elseif user_opts.hidetimeout >= 0 then
+ mode = "always"
+ else
+ mode = "never"
+ end
+ end
+
+ if mode == "auto" then
+ always_on(false)
+ enable_osc(true)
+ elseif mode == "always" then
+ enable_osc(true)
+ always_on(true)
+ elseif mode == "never" then
+ enable_osc(false)
+ else
+ msg.warn("Ignoring unknown visibility mode '" .. mode .. "'")
+ return
+ end
+
+ if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
+ mp.osd_message("OSC visibility: " .. mode)
+ end
+end
+
+visibility_mode(user_opts.visibility, true)
+mp.register_script_message("osc-visibility", visibility_mode)
+mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end)
+
+set_virt_mouse_area(0, 0, 0, 0, "input")
diff --git a/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_server.lua b/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_server.lua
new file mode 100644
index 0000000..21a34d8
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts/mpv_thumbnail_script_server.lua
@@ -0,0 +1,736 @@
+--[[
+ Copyright (C) 2017 AMM
+
+ 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/>.
+]]--
+--[[
+ mpv_thumbnail_script.lua 0.4.2 - commit a2de250 (branch master)
+ https://github.com/TheAMM/mpv_thumbnail_script
+ Built on 2018-02-07 20:36:54
+]]--
+local assdraw = require 'mp.assdraw'
+local msg = require 'mp.msg'
+local opt = require 'mp.options'
+local utils = require 'mp.utils'
+
+-- Determine platform --
+ON_WINDOWS = (package.config:sub(1,1) ~= '/')
+
+-- Some helper functions needed to parse the options --
+function isempty(v) return (v == false) or (v == nil) or (v == "") or (v == 0) or (type(v) == "table" and next(v) == nil) end
+
+function divmod (a, b)
+ return math.floor(a / b), a % b
+end
+
+-- Better modulo
+function bmod( i, N )
+ return (i % N + N) % N
+end
+
+function join_paths(...)
+ local sep = ON_WINDOWS and "\\" or "/"
+ local result = "";
+ for i, p in pairs({...}) do
+ if p ~= "" then
+ if is_absolute_path(p) then
+ result = p
+ else
+ result = (result ~= "") and (result:gsub("[\\"..sep.."]*$", "") .. sep .. p) or p
+ end
+ end
+ end
+ return result:gsub("[\\"..sep.."]*$", "")
+end
+
+-- /some/path/file.ext -> /some/path, file.ext
+function split_path( path )
+ local sep = ON_WINDOWS and "\\" or "/"
+ local first_index, last_index = path:find('^.*' .. sep)
+
+ if last_index == nil then
+ return "", path
+ else
+ local dir = path:sub(0, last_index-1)
+ local file = path:sub(last_index+1, -1)
+
+ return dir, file
+ end
+end
+
+function is_absolute_path( path )
+ local tmp, is_win = path:gsub("^[A-Z]:\\", "")
+ local tmp, is_unix = path:gsub("^/", "")
+ return (is_win > 0) or (is_unix > 0)
+end
+
+function Set(source)
+ local set = {}
+ for _, l in ipairs(source) do set[l] = true end
+ return set
+end
+
+---------------------------
+-- More helper functions --
+---------------------------
+
+-- Removes all keys from a table, without destroying the reference to it
+function clear_table(target)
+ for key, value in pairs(target) do
+ target[key] = nil
+ end
+end
+function shallow_copy(target)
+ local copy = {}
+ for k, v in pairs(target) do
+ copy[k] = v
+ end
+ return copy
+end
+
+-- Rounds to given decimals. eg. round_dec(3.145, 0) => 3
+function round_dec(num, idp)
+ local mult = 10^(idp or 0)
+ return math.floor(num * mult + 0.5) / mult
+end
+
+function file_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ local ok, err, code = f:read(1)
+ io.close(f)
+ return code == nil
+ else
+ return false
+ end
+end
+
+function path_exists(name)
+ local f = io.open(name, "rb")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+function create_directories(path)
+ local cmd
+ if ON_WINDOWS then
+ cmd = { args = {"cmd", "/c", "mkdir", path} }
+ else
+ cmd = { args = {"mkdir", "-p", path} }
+ end
+ utils.subprocess(cmd)
+end
+
+-- Find an executable in PATH or CWD with the given name
+function find_executable(name)
+ local delim = ON_WINDOWS and ";" or ":"
+
+ local pwd = os.getenv("PWD") or utils.getcwd()
+ local path = os.getenv("PATH")
+
+ local env_path = pwd .. delim .. path -- Check CWD first
+
+ local result, filename
+ for path_dir in env_path:gmatch("[^"..delim.."]+") do
+ filename = join_paths(path_dir, name)
+ if file_exists(filename) then
+ result = filename
+ break
+ end
+ end
+
+ return result
+end
+
+local ExecutableFinder = { path_cache = {} }
+-- Searches for an executable and caches the result if any
+function ExecutableFinder:get_executable_path( name, raw_name )
+ name = ON_WINDOWS and not raw_name and (name .. ".exe") or name
+
+ if self.path_cache[name] == nil then
+ self.path_cache[name] = find_executable(name) or false
+ end
+ return self.path_cache[name]
+end
+
+-- Format seconds to HH.MM.SS.sss
+function format_time(seconds, sep, decimals)
+ decimals = decimals == nil and 3 or decimals
+ sep = sep and sep or "."
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
+
+ return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
+end
+
+-- Format seconds to 1h 2m 3.4s
+function format_time_hms(seconds, sep, decimals, force_full)
+ decimals = decimals == nil and 1 or decimals
+ sep = sep ~= nil and sep or " "
+
+ local s = seconds
+ local h, s = divmod(s, 60*60)
+ local m, s = divmod(s, 60)
+
+ if force_full or h > 0 then
+ return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
+ elseif m > 0 then
+ return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
+ else
+ return string.format("%." .. tostring(decimals) .. "fs", s)
+ end
+end
+
+-- Writes text on OSD and console
+function log_info(txt, timeout)
+ timeout = timeout or 1.5
+ msg.info(txt)
+ mp.osd_message(txt, timeout)
+end
+
+-- Join table items, ala ({"a", "b", "c"}, "=", "-", ", ") => "=a-, =b-, =c-"
+function join_table(source, before, after, sep)
+ before = before or ""
+ after = after or ""
+ sep = sep or ", "
+ local result = ""
+ for i, v in pairs(source) do
+ if not isempty(v) then
+ local part = before .. v .. after
+ if i == 1 then
+ result = part
+ else
+ result = result .. sep .. part
+ end
+ end
+ end
+ return result
+end
+
+function wrap(s, char)
+ char = char or "'"
+ return char .. s .. char
+end
+-- Wraps given string into 'string' and escapes any 's in it
+function escape_and_wrap(s, char, replacement)
+ char = char or "'"
+ replacement = replacement or "\\" .. char
+ return wrap(string.gsub(s, char, replacement), char)
+end
+-- Escapes single quotes in a string and wraps the input in single quotes
+function escape_single_bash(s)
+ return escape_and_wrap(s, "'", "'\\''")
+end
+
+-- Returns (a .. b) if b is not empty or nil
+function joined_or_nil(a, b)
+ return not isempty(b) and (a .. b) or nil
+end
+
+-- Put items from one table into another
+function extend_table(target, source)
+ for i, v in pairs(source) do
+ table.insert(target, v)
+ end
+end
+
+-- Creates a handle and filename for a temporary random file (in current directory)
+function create_temporary_file(base, mode, suffix)
+ local handle, filename
+ suffix = suffix or ""
+ while true do
+ filename = base .. tostring(math.random(1, 5000)) .. suffix
+ handle = io.open(filename, "r")
+ if not handle then
+ handle = io.open(filename, mode)
+ break
+ end
+ io.close(handle)
+ end
+ return handle, filename
+end
+
+
+function get_processor_count()
+ local proc_count
+
+ if ON_WINDOWS then
+ proc_count = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
+ else
+ local cpuinfo_handle = io.open("/proc/cpuinfo")
+ if cpuinfo_handle ~= nil then
+ local cpuinfo_contents = cpuinfo_handle:read("*a")
+ local _, replace_count = cpuinfo_contents:gsub('processor', '')
+ proc_count = replace_count
+ end
+ end
+
+ if proc_count and proc_count > 0 then
+ return proc_count
+ else
+ return nil
+ end
+end
+
+function substitute_values(string, values)
+ local substitutor = function(match)
+ if match == "%" then
+ return "%"
+ else
+ -- nil is discarded by gsub
+ return values[match]
+ end
+ end
+
+ local substituted = string:gsub('%%(.)', substitutor)
+ return substituted
+end
+
+-- ASS HELPERS --
+function round_rect_top( ass, x0, y0, x1, y1, r )
+ local c = 0.551915024494 * r -- circle approximation
+ ass:move_to(x0 + r, y0)
+ ass:line_to(x1 - r, y0) -- top line
+ if r > 0 then
+ ass:bezier_curve(x1 - r + c, y0, x1, y0 + r - c, x1, y0 + r) -- top right corner
+ end
+ ass:line_to(x1, y1) -- right line
+ ass:line_to(x0, y1) -- bottom line
+ ass:line_to(x0, y0 + r) -- left line
+ if r > 0 then
+ ass:bezier_curve(x0, y0 + r - c, x0 + r - c, y0, x0 + r, y0) -- top left corner
+ end
+end
+
+function round_rect(ass, x0, y0, x1, y1, rtl, rtr, rbr, rbl)
+ local c = 0.551915024494
+ ass:move_to(x0 + rtl, y0)
+ ass:line_to(x1 - rtr, y0) -- top line
+ if rtr > 0 then
+ ass:bezier_curve(x1 - rtr + rtr*c, y0, x1, y0 + rtr - rtr*c, x1, y0 + rtr) -- top right corner
+ end
+ ass:line_to(x1, y1 - rbr) -- right line
+ if rbr > 0 then
+ ass:bezier_curve(x1, y1 - rbr + rbr*c, x1 - rbr + rbr*c, y1, x1 - rbr, y1) -- bottom right corner
+ end
+ ass:line_to(x0 + rbl, y1) -- bottom line
+ if rbl > 0 then
+ ass:bezier_curve(x0 + rbl - rbl*c, y1, x0, y1 - rbl + rbl*c, x0, y1 - rbl) -- bottom left corner
+ end
+ ass:line_to(x0, y0 + rtl) -- left line
+ if rtl > 0 then
+ ass:bezier_curve(x0, y0 + rtl - rtl*c, x0 + rtl - rtl*c, y0, x0 + rtl, y0) -- top left corner
+ end
+end
+local SCRIPT_NAME = "mpv_thumbnail_script"
+
+local default_cache_base = ON_WINDOWS and os.getenv("TEMP") or "/tmp/"
+
+local thumbnailer_options = {
+ -- The thumbnail directory
+ cache_directory = join_paths(default_cache_base, "mpv_thumbs_cache"),
+
+ ------------------------
+ -- Generation options --
+ ------------------------
+
+ -- Automatically generate the thumbnails on video load, without a keypress
+ autogenerate = true,
+
+ -- Only automatically thumbnail videos shorter than this (seconds)
+ autogenerate_max_duration = 3600, -- 1 hour
+
+ -- SHA1-sum filenames over this length
+ -- It's nice to know what files the thumbnails are (hence directory names)
+ -- but long URLs may approach filesystem limits.
+ hash_filename_length = 128,
+
+ -- Use mpv to generate thumbnail even if ffmpeg is found in PATH
+ -- ffmpeg does not handle ordered chapters (MKVs which rely on other MKVs)!
+ -- mpv is a bit slower, but has better support overall (eg. subtitles in the previews)
+ prefer_mpv = true,
+
+ -- Explicitly disable subtitles on the mpv sub-calls
+ mpv_no_sub = false,
+ -- Add a "--no-config" to the mpv sub-call arguments
+ mpv_no_config = false,
+ -- Add a "--profile=<mpv_profile>" to the mpv sub-call arguments
+ -- Use "" to disable
+ mpv_profile = "",
+ -- Output debug logs to <thumbnail_path>.log, ala <cache_directory>/<video_filename>/000000.bgra.log
+ -- The logs are removed after successful encodes, unless you set mpv_keep_logs below
+ mpv_logs = true,
+ -- Keep all mpv logs, even the succesfull ones
+ mpv_keep_logs = false,
+
+ -- Disable the built-in keybind ("T") to add your own
+ disable_keybinds = false,
+
+ ---------------------
+ -- Display options --
+ ---------------------
+
+ -- Move the thumbnail up or down
+ -- For example:
+ -- topbar/bottombar: 24
+ -- rest: 0
+ vertical_offset = 24,
+
+ -- Adjust background padding
+ -- Examples:
+ -- topbar: 0, 10, 10, 10
+ -- bottombar: 10, 0, 10, 10
+ -- slimbox/box: 10, 10, 10, 10
+ pad_top = 10,
+ pad_bot = 0,
+ pad_left = 10,
+ pad_right = 10,
+
+ -- If true, pad values are screen-pixels. If false, video-pixels.
+ pad_in_screenspace = true,
+ -- Calculate pad into the offset
+ offset_by_pad = true,
+
+ -- Background color in BBGGRR
+ background_color = "000000",
+ -- Alpha: 0 - fully opaque, 255 - transparent
+ background_alpha = 80,
+
+ -- Keep thumbnail on the screen near left or right side
+ constrain_to_screen = true,
+
+ -- Do not display the thumbnailing progress
+ hide_progress = false,
+
+ -----------------------
+ -- Thumbnail options --
+ -----------------------
+
+ -- The maximum dimensions of the thumbnails (pixels)
+ thumbnail_width = 200,
+ thumbnail_height = 200,
+
+ -- The thumbnail count target
+ -- (This will result in a thumbnail every ~10 seconds for a 25 minute video)
+ thumbnail_count = 150,
+
+ -- The above target count will be adjusted by the minimum and
+ -- maximum time difference between thumbnails.
+ -- The thumbnail_count will be used to calculate a target separation,
+ -- and min/max_delta will be used to constrict it.
+
+ -- In other words, thumbnails will be:
+ -- at least min_delta seconds apart (limiting the amount)
+ -- at most max_delta seconds apart (raising the amount if needed)
+ min_delta = 5,
+ -- 120 seconds aka 2 minutes will add more thumbnails when the video is over 5 hours!
+ max_delta = 90,
+
+
+ -- Overrides for remote urls (you generally want less thumbnails!)
+ -- Thumbnailing network paths will be done with mpv
+
+ -- Allow thumbnailing network paths (naive check for "://")
+ thumbnail_network = false,
+ -- Override thumbnail count, min/max delta
+ remote_thumbnail_count = 60,
+ remote_min_delta = 15,
+ remote_max_delta = 120,
+
+ -- Try to grab the raw stream and disable ytdl for the mpv subcalls
+ -- Much faster than passing the url to ytdl again, but may cause problems with some sites
+ remote_direct_stream = true,
+}
+
+read_options(thumbnailer_options, SCRIPT_NAME)
+function skip_nil(tbl)
+ local n = {}
+ for k, v in pairs(tbl) do
+ table.insert(n, v)
+ end
+ return n
+end
+
+function create_thumbnail_mpv(file_path, timestamp, size, output_path, options)
+ options = options or {}
+
+ local ytdl_disabled = not options.enable_ytdl and (mp.get_property_native("ytdl") == false
+ or thumbnailer_options.remote_direct_stream)
+
+ local header_fields_arg = nil
+ local header_fields = mp.get_property_native("http-header-fields")
+ if #header_fields > 0 then
+ -- We can't escape the headers, mpv won't parse "--http-header-fields='Name: value'" properly
+ header_fields_arg = "--http-header-fields=" .. table.concat(header_fields, ",")
+ end
+
+ local profile_arg = nil
+ if thumbnailer_options.mpv_profile ~= "" then
+ profile_arg = "--profile=" .. thumbnailer_options.mpv_profile
+ end
+
+ local log_arg = "--log-file=" .. output_path .. ".log"
+
+ local mpv_command = skip_nil({
+ "mpv",
+ -- Hide console output
+ "--msg-level=all=no",
+
+ -- Disable ytdl
+ (ytdl_disabled and "--no-ytdl" or nil),
+ -- Pass HTTP headers from current instance
+ header_fields_arg,
+ -- Pass User-Agent and Referer - should do no harm even with ytdl active
+ "--user-agent=" .. mp.get_property_native("user-agent"),
+ "--referrer=" .. mp.get_property_native("referrer"),
+ -- Disable hardware decoding
+ "--hwdec=no",
+
+ -- Insert --no-config, --profile=... and --log-file if enabled
+ (thumbnailer_options.mpv_no_config and "--no-config" or nil),
+ profile_arg,
+ (thumbnailer_options.mpv_logs and log_arg or nil),
+
+ file_path,
+
+ "--start=" .. tostring(timestamp),
+ "--frames=1",
+ "--hr-seek=yes",
+ "--no-audio",
+ -- Optionally disable subtitles
+ (thumbnailer_options.mpv_no_sub and "--no-sub" or nil),
+
+ ("--vf=scale=%d:%d"):format(size.w, size.h),
+ "--vf-add=format=bgra",
+ "--of=rawvideo",
+ "--ovc=rawvideo",
+ ("--o=%s"):format(output_path)
+ })
+ return utils.subprocess({args=mpv_command})
+end
+
+
+function create_thumbnail_ffmpeg(file_path, timestamp, size, output_path)
+ local ffmpeg_command = {
+ "ffmpeg",
+ "-loglevel", "quiet",
+ "-noaccurate_seek",
+ "-ss", format_time(timestamp, ":"),
+ "-i", file_path,
+
+ "-frames:v", "1",
+ "-an",
+
+ "-vf", ("scale=%d:%d"):format(size.w, size.h),
+ "-c:v", "rawvideo",
+ "-pix_fmt", "bgra",
+ "-f", "rawvideo",
+
+ "-y", output_path
+ }
+ return utils.subprocess({args=ffmpeg_command})
+end
+
+
+function check_output(ret, output_path, is_mpv)
+ local log_path = output_path .. ".log"
+ local success = true
+
+ if ret.killed_by_us then
+ return nil
+ else
+ if ret.error or ret.status ~= 0 then
+ msg.error("Thumbnailing command failed!")
+ msg.error("mpv process error:", ret.error)
+ msg.error("Process stdout:", ret.stdout)
+ if is_mpv then
+ msg.error("Debug log:", log_path)
+ end
+
+ success = false
+ end
+
+ if not file_exists(output_path) then
+ msg.error("Output file missing!", output_path)
+ success = false
+ end
+ end
+
+ if is_mpv and not thumbnailer_options.mpv_keep_logs then
+ -- Remove successful debug logs
+ if success and file_exists(log_path) then
+ os.remove(log_path)
+ end
+ end
+
+ return success
+end
+
+
+function do_worker_job(state_json_string, frames_json_string)
+ msg.debug("Handling given job")
+ local thumb_state, err = utils.parse_json(state_json_string)
+ if err then
+ msg.error("Failed to parse state JSON")
+ return
+ end
+
+ local thumbnail_indexes, err = utils.parse_json(frames_json_string)
+ if err then
+ msg.error("Failed to parse thumbnail frame indexes")
+ return
+ end
+
+ local thumbnail_func = create_thumbnail_mpv
+ if not thumbnailer_options.prefer_mpv then
+ if ExecutableFinder:get_executable_path("ffmpeg") then
+ thumbnail_func = create_thumbnail_ffmpeg
+ else
+ msg.warn("Could not find ffmpeg in PATH! Falling back on mpv.")
+ end
+ end
+
+ local file_duration = mp.get_property_native("duration")
+ local file_path = thumb_state.worker_input_path
+
+ if thumb_state.is_remote then
+ if (thumbnail_func == create_thumbnail_ffmpeg) then
+ msg.warn("Thumbnailing remote path, falling back on mpv.")
+ end
+ thumbnail_func = create_thumbnail_mpv
+ end
+
+ local generate_thumbnail_for_index = function(thumbnail_index)
+ -- Given a 1-based thumbnail index, generate a thumbnail for it based on the thumbnailer state
+ local thumb_idx = thumbnail_index - 1
+ msg.debug("Starting work on thumbnail", thumb_idx)
+
+ local thumbnail_path = thumb_state.thumbnail_template:format(thumb_idx)
+ -- Grab the "middle" of the thumbnail duration instead of the very start, and leave some margin in the end
+ local timestamp = math.min(file_duration - 0.25, (thumb_idx + 0.5) * thumb_state.thumbnail_delta)
+
+ mp.commandv("script-message", "mpv_thumbnail_script-progress", tostring(thumbnail_index))
+
+ -- The expected size (raw BGRA image)
+ local thumbnail_raw_size = (thumb_state.thumbnail_size.w * thumb_state.thumbnail_size.h * 4)
+
+ local need_thumbnail_generation = false
+
+ -- Check if the thumbnail already exists and is the correct size
+ local thumbnail_file = io.open(thumbnail_path, "rb")
+ if thumbnail_file == nil then
+ need_thumbnail_generation = true
+ else
+ local existing_thumbnail_filesize = thumbnail_file:seek("end")
+ if existing_thumbnail_filesize ~= thumbnail_raw_size then
+ -- Size doesn't match, so (re)generate
+ msg.warn("Thumbnail", thumb_idx, "did not match expected size, regenerating")
+ need_thumbnail_generation = true
+ end
+ thumbnail_file:close()
+ end
+
+ if need_thumbnail_generation then
+ local ret = thumbnail_func(file_path, timestamp, thumb_state.thumbnail_size, thumbnail_path, thumb_state.worker_extra)
+ local success = check_output(ret, thumbnail_path, thumbnail_func == create_thumbnail_mpv)
+
+ if success == nil then
+ -- Killed by us, changing files, ignore
+ msg.debug("Changing files, subprocess killed")
+ return true
+ elseif not success then
+ -- Real failure
+ mp.osd_message("Thumbnailing failed, check console for details", 3.5)
+ return true
+ end
+ else
+ msg.debug("Thumbnail", thumb_idx, "already done!")
+ end
+
+ -- Verify thumbnail size
+ -- Sometimes ffmpeg will output an empty file when seeking to a "bad" section (usually the end)
+ thumbnail_file = io.open(thumbnail_path, "rb")
+
+ -- Bail if we can't read the file (it should really exist by now, we checked this in check_output!)
+ if thumbnail_file == nil then
+ msg.error("Thumbnail suddenly disappeared!")
+ return true
+ end
+
+ -- Check the size of the generated file
+ local thumbnail_file_size = thumbnail_file:seek("end")
+ thumbnail_file:close()
+
+ -- Check if the file is big enough
+ local missing_bytes = math.max(0, thumbnail_raw_size - thumbnail_file_size)
+ if missing_bytes > 0 then
+ msg.warn(("Thumbnail missing %d bytes (expected %d, had %d), padding %s"):format(
+ missing_bytes, thumbnail_raw_size, thumbnail_file_size, thumbnail_path
+ ))
+ -- Pad the file if it's missing content (eg. ffmpeg seek to file end)
+ thumbnail_file = io.open(thumbnail_path, "ab")
+ thumbnail_file:write(string.rep(string.char(0), missing_bytes))
+ thumbnail_file:close()
+ end
+
+ msg.debug("Finished work on thumbnail", thumb_idx)
+ mp.commandv("script-message", "mpv_thumbnail_script-ready", tostring(thumbnail_index), thumbnail_path)
+ end
+
+ msg.debug(("Generating %d thumbnails @ %dx%d for %q"):format(
+ #thumbnail_indexes,
+ thumb_state.thumbnail_size.w,
+ thumb_state.thumbnail_size.h,
+ file_path))
+
+ for i, thumbnail_index in ipairs(thumbnail_indexes) do
+ local bail = generate_thumbnail_for_index(thumbnail_index)
+ if bail then return end
+ end
+
+end
+
+-- Set up listeners and keybinds
+
+-- Job listener
+mp.register_script_message("mpv_thumbnail_script-job", do_worker_job)
+
+
+-- Register this worker with the master script
+local register_timer = nil
+local register_timeout = mp.get_time() + 1.5
+
+local register_function = function()
+ if mp.get_time() > register_timeout and register_timer then
+ msg.error("Thumbnail worker registering timed out")
+ register_timer:stop()
+ else
+ msg.debug("Announcing self to master...")
+ mp.commandv("script-message", "mpv_thumbnail_script-worker", mp.get_script_name())
+ end
+end
+
+register_timer = mp.add_periodic_timer(0.1, register_function)
+
+mp.register_script_message("mpv_thumbnail_script-slaved", function()
+ msg.debug("Successfully registered with master")
+ register_timer:stop()
+end)
diff --git a/stow/mpv/dot-config/mpv/scripts/youtube-quality.lua b/stow/mpv/dot-config/mpv/scripts/youtube-quality.lua
new file mode 100644
index 0000000..b587f37
--- /dev/null
+++ b/stow/mpv/dot-config/mpv/scripts/youtube-quality.lua
@@ -0,0 +1,275 @@
+-- youtube-quality.lua
+--
+-- Change youtube video quality on the fly.
+--
+-- Diplays a menu that lets you switch to different ytdl-format settings while
+-- you're in the middle of a video (just like you were using the web player).
+--
+-- Bound to ctrl-f by default.
+
+local mp = require 'mp'
+local utils = require 'mp.utils'
+local msg = require 'mp.msg'
+local assdraw = require 'mp.assdraw'
+
+local opts = {
+ --key bindings
+ toggle_menu_binding = "ctrl+f",
+ up_binding = "UP",
+ down_binding = "DOWN",
+ select_binding = "ENTER",
+
+ --formatting / cursors
+ selected_and_active = "â–¶ - ",
+ selected_and_inactive = "â— - ",
+ unselected_and_active = "â–· - ",
+ unselected_and_inactive = "â—‹ - ",
+
+ --font size scales by window, if false requires larger font and padding sizes
+ scale_playlist_by_window=false,
+
+ --playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
+ --example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
+ --read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
+ --undeclared tags will use default osd settings
+ --these styles will be used for the whole playlist. More specific styling will need to be hacked in
+ --
+ --(a monospaced font is recommended but not required)
+ style_ass_tags = "{\\fnmonospace}",
+
+ --paddings for top left corner
+ text_padding_x = 5,
+ text_padding_y = 5,
+
+ --other
+ menu_timeout = 10,
+
+ --use youtube-dl to fetch a list of available formats (overrides quality_strings)
+ fetch_formats = true,
+
+ --default menu entries
+ quality_strings=[[
+ [
+ {"4320p" : "bestvideo[height<=?4320p]+bestaudio/best"},
+ {"2160p" : "bestvideo[height<=?2160]+bestaudio/best"},
+ {"1440p" : "bestvideo[height<=?1440]+bestaudio/best"},
+ {"1080p" : "bestvideo[height<=?1080]+bestaudio/best"},
+ {"720p" : "bestvideo[height<=?720]+bestaudio/best"},
+ {"480p" : "bestvideo[height<=?480]+bestaudio/best"},
+ {"360p" : "bestvideo[height<=?360]+bestaudio/best"},
+ {"240p" : "bestvideo[height<=?240]+bestaudio/best"},
+ {"144p" : "bestvideo[height<=?144]+bestaudio/best"}
+ ]
+ ]],
+}
+(require 'mp.options').read_options(opts, "youtube-quality")
+opts.quality_strings = utils.parse_json(opts.quality_strings)
+
+local destroyer = nil
+
+
+function show_menu()
+ local selected = 1
+ local active = 0
+ local current_ytdl_format = mp.get_property("ytdl-format")
+ msg.verbose("current ytdl-format: "..current_ytdl_format)
+ local num_options = 0
+ local options = {}
+
+
+ if opts.fetch_formats then
+ options, num_options = download_formats()
+ end
+
+ if next(options) == nil then
+ for i,v in ipairs(opts.quality_strings) do
+ num_options = num_options + 1
+ for k,v2 in pairs(v) do
+ options[i] = {label = k, format=v2}
+ if v2 == current_ytdl_format then
+ active = i
+ selected = active
+ end
+ end
+ end
+ end
+
+ --set the cursor to the currently format
+ for i,v in ipairs(options) do
+ if v.format == current_ytdl_format then
+ active = i
+ selected = active
+ break
+ end
+ end
+
+ function selected_move(amt)
+ selected = selected + amt
+ if selected < 1 then selected = num_options
+ elseif selected > num_options then selected = 1 end
+ timeout:kill()
+ timeout:resume()
+ draw_menu()
+ end
+ function choose_prefix(i)
+ if i == selected and i == active then return opts.selected_and_active
+ elseif i == selected then return opts.selected_and_inactive end
+
+ if i ~= selected and i == active then return opts.unselected_and_active
+ elseif i ~= selected then return opts.unselected_and_inactive end
+ return "> " --shouldn't get here.
+ end
+
+ function draw_menu()
+ local ass = assdraw.ass_new()
+
+ ass:pos(opts.text_padding_x, opts.text_padding_y)
+ ass:append(opts.style_ass_tags)
+
+ for i,v in ipairs(options) do
+ ass:append(choose_prefix(i)..v.label.."\\N")
+ end
+
+ local w, h = mp.get_osd_size()
+ if opts.scale_playlist_by_window then w,h = 0, 0 end
+ mp.set_osd_ass(w, h, ass.text)
+ end
+
+ function destroy()
+ timeout:kill()
+ mp.set_osd_ass(0,0,"")
+ mp.remove_key_binding("move_up")
+ mp.remove_key_binding("move_down")
+ mp.remove_key_binding("select")
+ mp.remove_key_binding("escape")
+ destroyer = nil
+ end
+ timeout = mp.add_periodic_timer(opts.menu_timeout, destroy)
+ destroyer = destroy
+
+ mp.add_forced_key_binding(opts.up_binding, "move_up", function() selected_move(-1) end, {repeatable=true})
+ mp.add_forced_key_binding(opts.down_binding, "move_down", function() selected_move(1) end, {repeatable=true})
+ mp.add_forced_key_binding(opts.select_binding, "select", function()
+ destroy()
+ mp.set_property("ytdl-format", options[selected].format)
+ reload_resume()
+ end)
+ mp.add_forced_key_binding(opts.toggle_menu_binding, "escape", destroy)
+
+ draw_menu()
+ return
+end
+
+local ytdl = {
+ path = "youtube-dl",
+ searched = false,
+ blacklisted = {}
+}
+
+format_cache={}
+function download_formats()
+ local function exec(args)
+ local ret = utils.subprocess({args = args})
+ return ret.status, ret.stdout, ret
+ end
+
+ local function table_size(t)
+ s = 0
+ for i,v in ipairs(t) do
+ s = s+1
+ end
+ return s
+ end
+
+ local url = mp.get_property("path")
+
+ url = string.gsub(url, "ytdl://", "") -- Strip possible ytdl:// prefix.
+
+ -- don't fetch the format list if we already have it
+ if format_cache[url] ~= nil then
+ local res = format_cache[url]
+ return res, table_size(res)
+ end
+ mp.osd_message("fetching available formats with youtube-dl...", 60)
+
+ if not (ytdl.searched) then
+ local ytdl_mcd = mp.find_config_file("youtube-dl")
+ if not (ytdl_mcd == nil) then
+ msg.verbose("found youtube-dl at: " .. ytdl_mcd)
+ ytdl.path = ytdl_mcd
+ end
+ ytdl.searched = true
+ end
+
+ local command = {ytdl.path, "--no-warnings", "--no-playlist", "-J"}
+ table.insert(command, url)
+ local es, json, result = exec(command)
+
+ if (es < 0) or (json == nil) or (json == "") then
+ mp.osd_message("fetching formats failed...", 1)
+ msg.error("failed to get format list: " .. err)
+ return {}, 0
+ end
+
+ local json, err = utils.parse_json(json)
+
+ if (json == nil) then
+ mp.osd_message("fetching formats failed...", 1)
+ msg.error("failed to parse JSON data: " .. err)
+ return {}, 0
+ end
+
+ res = {}
+ msg.verbose("youtube-dl succeeded!")
+ for i,v in ipairs(json.formats) do
+ if v.vcodec ~= "none" then
+ local fps = v.fps and v.fps.."fps" or ""
+ local resolution = string.format("%sx%s", v.width, v.height)
+ local l = string.format("%-9s %-5s (%-4s / %s)", resolution, fps, v.ext, v.vcodec)
+ local f = string.format("%s+bestaudio/best", v.format_id)
+ table.insert(res, {label=l, format=f, width=v.width })
+ end
+ end
+
+ table.sort(res, function(a, b) return a.width > b.width end)
+
+ mp.osd_message("", 0)
+ format_cache[url] = res
+ return res, table_size(res)
+end
+
+
+-- register script message to show menu
+mp.register_script_message("toggle-quality-menu",
+function()
+ if destroyer ~= nil then
+ destroyer()
+ else
+ show_menu()
+ end
+end)
+
+-- keybind to launch menu
+mp.add_key_binding(opts.toggle_menu_binding, "quality-menu", show_menu)
+
+-- special thanks to reload.lua (https://github.com/4e6/mpv-reload/)
+function reload_resume()
+ local playlist_pos = mp.get_property_number("playlist-pos")
+ local reload_duration = mp.get_property_native("duration")
+ local time_pos = mp.get_property("time-pos")
+
+ mp.set_property_number("playlist-pos", playlist_pos)
+
+ -- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero
+ -- duration property. When reloading VOD, to keep the current time position
+ -- we should provide offset from the start. Stream doesn't have fixed start.
+ -- Decent choice would be to reload stream from it's current 'live' positon.
+ -- That's the reason we don't pass the offset when reloading streams.
+ if reload_duration and reload_duration > 0 then
+ local function seeker()
+ mp.commandv("seek", time_pos, "absolute")
+ mp.unregister_event(seeker)
+ end
+ mp.register_event("file-loaded", seeker)
+ end
+end
diff --git a/stow/neofetch/dot-config/neofetch/config.conf b/stow/neofetch/dot-config/neofetch/config.conf
new file mode 100644
index 0000000..41e77d4
--- /dev/null
+++ b/stow/neofetch/dot-config/neofetch/config.conf
@@ -0,0 +1,764 @@
+# See this wiki page for more info:
+# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info
+print_info() {
+ info title
+ info underline
+
+ info "OS" distro
+ info "Host" model
+ info "Kernel" kernel
+ info "Uptime" uptime
+ info "Packages" packages
+ info "Shell" shell
+ info "Resolution" resolution
+ info "DE" de
+ info "WM" wm
+ info "WM Theme" wm_theme
+ info "Theme" theme
+ info "Icons" icons
+ info "Terminal" term
+ info "Terminal Font" term_font
+ info "CPU" cpu
+ info "GPU" gpu
+ info "Memory" memory
+
+ # info "GPU Driver" gpu_driver # Linux/macOS only
+ # info "CPU Usage" cpu_usage
+ # info "Disk" disk
+ # info "Battery" battery
+ # info "Font" font
+ # info "Song" song
+ # [[ "$player" ]] && prin "Music Player" "$player"
+ # info "Local IP" local_ip
+ # info "Public IP" public_ip
+ # info "Users" users
+ # info "Locale" locale # This only works on glibc systems.
+
+ info cols
+}
+
+
+# Kernel
+
+
+# Shorten the output of the kernel function.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --kernel_shorthand
+# Supports: Everything except *BSDs (except PacBSD and PC-BSD)
+#
+# Example:
+# on: '4.8.9-1-ARCH'
+# off: 'Linux 4.8.9-1-ARCH'
+kernel_shorthand="on"
+
+
+# Distro
+
+
+# Shorten the output of the distro function
+#
+# Default: 'off'
+# Values: 'on', 'tiny', 'off'
+# Flag: --distro_shorthand
+# Supports: Everything except Windows and Haiku
+distro_shorthand="off"
+
+# Show/Hide OS Architecture.
+# Show 'x86_64', 'x86' and etc in 'Distro:' output.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --os_arch
+#
+# Example:
+# on: 'Arch Linux x86_64'
+# off: 'Arch Linux'
+os_arch="on"
+
+
+# Uptime
+
+
+# Shorten the output of the uptime function
+#
+# Default: 'on'
+# Values: 'on', 'tiny', 'off'
+# Flag: --uptime_shorthand
+#
+# Example:
+# on: '2 days, 10 hours, 3 mins'
+# tiny: '2d 10h 3m'
+# off: '2 days, 10 hours, 3 minutes'
+uptime_shorthand="on"
+
+
+# Memory
+
+
+# Show memory pecentage in output.
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --memory_percent
+#
+# Example:
+# on: '1801MiB / 7881MiB (22%)'
+# off: '1801MiB / 7881MiB'
+memory_percent="off"
+
+
+# Packages
+
+
+# Show/Hide Package Manager names.
+#
+# Default: 'tiny'
+# Values: 'on', 'tiny' 'off'
+# Flag: --package_managers
+#
+# Example:
+# on: '998 (pacman), 8 (flatpak), 4 (snap)'
+# tiny: '908 (pacman, flatpak, snap)'
+# off: '908'
+package_managers="on"
+
+
+# Shell
+
+
+# Show the path to $SHELL
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --shell_path
+#
+# Example:
+# on: '/bin/bash'
+# off: 'bash'
+shell_path="off"
+
+# Show $SHELL version
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --shell_version
+#
+# Example:
+# on: 'bash 4.4.5'
+# off: 'bash'
+shell_version="on"
+
+
+# CPU
+
+
+# CPU speed type
+#
+# Default: 'bios_limit'
+# Values: 'scaling_cur_freq', 'scaling_min_freq', 'scaling_max_freq', 'bios_limit'.
+# Flag: --speed_type
+# Supports: Linux with 'cpufreq'
+# NOTE: Any file in '/sys/devices/system/cpu/cpu0/cpufreq' can be used as a value.
+speed_type="bios_limit"
+
+# CPU speed shorthand
+#
+# Default: 'off'
+# Values: 'on', 'off'.
+# Flag: --speed_shorthand
+# NOTE: This flag is not supported in systems with CPU speed less than 1 GHz
+#
+# Example:
+# on: 'i7-6500U (4) @ 3.1GHz'
+# off: 'i7-6500U (4) @ 3.100GHz'
+speed_shorthand="off"
+
+# Enable/Disable CPU brand in output.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --cpu_brand
+#
+# Example:
+# on: 'Intel i7-6500U'
+# off: 'i7-6500U (4)'
+cpu_brand="on"
+
+# CPU Speed
+# Hide/Show CPU speed.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --cpu_speed
+#
+# Example:
+# on: 'Intel i7-6500U (4) @ 3.1GHz'
+# off: 'Intel i7-6500U (4)'
+cpu_speed="on"
+
+# CPU Cores
+# Display CPU cores in output
+#
+# Default: 'logical'
+# Values: 'logical', 'physical', 'off'
+# Flag: --cpu_cores
+# Support: 'physical' doesn't work on BSD.
+#
+# Example:
+# logical: 'Intel i7-6500U (4) @ 3.1GHz' (All virtual cores)
+# physical: 'Intel i7-6500U (2) @ 3.1GHz' (All physical cores)
+# off: 'Intel i7-6500U @ 3.1GHz'
+cpu_cores="logical"
+
+# CPU Temperature
+# Hide/Show CPU temperature.
+# Note the temperature is added to the regular CPU function.
+#
+# Default: 'off'
+# Values: 'C', 'F', 'off'
+# Flag: --cpu_temp
+# Supports: Linux, BSD
+# NOTE: For FreeBSD and NetBSD-based systems, you'll need to enable
+# coretemp kernel module. This only supports newer Intel processors.
+#
+# Example:
+# C: 'Intel i7-6500U (4) @ 3.1GHz [27.2°C]'
+# F: 'Intel i7-6500U (4) @ 3.1GHz [82.0°F]'
+# off: 'Intel i7-6500U (4) @ 3.1GHz'
+cpu_temp="off"
+
+
+# GPU
+
+
+# Enable/Disable GPU Brand
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gpu_brand
+#
+# Example:
+# on: 'AMD HD 7950'
+# off: 'HD 7950'
+gpu_brand="on"
+
+# Which GPU to display
+#
+# Default: 'all'
+# Values: 'all', 'dedicated', 'integrated'
+# Flag: --gpu_type
+# Supports: Linux
+#
+# Example:
+# all:
+# GPU1: AMD HD 7950
+# GPU2: Intel Integrated Graphics
+#
+# dedicated:
+# GPU1: AMD HD 7950
+#
+# integrated:
+# GPU1: Intel Integrated Graphics
+gpu_type="all"
+
+
+# Resolution
+
+
+# Display refresh rate next to each monitor
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --refresh_rate
+# Supports: Doesn't work on Windows.
+#
+# Example:
+# on: '1920x1080 @ 60Hz'
+# off: '1920x1080'
+refresh_rate="off"
+
+
+# Gtk Theme / Icons / Font
+
+
+# Shorten output of GTK Theme / Icons / Font
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --gtk_shorthand
+#
+# Example:
+# on: 'Numix, Adwaita'
+# off: 'Numix [GTK2], Adwaita [GTK3]'
+gtk_shorthand="off"
+
+
+# Enable/Disable gtk2 Theme / Icons / Font
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gtk2
+#
+# Example:
+# on: 'Numix [GTK2], Adwaita [GTK3]'
+# off: 'Adwaita [GTK3]'
+gtk2="on"
+
+# Enable/Disable gtk3 Theme / Icons / Font
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --gtk3
+#
+# Example:
+# on: 'Numix [GTK2], Adwaita [GTK3]'
+# off: 'Numix [GTK2]'
+gtk3="on"
+
+
+# IP Address
+
+
+# Website to ping for the public IP
+#
+# Default: 'http://ident.me'
+# Values: 'url'
+# Flag: --ip_host
+public_ip_host="http://ident.me"
+
+# Public IP timeout.
+#
+# Default: '2'
+# Values: 'int'
+# Flag: --ip_timeout
+public_ip_timeout=2
+
+
+# Disk
+
+
+# Which disks to display.
+# The values can be any /dev/sdXX, mount point or directory.
+# NOTE: By default we only show the disk info for '/'.
+#
+# Default: '/'
+# Values: '/', '/dev/sdXX', '/path/to/drive'.
+# Flag: --disk_show
+#
+# Example:
+# disk_show=('/' '/dev/sdb1'):
+# 'Disk (/): 74G / 118G (66%)'
+# 'Disk (/mnt/Videos): 823G / 893G (93%)'
+#
+# disk_show=('/'):
+# 'Disk (/): 74G / 118G (66%)'
+#
+disk_show=('/')
+
+# Disk subtitle.
+# What to append to the Disk subtitle.
+#
+# Default: 'mount'
+# Values: 'mount', 'name', 'dir'
+# Flag: --disk_subtitle
+#
+# Example:
+# name: 'Disk (/dev/sda1): 74G / 118G (66%)'
+# 'Disk (/dev/sdb2): 74G / 118G (66%)'
+#
+# mount: 'Disk (/): 74G / 118G (66%)'
+# 'Disk (/mnt/Local Disk): 74G / 118G (66%)'
+# 'Disk (/mnt/Videos): 74G / 118G (66%)'
+#
+# dir: 'Disk (/): 74G / 118G (66%)'
+# 'Disk (Local Disk): 74G / 118G (66%)'
+# 'Disk (Videos): 74G / 118G (66%)'
+disk_subtitle="mount"
+
+
+# Song
+
+
+# Manually specify a music player.
+#
+# Default: 'auto'
+# Values: 'auto', 'player-name'
+# Flag: --music_player
+#
+# Available values for 'player-name':
+#
+# amarok
+# audacious
+# banshee
+# bluemindo
+# clementine
+# cmus
+# deadbeef
+# deepin-music
+# dragon
+# elisa
+# exaile
+# gnome-music
+# gmusicbrowser
+# gogglesmm
+# guayadeque
+# iTunes
+# juk
+# lollypop
+# mocp
+# mopidy
+# mpd
+# netease-cloud-music
+# pogo
+# pragha
+# qmmp
+# quodlibet
+# rhythmbox
+# sayonara
+# smplayer
+# spotify
+# strawberry
+# tomahawk
+# vlc
+# xmms2d
+# xnoise
+# yarock
+music_player="auto"
+
+# Format to display song information.
+#
+# Default: '%artist% - %album% - %title%'
+# Values: '%artist%', '%album%', '%title%'
+# Flag: --song_format
+#
+# Example:
+# default: 'Song: Jet - Get Born - Sgt Major'
+song_format="%artist% - %album% - %title%"
+
+# Print the Artist, Album and Title on separate lines
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --song_shorthand
+#
+# Example:
+# on: 'Artist: The Fratellis'
+# 'Album: Costello Music'
+# 'Song: Chelsea Dagger'
+#
+# off: 'Song: The Fratellis - Costello Music - Chelsea Dagger'
+song_shorthand="off"
+
+# 'mpc' arguments (specify a host, password etc).
+#
+# Default: ''
+# Example: mpc_args=(-h HOST -P PASSWORD)
+mpc_args=()
+
+
+# Text Colors
+
+
+# Text Colors
+#
+# Default: 'distro'
+# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num'
+# Flag: --colors
+#
+# Each number represents a different part of the text in
+# this order: 'title', '@', 'underline', 'subtitle', 'colon', 'info'
+#
+# Example:
+# colors=(distro) - Text is colored based on Distro colors.
+# colors=(4 6 1 8 8 6) - Text is colored in the order above.
+colors=(distro)
+
+
+# Text Options
+
+
+# Toggle bold text
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --bold
+bold="on"
+
+# Enable/Disable Underline
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --underline
+underline_enabled="on"
+
+# Underline character
+#
+# Default: '-'
+# Values: 'string'
+# Flag: --underline_char
+underline_char="-"
+
+
+# Info Separator
+# Replace the default separator with the specified string.
+#
+# Default: ':'
+# Flag: --separator
+#
+# Example:
+# separator="->": 'Shell-> bash'
+# separator=" =": 'WM = dwm'
+separator=":"
+
+
+# Color Blocks
+
+
+# Color block range
+# The range of colors to print.
+#
+# Default: '0', '15'
+# Values: 'num'
+# Flag: --block_range
+#
+# Example:
+#
+# Display colors 0-7 in the blocks. (8 colors)
+# neofetch --block_range 0 7
+#
+# Display colors 0-15 in the blocks. (16 colors)
+# neofetch --block_range 0 15
+block_range=(0 15)
+
+# Toggle color blocks
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --color_blocks
+color_blocks="on"
+
+# Color block width in spaces
+#
+# Default: '3'
+# Values: 'num'
+# Flag: --block_width
+block_width=3
+
+# Color block height in lines
+#
+# Default: '1'
+# Values: 'num'
+# Flag: --block_height
+block_height=1
+
+
+# Progress Bars
+
+
+# Bar characters
+#
+# Default: '-', '='
+# Values: 'string', 'string'
+# Flag: --bar_char
+#
+# Example:
+# neofetch --bar_char 'elapsed' 'total'
+# neofetch --bar_char '-' '='
+bar_char_elapsed="-"
+bar_char_total="="
+
+# Toggle Bar border
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --bar_border
+bar_border="on"
+
+# Progress bar length in spaces
+# Number of chars long to make the progress bars.
+#
+# Default: '15'
+# Values: 'num'
+# Flag: --bar_length
+bar_length=15
+
+# Progress bar colors
+# When set to distro, uses your distro's logo colors.
+#
+# Default: 'distro', 'distro'
+# Values: 'distro', 'num'
+# Flag: --bar_colors
+#
+# Example:
+# neofetch --bar_colors 3 4
+# neofetch --bar_colors distro 5
+bar_color_elapsed="distro"
+bar_color_total="distro"
+
+
+# Info display
+# Display a bar with the info.
+#
+# Default: 'off'
+# Values: 'bar', 'infobar', 'barinfo', 'off'
+# Flags: --cpu_display
+# --memory_display
+# --battery_display
+# --disk_display
+#
+# Example:
+# bar: '[---=======]'
+# infobar: 'info [---=======]'
+# barinfo: '[---=======] info'
+# off: 'info'
+cpu_display="off"
+memory_display="off"
+battery_display="off"
+disk_display="off"
+
+
+# Backend Settings
+
+
+# Image backend.
+#
+# Default: 'ascii'
+# Values: 'ascii', 'caca', 'chafa', 'jp2a', 'iterm2', 'off',
+# 'termpix', 'pixterm', 'tycat', 'w3m', 'kitty'
+# Flag: --backend
+image_backend="ascii"
+
+# Image Source
+#
+# Which image or ascii file to display.
+#
+# Default: 'auto'
+# Values: 'auto', 'ascii', 'wallpaper', '/path/to/img', '/path/to/ascii', '/path/to/dir/'
+# 'command output (neofetch --ascii "$(fortune | cowsay -W 30)")'
+# Flag: --source
+#
+# NOTE: 'auto' will pick the best image source for whatever image backend is used.
+# In ascii mode, distro ascii art will be used and in an image mode, your
+# wallpaper will be used.
+image_source="auto"
+
+
+# Ascii Options
+
+
+# Ascii distro
+# Which distro's ascii art to display.
+#
+# Default: 'auto'
+# Values: 'auto', 'distro_name'
+# Flag: --ascii_distro
+#
+# NOTE: Arch and Ubuntu have 'old' logo variants.
+# Change this to 'arch_old' or 'ubuntu_old' to use the old logos.
+# NOTE: Ubuntu has flavor variants.
+# Change this to 'Lubuntu', 'Xubuntu', 'Ubuntu-GNOME' or 'Ubuntu-Budgie' to use the flavors.
+# NOTE: Arch, Crux and Gentoo have a smaller logo variant.
+# Change this to 'arch_small', 'crux_small' or 'gentoo_small' to use the small logos.
+ascii_distro="auto"
+
+# Ascii Colors
+#
+# Default: 'distro'
+# Values: 'distro', 'num' 'num' 'num' 'num' 'num' 'num'
+# Flag: --ascii_colors
+#
+# Example:
+# ascii_colors=(distro) - Ascii is colored based on Distro colors.
+# ascii_colors=(4 6 1 8 8 6) - Ascii is colored using these colors.
+ascii_colors=(distro)
+
+# Bold ascii logo
+# Whether or not to bold the ascii logo.
+#
+# Default: 'on'
+# Values: 'on', 'off'
+# Flag: --ascii_bold
+ascii_bold="on"
+
+
+# Image Options
+
+
+# Image loop
+# Setting this to on will make neofetch redraw the image constantly until
+# Ctrl+C is pressed. This fixes display issues in some terminal emulators.
+#
+# Default: 'off'
+# Values: 'on', 'off'
+# Flag: --loop
+image_loop="off"
+
+# Thumbnail directory
+#
+# Default: '~/.cache/thumbnails/neofetch'
+# Values: 'dir'
+thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch"
+
+# Crop mode
+#
+# Default: 'normal'
+# Values: 'normal', 'fit', 'fill'
+# Flag: --crop_mode
+#
+# See this wiki page to learn about the fit and fill options.
+# https://github.com/dylanaraps/neofetch/wiki/What-is-Waifu-Crop%3F
+crop_mode="normal"
+
+# Crop offset
+# Note: Only affects 'normal' crop mode.
+#
+# Default: 'center'
+# Values: 'northwest', 'north', 'northeast', 'west', 'center'
+# 'east', 'southwest', 'south', 'southeast'
+# Flag: --crop_offset
+crop_offset="center"
+
+# Image size
+# The image is half the terminal width by default.
+#
+# Default: 'auto'
+# Values: 'auto', '00px', '00%', 'none'
+# Flags: --image_size
+# --size
+image_size="auto"
+
+# Gap between image and text
+#
+# Default: '3'
+# Values: 'num', '-num'
+# Flag: --gap
+gap=3
+
+# Image offsets
+# Only works with the w3m backend.
+#
+# Default: '0'
+# Values: 'px'
+# Flags: --xoffset
+# --yoffset
+yoffset=0
+xoffset=0
+
+# Image background color
+# Only works with the w3m backend.
+#
+# Default: ''
+# Values: 'color', 'blue'
+# Flag: --bg_color
+background_color=
+
+
+# Misc Options
+
+# Stdout mode
+# Turn off all colors and disables image backend (ASCII/Image).
+# Useful for piping into another command.
+# Default: 'off'
+# Values: 'on', 'off'
+stdout="off"
diff --git a/stow/neofetch/dot-config/neofetch/neofetchLogin.conf b/stow/neofetch/dot-config/neofetch/neofetchLogin.conf
new file mode 100644
index 0000000..7750481
--- /dev/null
+++ b/stow/neofetch/dot-config/neofetch/neofetchLogin.conf
@@ -0,0 +1,63 @@
+# See this wiki page for more info:
+# https://github.com/dylanaraps/neofetch/wiki/Customizing-Info
+print_info() {
+ info underline
+ info title
+ info underline
+
+ info "OS" distro
+ info "Kernel" kernel
+ info "Shell" shell
+ info "Resolution" resolution
+ info "Memory" memory
+ info "Disk" disk
+ info "Battery" battery
+ info "Uptime" uptime
+}
+
+
+
+
+ascii_bold="off"
+#ascii_colors=(4 4)
+#ascii_distro="auto"
+ascii_colors=(3)
+ascii_distro="gnu"
+background_color=
+bar_border="on"
+bar_char_elapsed="-"
+bar_char_total="="
+bar_color_elapsed="distro"
+bar_color_total="distro"
+bar_length=15
+battery_display="off"
+bold="on"
+colors=(6 5 4 4 5 6)
+cpu_display="off"
+crop_mode="normal"
+crop_offset="center"
+disk_display="on"
+disk_show=('/' '/home')
+disk_subtitle="mount"
+distro_shorthand="off"
+gap=3
+image_backend="ascii"
+image_loop="off"
+image_size="auto"
+image_source="auto"
+kernel_shorthand="off"
+memory_display="off"
+memory_percent="on"
+os_arch="on"
+refresh_rate="off"
+separator=":"
+shell_path="off"
+shell_version="on"
+speed_type="bios_limit"
+stdout="off"
+thumbnail_dir="${XDG_CACHE_HOME:-${HOME}/.cache}/thumbnails/neofetch"
+underline_char="-"
+underline_enabled="on"
+uptime_shorthand="on"
+xoffset=10
+yoffset=0
diff --git a/stow/newsboat/dot-config/newsboat/config b/stow/newsboat/dot-config/newsboat/config
new file mode 100644
index 0000000..b789f5a
--- /dev/null
+++ b/stow/newsboat/dot-config/newsboat/config
@@ -0,0 +1,11 @@
+bind-key l open
+bind-key l show-urls article
+bind-key h quit
+bind-key j down
+bind-key k up
+bind-key J next-feed
+bind-key K prev-feed
+bind-key g home
+bind-key G end
+
+external-url-viewer urlview
diff --git a/stow/nvim/dot-config/nvim/init.vim b/stow/nvim/dot-config/nvim/init.vim
new file mode 100644
index 0000000..f182e5b
--- /dev/null
+++ b/stow/nvim/dot-config/nvim/init.vim
@@ -0,0 +1,3 @@
+set runtimepath^=~/.vim runtimepath+=~/.vim/after
+let &packpath = &runtimepath
+source ~/.vimrc
diff --git a/stow/pandoc/dot-config/pandoc/luascripts/diagram-generator.lua b/stow/pandoc/dot-config/pandoc/luascripts/diagram-generator.lua
new file mode 100644
index 0000000..4d69ba3
--- /dev/null
+++ b/stow/pandoc/dot-config/pandoc/luascripts/diagram-generator.lua
@@ -0,0 +1,299 @@
+--[[
+ This Lua filter is used to create images with or without captions from
+ code blocks. Currently PlantUML, GraphViz, Tikz, and Python can be
+ processed. For further details, see README.md.
+
+ Thanks to @floriandd2ba and @jgm for the initial implementation of
+ the PlantUML filter, which I used as a template. Thanks also @muxueqz
+ for the code to generate a GraphViz image.
+]]
+
+-- The PlantUML path. If set, uses the environment variable PLANTUML or the
+-- value "plantuml.jar" (local PlantUML version). In order to define a
+-- PlantUML version per pandoc document, use the meta data to define the key
+-- "plantumlPath".
+local plantumlPath = os.getenv("PLANTUML") or "plantuml.jar"
+
+-- The Inkscape path. In order to define an Inkscape version per pandoc
+-- document, use the meta data to define the key "inkscapePath".
+local inkscapePath = os.getenv("INKSCAPE") or "inkscape"
+
+-- The Python path. In order to define a Python version per pandoc document,
+-- use the meta data to define the key "pythonPath".
+local pythonPath = os.getenv("PYTHON")
+
+-- The Python environment's activate script. Can be set on a per document
+-- basis by using the meta data key "activatePythonPath".
+local pythonActivatePath = os.getenv("PYTHON_ACTIVATE")
+
+-- The Java path. In order to define a Java version per pandoc document,
+-- use the meta data to define the key "javaPath".
+local javaPath = os.getenv("JAVA_HOME")
+if javaPath then
+ javaPath = javaPath .. package.config:sub(1,1) .. "bin"
+ .. package.config:sub(1,1) .. "java"
+else
+ javaPath = "java"
+end
+
+-- The dot (Graphviz) path. In order to define a dot version per pandoc
+-- document, use the meta data to define the key "dotPath".
+local dotPath = os.getenv("DOT") or "dot"
+
+-- The pdflatex path. In order to define a pdflatex version per pandoc
+-- document, use the meta data to define the key "pdflatexPath".
+local pdflatexPath = os.getenv("PDFLATEX") or "pdflatex"
+
+-- The default format is SVG i.e. vector graphics:
+local filetype = "svg"
+local mimetype = "image/svg+xml"
+
+-- Check for output formats that potentially cannot use SVG
+-- vector graphics. In these cases, we use a different format
+-- such as PNG:
+if FORMAT == "docx" then
+ filetype = "png"
+ mimetype = "image/png"
+elseif FORMAT == "pptx" then
+ filetype = "png"
+ mimetype = "image/png"
+elseif FORMAT == "rtf" then
+ filetype = "png"
+ mimetype = "image/png"
+end
+
+-- Execute the meta data table to determine the paths. This function
+-- must be called first to get the desired path. If one of these
+-- meta options was set, it gets used instead of the corresponding
+-- environment variable:
+function Meta(meta)
+ plantumlPath = meta.plantumlPath or plantumlPath
+ inkscapePath = meta.inkscapePath or inkscapePath
+ pythonPath = meta.pythonPath or pythonPath
+ pythonActivatePath = meta.activatePythonPath or pythonActivatePath
+ javaPath = meta.javaPath or javaPath
+ dotPath = meta.dotPath or dotPath
+ pdflatexPath = meta.pdflatexPath or pdflatexPath
+end
+
+-- Call plantuml.jar with some parameters (cf. PlantUML help):
+local function plantuml(puml, filetype)
+ local final = pandoc.pipe(javaPath, {"-jar", plantumlPath, "-t" .. filetype, "-pipe", "-charset", "UTF8"}, puml)
+ return final
+end
+
+-- Call dot (GraphViz) in order to generate the image
+-- (thanks @muxueqz for this code):
+local function graphviz(code, filetype)
+ local final = pandoc.pipe(dotPath, {"-T" .. filetype}, code)
+ return final
+end
+
+-- Compile LaTeX with Tikz code to an image:
+local function tikz2image(src, filetype, additionalPackages)
+
+ -- Define file names:
+ local outfile = string.format("./tmp-latex/file.%s", filetype)
+ local tmp = "./tmp-latex/file"
+ local tmpDir = "./tmp-latex/"
+
+ -- Ensure, that the tmp directory exists:
+ os.execute("mkdir -p tmp-latex")
+
+ -- Build and write the LaTeX document:
+ local f = io.open(tmp .. ".tex", 'w')
+ f:write("\\documentclass{standalone}\n\\usepackage{tikz}\n")
+
+ -- Any additional package(s) are desired?
+ if additionalPackages then
+ f:write(additionalPackages)
+ end
+
+ f:write("\\begin{document}\n")
+ f:write(src)
+ f:write("\n\\end{document}\n")
+ f:close()
+
+ -- Execute the LaTeX compiler:
+ pandoc.pipe(pdflatexPath, {'-output-directory', tmpDir, tmp}, '')
+
+ -- Build the basic Inkscape command for the conversion:
+ local baseCommand = " --without-gui --file=" .. tmp .. ".pdf"
+ local knownFormat = false
+
+ if filetype == "png" then
+
+ -- Append the subcommands to convert into a PNG file:
+ baseCommand = baseCommand .. " --export-png="
+ .. tmp .. ".png --export-dpi=300"
+ knownFormat = true
+
+ elseif filetype == "svg" then
+
+ -- Append the subcommands to convert into a SVG file:
+ baseCommand = baseCommand .. " --export-plain-svg=" .. tmp .. ".svg"
+ knownFormat = true
+
+ end
+
+ -- Unfortunately, continuation is only possible, if we know the actual
+ -- format:
+ if not knownFormat then
+ error(string.format("Don't know how to convert pdf to %s.", filetype))
+ end
+
+ local imgData = nil
+
+ -- We know the desired format. Thus, execute Inkscape:
+ os.execute("\"" .. inkscapePath .. "\"" .. baseCommand)
+
+ -- Try to open the image:
+ local r = io.open(tmp .. "." .. filetype, 'rb')
+
+ -- Read the image, if available:
+ if r then
+ imgData = r:read("*all")
+ r:close()
+ end
+
+ -- Delete the image tmp file:
+ os.remove(outfile)
+
+ -- Remove the temporary files:
+ os.remove(tmp .. ".tex")
+ os.remove(tmp .. ".pdf")
+ os.remove(tmp .. ".log")
+ os.remove(tmp .. ".aux")
+
+ return imgData
+end
+
+-- Run Python to generate an image:
+local function py2image(code, filetype)
+
+ -- Define the temp files:
+ local outfile = string.format('%s.%s', os.tmpname(), filetype)
+ local pyfile = os.tmpname()
+
+ -- Replace the desired destination's file type in the Python code:
+ local extendedCode = string.gsub(code, "%$FORMAT%$", filetype)
+
+ -- Replace the desired destination's path in the Python code:
+ extendedCode = string.gsub(extendedCode, "%$DESTINATION%$", outfile)
+
+ -- Write the Python code:
+ local f = io.open(pyfile, 'w')
+ f:write(extendedCode)
+ f:close()
+
+ -- Execute Python in the desired environment:
+ local pycmd = pythonPath .. ' ' .. pyfile
+ local command = pythonActivatePath
+ and pythonActivatePath .. ' && ' .. pycmd
+ or pycmd
+ os.execute(command)
+
+ -- Try to open the written image:
+ local r = io.open(outfile, 'rb')
+ local imgData = nil
+
+ -- When the image exist, read it:
+ if r then
+ imgData = r:read("*all")
+ r:close()
+ else
+ io.stderr:write(string.format("File '%s' could not be opened", outfile))
+ error 'Could not create image from python code.'
+ end
+
+ -- Delete the tmp files:
+ os.remove(pyfile)
+ os.remove(outfile)
+
+ return imgData
+end
+
+-- Executes each document's code block to find matching code blocks:
+function CodeBlock(block)
+
+ -- Predefine a potential image:
+ local fname = nil
+
+ -- Using a table with all known generators i.e. converters:
+ local converters = {
+ plantuml = plantuml,
+ graphviz = graphviz,
+ tikz = tikz2image,
+ py2image = py2image,
+ }
+
+ -- Check if a converter exists for this block. If not, return the block
+ -- unchanged.
+ local img_converter = converters[block.classes[1]]
+ if not img_converter then
+ return nil
+ end
+
+ -- Call the correct converter which belongs to the used class:
+ local success, img = pcall(img_converter, block.text,
+ filetype, block.attributes["additionalPackages"] or nil)
+
+ -- Was ok?
+ if success and img then
+ -- Hash the figure name and content:
+ fname = pandoc.sha1(img) .. "." .. filetype
+
+ -- Store the data in the media bag:
+ pandoc.mediabag.insert(fname, mimetype, img)
+
+ else
+
+ -- an error occured; img contains the error message
+ io.stderr:write(tostring(img))
+ io.stderr:write('\n')
+ error 'Image conversion failed. Aborting.'
+
+ end
+
+ -- Case: This code block was an image e.g. PlantUML or dot/Graphviz, etc.:
+ if fname then
+
+ -- Define the default caption:
+ local caption = {}
+ local enableCaption = nil
+
+ -- If the user defines a caption, use it:
+ if block.attributes["caption"] then
+ caption = pandoc.read(block.attributes.caption).blocks[1].content
+
+ -- This is pandoc's current hack to enforce a caption:
+ enableCaption = "fig:"
+ end
+
+ -- Create a new image for the document's structure. Attach the user's
+ -- caption. Also use a hack (fig:) to enforce pandoc to create a
+ -- figure i.e. attach a caption to the image.
+ local imgObj = pandoc.Image(caption, fname, enableCaption)
+
+ -- Now, transfer the attribute "name" from the code block to the new
+ -- image block. It might gets used by the figure numbering lua filter.
+ -- If the figure numbering gets not used, this additional attribute
+ -- gets ignored as well.
+ if block.attributes["name"] then
+ imgObj.attributes["name"] = block.attributes["name"]
+ end
+
+ -- Finally, put the image inside an empty paragraph. By returning the
+ -- resulting paragraph object, the source code block gets replaced by
+ -- the image:
+ return pandoc.Para{ imgObj }
+ end
+end
+
+-- Normally, pandoc will run the function in the built-in order Inlines ->
+-- Blocks -> Meta -> Pandoc. We instead want Meta -> Blocks. Thus, we must
+-- define our custom order:
+return {
+ {Meta = Meta},
+ {CodeBlock = CodeBlock},
+}
diff --git a/stow/picom/dot-config/picom/picom.conf b/stow/picom/dot-config/picom/picom.conf
new file mode 100644
index 0000000..2f4dfc7
--- /dev/null
+++ b/stow/picom/dot-config/picom/picom.conf
@@ -0,0 +1,502 @@
+#################################
+# Animations #
+#################################
+# requires https://github.com/jonaburg/picom
+# (These are also the default values)
+transition-length = 200
+transition-pow-x = 0.5
+transition-pow-y = 0.5
+transition-pow-w = 0.5
+transition-pow-h = 0.5
+size-transition = true
+
+
+#################################
+# Corners #
+#################################
+# requires: https://github.com/sdhand/compton or https://github.com/jonaburg/picom
+corner-radius = 0;
+rounded-corners-exclude = [
+ #"window_type = 'normal'",
+ "class_g = 'awesome'",
+ #"class_g = 'URxvt'",
+ "class_g = 'i3bar'",
+ "class_g = 'dwm'",
+ "class_g = 'XTerm'",
+ "class_g = 'kitty'",
+ "class_g = 'Alacritty'",
+ "class_g = 'Polybar'",
+ "class_g = 'code-oss'",
+ #"class_g = 'TelegramDesktop'",
+ "class_g = 'firefox'",
+ "class_g = 'Thunderbird'"
+];
+round-borders = 0;
+round-borders-exclude = [
+ #"class_g = 'TelegramDesktop'",
+];
+
+#################################
+# Shadows #
+#################################
+
+
+# Enabled client-side shadows on windows. Note desktop windows
+# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
+# unless explicitly requested using the wintypes option.
+#
+# shadow = false
+shadow = true;
+
+# The blur radius for shadows, in pixels. (defaults to 12)
+# shadow-radius = 12
+shadow-radius = 12;
+
+# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+# shadow-opacity = .75
+
+# The left offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-x = -15
+shadow-offset-x = -7;
+
+# The top offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-y = -15
+shadow-offset-y = -7;
+
+# Avoid drawing shadows on dock/panel windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dock-shadow = false
+
+# Don't draw shadows on drag-and-drop windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dnd-shadow = false
+
+# Red color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-red = 0
+
+# Green color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-green = 0
+
+# Blue color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-blue = 0
+
+# Do not paint shadows on shaped windows. Note shaped windows
+# here means windows setting its shape through X Shape extension.
+# Those using ARGB background is beyond our control.
+# Deprecated, use
+# shadow-exclude = 'bounding_shaped'
+# or
+# shadow-exclude = 'bounding_shaped && !rounded_corners'
+# instead.
+#
+# shadow-ignore-shaped = ''
+
+# Specify a list of conditions of windows that should have no shadow.
+#
+# examples:
+# shadow-exclude = "n:e:Notification";
+#
+# shadow-exclude = []
+shadow-exclude = [
+ "name = 'Notification'",
+ "class_g = 'Conky'",
+ "class_g ?= 'Notify-osd'",
+ "class_g = 'Cairo-clock'",
+ "class_g = 'slop'",
+ "class_g = 'Polybar'",
+ "_GTK_FRAME_EXTENTS@:c"
+];
+
+# Specify a X geometry that describes the region in which shadow should not
+# be painted in, such as a dock window region. Use
+# shadow-exclude-reg = "x10+0+0"
+# for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
+#
+# shadow-exclude-reg = ""
+
+# Crop shadow of a window fully on a particular Xinerama screen to the screen.
+# xinerama-shadow-crop = false
+
+
+#################################
+# Fading #
+#################################
+
+
+# Fade windows in/out when opening/closing and when opacity changes,
+# unless no-fading-openclose is used.
+# fading = false
+fading = true;
+
+# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+# fade-in-step = 0.028
+fade-in-step = 0.03;
+
+# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+# fade-out-step = 0.03
+fade-out-step = 0.03;
+
+# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+# fade-delta = 10
+
+# Specify a list of conditions of windows that should not be faded.
+# don't need this, we disable fading for all normal windows with wintypes: {}
+fade-exclude = [
+ "class_g = 'slop'" # maim
+]
+
+# Do not fade on window open/close.
+# no-fading-openclose = false
+
+# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
+# no-fading-destroyed-argb = false
+
+
+#################################
+# Transparency / Opacity #
+#################################
+
+# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
+# inactive-opacity = 1
+inactive-opacity = 0.8;
+
+# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+# frame-opacity = 1.0
+frame-opacity = 0.7;
+
+# Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0)
+# menu-opacity = 1.0
+# menu-opacity is depreciated use dropdown-menu and popup-menu instead.
+
+#If using these 2 below change their values in line 510 & 511 aswell
+popup_menu = { opacity = 0.8; }
+dropdown_menu = { opacity = 0.8; }
+
+
+# Let inactive opacity set by -i override the '_NET_WM_OPACITY' values of windows.
+# inactive-opacity-override = true
+inactive-opacity-override = false;
+
+# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
+active-opacity = 1.0;
+
+# Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+# inactive-dim = 0.0
+
+# Specify a list of conditions of windows that should always be considered focused.
+# focus-exclude = []
+focus-exclude = [
+ "class_g = 'Cairo-clock'",
+ "class_g = 'Bar'", # lemonbar
+ "class_g = 'slop'" # maim
+];
+
+# Use fixed inactive dim value, instead of adjusting according to window opacity.
+# inactive-dim-fixed = 1.0
+
+# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
+# like `50:name *= "Firefox"`. picom-trans is recommended over this.
+# Note we don't make any guarantee about possible conflicts with other
+# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
+# example:
+# opacity-rule = [ "80:class_g = 'URxvt'" ];
+#
+# opacity-rule = []
+opacity-rule = [
+ "90:class_g = 'URxvt' && !focused",
+ "80:class_g = 'dmenu'"
+];
+
+
+#################################
+# Background-Blurring #
+#################################
+
+
+# Parameters for background blurring, see the *BLUR* section for more information.
+# blur-method =
+# blur-size = 12
+#
+# blur-deviation = false
+
+# Blur background of semi-transparent / ARGB windows.
+# Bad in performance, with driver-dependent behavior.
+# The name of the switch may change without prior notifications.
+#
+# blur-background = true;
+
+# Blur background of windows when the window frame is not opaque.
+# Implies:
+# blur-background
+# Bad in performance, with driver-dependent behavior. The name may change.
+#
+# blur-background-frame = false;
+
+
+# Use fixed blur strength rather than adjusting according to window opacity.
+# blur-background-fixed = false;
+
+
+# Specify the blur convolution kernel, with the following format:
+# example:
+# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
+#
+# blur-kern = ''
+# blur-kern = "3x3box";
+
+blur: {
+ # requires: https://github.com/ibhagwan/picom
+ method = "kawase";
+ #method = "kernel";
+ strength = 3;
+ # deviation = 1.0;
+ # kernel = "11x11gaussian";
+ background = false;
+ background-frame = false;
+ background-fixed = false;
+ kern = "3x3box";
+}
+
+# Exclude conditions for background blur.
+blur-background-exclude = [
+ #"window_type = 'dock'",
+ #"window_type = 'desktop'",
+ #"class_g = 'URxvt'",
+ #
+ # prevents picom from blurring the background
+ # when taking selection screenshot with `main`
+ # https://github.com/naelstrof/maim/issues/130
+ "class_g = 'slop'",
+ "_GTK_FRAME_EXTENTS@:c"
+];
+
+
+#################################
+# General Settings #
+#################################
+
+# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
+# daemon = false
+
+# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`.
+# `xrender` is the default one.
+#
+experimental-backends = true;
+backend = "glx";
+#backend = "xrender";
+
+
+# Enable/disable VSync.
+# vsync = false
+vsync = true
+
+# Enable remote control via D-Bus. See the *D-BUS API* section below for more details.
+# dbus = false
+
+# Try to detect WM windows (a non-override-redirect window with no
+# child that has 'WM_STATE') and mark them as active.
+#
+# mark-wmwin-focused = false
+mark-wmwin-focused = true;
+
+# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
+# mark-ovredir-focused = false
+mark-ovredir-focused = true;
+
+# Try to detect windows with rounded corners and don't consider them
+# shaped windows. The accuracy is not very high, unfortunately.
+#
+# detect-rounded-corners = false
+detect-rounded-corners = true;
+
+# Detect '_NET_WM_OPACITY' on client windows, useful for window managers
+# not passing '_NET_WM_OPACITY' of client windows to frame windows.
+#
+# detect-client-opacity = false
+detect-client-opacity = true;
+
+# Specify refresh rate of the screen. If not specified or 0, picom will
+# try detecting this with X RandR extension.
+#
+# refresh-rate = 60
+refresh-rate = 0
+
+# Limit picom to repaint at most once every 1 / 'refresh_rate' second to
+# boost performance. This should not be used with
+# vsync drm/opengl/opengl-oml
+# as they essentially does sw-opti's job already,
+# unless you wish to specify a lower refresh rate than the actual value.
+#
+# sw-opti =
+
+# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
+# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
+# provided that the WM supports it.
+#
+# use-ewmh-active-win = false
+
+# Unredirect all windows if a full-screen opaque window is detected,
+# to maximize performance for full-screen windows. Known to cause flickering
+# when redirecting/unredirecting windows. paint-on-overlay may make the flickering less obvious.
+#
+# unredir-if-possible = false
+
+# Delay before unredirecting the window, in milliseconds. Defaults to 0.
+# unredir-if-possible-delay = 0
+
+# Conditions of windows that shouldn't be considered full-screen for unredirecting screen.
+# unredir-if-possible-exclude = []
+
+# Use 'WM_TRANSIENT_FOR' to group windows, and consider windows
+# in the same group focused at the same time.
+#
+# detect-transient = false
+detect-transient = true
+
+# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
+# group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if
+# detect-transient is enabled, too.
+#
+# detect-client-leader = false
+detect-client-leader = true
+
+# Resize damaged region by a specific number of pixels.
+# A positive value enlarges it while a negative one shrinks it.
+# If the value is positive, those additional pixels will not be actually painted
+# to screen, only used in blur calculation, and such. (Due to technical limitations,
+# with use-damage, those pixels will still be incorrectly painted to screen.)
+# Primarily used to fix the line corruption issues of blur,
+# in which case you should use the blur radius value here
+# (e.g. with a 3x3 kernel, you should use `--resize-damage 1`,
+# with a 5x5 one you use `--resize-damage 2`, and so on).
+# May or may not work with *--glx-no-stencil*. Shrinking doesn't function correctly.
+#
+# resize-damage = 1
+
+# Specify a list of conditions of windows that should be painted with inverted color.
+# Resource-hogging, and is not well tested.
+#
+# invert-color-include = []
+
+# GLX backend: Avoid using stencil buffer, useful if you don't have a stencil buffer.
+# Might cause incorrect opacity when rendering transparent content (but never
+# practically happened) and may not work with blur-background.
+# My tests show a 15% performance boost. Recommended.
+#
+# glx-no-stencil = false
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes,
+# but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.).
+# Recommended if it works.
+#
+# glx-no-rebind-pixmap = false
+
+# Disable the use of damage information.
+# This cause the whole screen to be redrawn everytime, instead of the part of the screen
+# has actually changed. Potentially degrades the performance, but might fix some artifacts.
+# The opposing option is use-damage
+#
+# no-use-damage = false
+#use-damage = true (Causing Weird Black semi opaque rectangles when terminal is opened)
+#Changing use-damage to false fixes the problem
+use-damage = false
+
+# Use X Sync fence to sync clients' draw calls, to make sure all draw
+# calls are finished before picom starts drawing. Needed on nvidia-drivers
+# with GLX backend for some users.
+#
+# xrender-sync-fence = false
+
+# GLX backend: Use specified GLSL fragment shader for rendering window contents.
+# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl`
+# in the source tree for examples.
+#
+# glx-fshader-win = ''
+
+# Force all windows to be painted with blending. Useful if you
+# have a glx-fshader-win that could turn opaque pixels transparent.
+#
+# force-win-blend = false
+
+# Do not use EWMH to detect fullscreen windows.
+# Reverts to checking if a window is fullscreen based only on its size and coordinates.
+#
+# no-ewmh-fullscreen = false
+
+# Dimming bright windows so their brightness doesn't exceed this set value.
+# Brightness of a window is estimated by averaging all pixels in the window,
+# so this could comes with a performance hit.
+# Setting this to 1.0 disables this behaviour. Requires --use-damage to be disabled. (default: 1.0)
+#
+# max-brightness = 1.0
+
+# Make transparent windows clip other windows like non-transparent windows do,
+# instead of blending on top of them.
+#
+# transparent-clipping = false
+
+# Set the log level. Possible values are:
+# "trace", "debug", "info", "warn", "error"
+# in increasing level of importance. Case doesn't matter.
+# If using the "TRACE" log level, it's better to log into a file
+# using *--log-file*, since it can generate a huge stream of logs.
+#
+# log-level = "debug"
+log-level = "info";
+
+# Set the log file.
+# If *--log-file* is never specified, logs will be written to stderr.
+# Otherwise, logs will to written to the given file, though some of the early
+# logs might still be written to the stderr.
+# When setting this option from the config file, it is recommended to use an absolute path.
+#
+# log-file = '/path/to/your/log/file'
+
+# Show all X errors (for debugging)
+# show-all-xerrors = false
+
+# Write process ID to a file.
+# write-pid-path = '/path/to/your/log/file'
+
+# Window type settings
+#
+# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard:
+# "unknown", "desktop", "dock", "toolbar", "menu", "utility",
+# "splash", "dialog", "normal", "dropdown_menu", "popup_menu",
+# "tooltip", "notification", "combo", and "dnd".
+#
+# Following per window-type options are available: ::
+#
+# fade, shadow:::
+# Controls window-type-specific shadow and fade settings.
+#
+# opacity:::
+# Controls default opacity of the window type.
+#
+# focus:::
+# Controls whether the window of this type is to be always considered focused.
+# (By default, all window types except "normal" and "dialog" has this on.)
+#
+# full-shadow:::
+# Controls whether shadow is drawn under the parts of the window that you
+# normally won't be able to see. Useful when the window has parts of it
+# transparent, and you want shadows in those areas.
+#
+# redir-ignore:::
+# Controls whether this type of windows should cause screen to become
+# redirected again after been unredirected. If you have unredir-if-possible
+# set, and doesn't want certain window to cause unnecessary screen redirection,
+# you can set this to `true`.
+#
+wintypes:
+{
+ normal = { fade = false; shadow = false; }
+ tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
+ dock = { shadow = false; }
+ dnd = { shadow = false; }
+ popup_menu = { opacity = 0.8; }
+ dropdown_menu = { opacity = 0.8; }
+};
diff --git a/stow/picom/dot-config/picom/picom.jonaburg.sample.conf b/stow/picom/dot-config/picom/picom.jonaburg.sample.conf
new file mode 100644
index 0000000..ee3fbbe
--- /dev/null
+++ b/stow/picom/dot-config/picom/picom.jonaburg.sample.conf
@@ -0,0 +1,512 @@
+#################################
+# Animations #
+#################################
+# requires https://github.com/jonaburg/picom
+# (These are also the default values)
+transition-length = 300
+transition-pow-x = 0.1
+transition-pow-y = 0.1
+transition-pow-w = 0.1
+transition-pow-h = 0.1
+size-transition = true
+
+
+#################################
+# Corners #
+#################################
+# requires: https://github.com/sdhand/compton or https://github.com/jonaburg/picom
+corner-radius = 10.0;
+rounded-corners-exclude = [
+ #"window_type = 'normal'",
+ "class_g = 'awesome'",
+ "class_g = 'URxvt'",
+ "class_g = 'XTerm'",
+ "class_g = 'kitty'",
+ "class_g = 'Alacritty'",
+ "class_g = 'Polybar'",
+ "class_g = 'code-oss'",
+ #"class_g = 'TelegramDesktop'",
+ "class_g = 'firefox'",
+ "class_g = 'Thunderbird'"
+];
+round-borders = 1;
+round-borders-exclude = [
+ #"class_g = 'TelegramDesktop'",
+];
+
+#################################
+# Shadows #
+#################################
+
+
+# Enabled client-side shadows on windows. Note desktop windows
+# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
+# unless explicitly requested using the wintypes option.
+#
+# shadow = false
+shadow = false;
+
+# The blur radius for shadows, in pixels. (defaults to 12)
+# shadow-radius = 12
+shadow-radius = 7;
+
+# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+# shadow-opacity = .75
+
+# The left offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-x = -15
+shadow-offset-x = -7;
+
+# The top offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-y = -15
+shadow-offset-y = -7;
+
+# Avoid drawing shadows on dock/panel windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dock-shadow = false
+
+# Don't draw shadows on drag-and-drop windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dnd-shadow = false
+
+# Red color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-red = 0
+
+# Green color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-green = 0
+
+# Blue color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-blue = 0
+
+# Do not paint shadows on shaped windows. Note shaped windows
+# here means windows setting its shape through X Shape extension.
+# Those using ARGB background is beyond our control.
+# Deprecated, use
+# shadow-exclude = 'bounding_shaped'
+# or
+# shadow-exclude = 'bounding_shaped && !rounded_corners'
+# instead.
+#
+# shadow-ignore-shaped = ''
+
+# Specify a list of conditions of windows that should have no shadow.
+#
+# examples:
+# shadow-exclude = "n:e:Notification";
+#
+# shadow-exclude = []
+shadow-exclude = [
+ "name = 'Notification'",
+ "class_g = 'Conky'",
+ "class_g ?= 'Notify-osd'",
+ "class_g = 'Cairo-clock'",
+ "class_g = 'slop'",
+ "class_g = 'Polybar'",
+ "_GTK_FRAME_EXTENTS@:c"
+];
+
+# Specify a X geometry that describes the region in which shadow should not
+# be painted in, such as a dock window region. Use
+# shadow-exclude-reg = "x10+0+0"
+# for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
+#
+# shadow-exclude-reg = ""
+
+# Crop shadow of a window fully on a particular Xinerama screen to the screen.
+# xinerama-shadow-crop = false
+
+
+#################################
+# Fading #
+#################################
+
+
+# Fade windows in/out when opening/closing and when opacity changes,
+# unless no-fading-openclose is used.
+# fading = false
+fading = true;
+
+# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+# fade-in-step = 0.028
+fade-in-step = 0.03;
+
+# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+# fade-out-step = 0.03
+fade-out-step = 0.03;
+
+# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+# fade-delta = 10
+
+# Specify a list of conditions of windows that should not be faded.
+# don't need this, we disable fading for all normal windows with wintypes: {}
+fade-exclude = [
+ "class_g = 'slop'" # maim
+]
+
+# Do not fade on window open/close.
+# no-fading-openclose = false
+
+# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
+# no-fading-destroyed-argb = false
+
+
+#################################
+# Transparency / Opacity #
+#################################
+
+
+# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
+# inactive-opacity = 1
+inactive-opacity = 0.8;
+
+# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+# frame-opacity = 1.0
+frame-opacity = 0.7;
+
+# Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0)
+# menu-opacity = 1.0
+# menu-opacity is depreciated use dropdown-menu and popup-menu instead.
+
+#If using these 2 below change their values in line 510 & 511 aswell
+popup_menu = { opacity = 0.8; }
+dropdown_menu = { opacity = 0.8; }
+
+
+# Let inactive opacity set by -i override the '_NET_WM_OPACITY' values of windows.
+# inactive-opacity-override = true
+inactive-opacity-override = false;
+
+# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
+active-opacity = 1.0;
+
+# Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+# inactive-dim = 0.0
+
+# Specify a list of conditions of windows that should always be considered focused.
+# focus-exclude = []
+focus-exclude = [
+ "class_g = 'Cairo-clock'",
+ "class_g = 'Bar'", # lemonbar
+ "class_g = 'slop'" # maim
+];
+
+# Use fixed inactive dim value, instead of adjusting according to window opacity.
+# inactive-dim-fixed = 1.0
+
+# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
+# like `50:name *= "Firefox"`. picom-trans is recommended over this.
+# Note we don't make any guarantee about possible conflicts with other
+# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
+# example:
+# opacity-rule = [ "80:class_g = 'URxvt'" ];
+#
+# opacity-rule = []
+opacity-rule = [
+ "80:class_g = 'Bar'", # lemonbar
+ "100:class_g = 'slop'", # maim
+ "100:class_g = 'XTerm'",
+ "100:class_g = 'URxvt'",
+ "100:class_g = 'kitty'",
+ "100:class_g = 'Alacritty'",
+ "80:class_g = 'Polybar'",
+ "100:class_g = 'code-oss'",
+ "100:class_g = 'Meld'",
+ "70:class_g = 'TelegramDesktop'",
+ "90:class_g = 'Joplin'",
+ "100:class_g = 'firefox'",
+ "100:class_g = 'Thunderbird'"
+];
+
+
+#################################
+# Background-Blurring #
+#################################
+
+
+# Parameters for background blurring, see the *BLUR* section for more information.
+# blur-method =
+# blur-size = 12
+#
+# blur-deviation = false
+
+# Blur background of semi-transparent / ARGB windows.
+# Bad in performance, with driver-dependent behavior.
+# The name of the switch may change without prior notifications.
+#
+# blur-background = true;
+
+# Blur background of windows when the window frame is not opaque.
+# Implies:
+# blur-background
+# Bad in performance, with driver-dependent behavior. The name may change.
+#
+# blur-background-frame = false;
+
+
+# Use fixed blur strength rather than adjusting according to window opacity.
+# blur-background-fixed = false;
+
+
+# Specify the blur convolution kernel, with the following format:
+# example:
+# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
+#
+# blur-kern = ''
+# blur-kern = "3x3box";
+
+blur: {
+ # requires: https://github.com/ibhagwan/picom
+ method = "kawase";
+ #method = "kernel";
+ strength = 7;
+ # deviation = 1.0;
+ # kernel = "11x11gaussian";
+ background = false;
+ background-frame = false;
+ background-fixed = false;
+ kern = "3x3box";
+}
+
+# Exclude conditions for background blur.
+blur-background-exclude = [
+ #"window_type = 'dock'",
+ #"window_type = 'desktop'",
+ #"class_g = 'URxvt'",
+ #
+ # prevents picom from blurring the background
+ # when taking selection screenshot with `main`
+ # https://github.com/naelstrof/maim/issues/130
+ "class_g = 'slop'",
+ "_GTK_FRAME_EXTENTS@:c"
+];
+
+
+#################################
+# General Settings #
+#################################
+
+# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
+# daemon = false
+
+# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`.
+# `xrender` is the default one.
+#
+experimental-backends = true;
+backend = "glx";
+#backend = "xrender";
+
+
+# Enable/disable VSync.
+# vsync = false
+vsync = true
+
+# Enable remote control via D-Bus. See the *D-BUS API* section below for more details.
+# dbus = false
+
+# Try to detect WM windows (a non-override-redirect window with no
+# child that has 'WM_STATE') and mark them as active.
+#
+# mark-wmwin-focused = false
+mark-wmwin-focused = true;
+
+# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
+# mark-ovredir-focused = false
+mark-ovredir-focused = true;
+
+# Try to detect windows with rounded corners and don't consider them
+# shaped windows. The accuracy is not very high, unfortunately.
+#
+# detect-rounded-corners = false
+detect-rounded-corners = true;
+
+# Detect '_NET_WM_OPACITY' on client windows, useful for window managers
+# not passing '_NET_WM_OPACITY' of client windows to frame windows.
+#
+# detect-client-opacity = false
+detect-client-opacity = true;
+
+# Specify refresh rate of the screen. If not specified or 0, picom will
+# try detecting this with X RandR extension.
+#
+# refresh-rate = 60
+refresh-rate = 0
+
+# Limit picom to repaint at most once every 1 / 'refresh_rate' second to
+# boost performance. This should not be used with
+# vsync drm/opengl/opengl-oml
+# as they essentially does sw-opti's job already,
+# unless you wish to specify a lower refresh rate than the actual value.
+#
+# sw-opti =
+
+# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
+# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
+# provided that the WM supports it.
+#
+# use-ewmh-active-win = false
+
+# Unredirect all windows if a full-screen opaque window is detected,
+# to maximize performance for full-screen windows. Known to cause flickering
+# when redirecting/unredirecting windows. paint-on-overlay may make the flickering less obvious.
+#
+# unredir-if-possible = false
+
+# Delay before unredirecting the window, in milliseconds. Defaults to 0.
+# unredir-if-possible-delay = 0
+
+# Conditions of windows that shouldn't be considered full-screen for unredirecting screen.
+# unredir-if-possible-exclude = []
+
+# Use 'WM_TRANSIENT_FOR' to group windows, and consider windows
+# in the same group focused at the same time.
+#
+# detect-transient = false
+detect-transient = true
+
+# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
+# group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if
+# detect-transient is enabled, too.
+#
+# detect-client-leader = false
+detect-client-leader = true
+
+# Resize damaged region by a specific number of pixels.
+# A positive value enlarges it while a negative one shrinks it.
+# If the value is positive, those additional pixels will not be actually painted
+# to screen, only used in blur calculation, and such. (Due to technical limitations,
+# with use-damage, those pixels will still be incorrectly painted to screen.)
+# Primarily used to fix the line corruption issues of blur,
+# in which case you should use the blur radius value here
+# (e.g. with a 3x3 kernel, you should use `--resize-damage 1`,
+# with a 5x5 one you use `--resize-damage 2`, and so on).
+# May or may not work with *--glx-no-stencil*. Shrinking doesn't function correctly.
+#
+# resize-damage = 1
+
+# Specify a list of conditions of windows that should be painted with inverted color.
+# Resource-hogging, and is not well tested.
+#
+# invert-color-include = []
+
+# GLX backend: Avoid using stencil buffer, useful if you don't have a stencil buffer.
+# Might cause incorrect opacity when rendering transparent content (but never
+# practically happened) and may not work with blur-background.
+# My tests show a 15% performance boost. Recommended.
+#
+# glx-no-stencil = false
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes,
+# but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.).
+# Recommended if it works.
+#
+# glx-no-rebind-pixmap = false
+
+# Disable the use of damage information.
+# This cause the whole screen to be redrawn everytime, instead of the part of the screen
+# has actually changed. Potentially degrades the performance, but might fix some artifacts.
+# The opposing option is use-damage
+#
+# no-use-damage = false
+#use-damage = true (Causing Weird Black semi opaque rectangles when terminal is opened)
+#Changing use-damage to false fixes the problem
+use-damage = false
+
+# Use X Sync fence to sync clients' draw calls, to make sure all draw
+# calls are finished before picom starts drawing. Needed on nvidia-drivers
+# with GLX backend for some users.
+#
+# xrender-sync-fence = false
+
+# GLX backend: Use specified GLSL fragment shader for rendering window contents.
+# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl`
+# in the source tree for examples.
+#
+# glx-fshader-win = ''
+
+# Force all windows to be painted with blending. Useful if you
+# have a glx-fshader-win that could turn opaque pixels transparent.
+#
+# force-win-blend = false
+
+# Do not use EWMH to detect fullscreen windows.
+# Reverts to checking if a window is fullscreen based only on its size and coordinates.
+#
+# no-ewmh-fullscreen = false
+
+# Dimming bright windows so their brightness doesn't exceed this set value.
+# Brightness of a window is estimated by averaging all pixels in the window,
+# so this could comes with a performance hit.
+# Setting this to 1.0 disables this behaviour. Requires --use-damage to be disabled. (default: 1.0)
+#
+# max-brightness = 1.0
+
+# Make transparent windows clip other windows like non-transparent windows do,
+# instead of blending on top of them.
+#
+# transparent-clipping = false
+
+# Set the log level. Possible values are:
+# "trace", "debug", "info", "warn", "error"
+# in increasing level of importance. Case doesn't matter.
+# If using the "TRACE" log level, it's better to log into a file
+# using *--log-file*, since it can generate a huge stream of logs.
+#
+# log-level = "debug"
+log-level = "info";
+
+# Set the log file.
+# If *--log-file* is never specified, logs will be written to stderr.
+# Otherwise, logs will to written to the given file, though some of the early
+# logs might still be written to the stderr.
+# When setting this option from the config file, it is recommended to use an absolute path.
+#
+# log-file = '/path/to/your/log/file'
+
+# Show all X errors (for debugging)
+# show-all-xerrors = false
+
+# Write process ID to a file.
+# write-pid-path = '/path/to/your/log/file'
+
+# Window type settings
+#
+# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard:
+# "unknown", "desktop", "dock", "toolbar", "menu", "utility",
+# "splash", "dialog", "normal", "dropdown_menu", "popup_menu",
+# "tooltip", "notification", "combo", and "dnd".
+#
+# Following per window-type options are available: ::
+#
+# fade, shadow:::
+# Controls window-type-specific shadow and fade settings.
+#
+# opacity:::
+# Controls default opacity of the window type.
+#
+# focus:::
+# Controls whether the window of this type is to be always considered focused.
+# (By default, all window types except "normal" and "dialog" has this on.)
+#
+# full-shadow:::
+# Controls whether shadow is drawn under the parts of the window that you
+# normally won't be able to see. Useful when the window has parts of it
+# transparent, and you want shadows in those areas.
+#
+# redir-ignore:::
+# Controls whether this type of windows should cause screen to become
+# redirected again after been unredirected. If you have unredir-if-possible
+# set, and doesn't want certain window to cause unnecessary screen redirection,
+# you can set this to `true`.
+#
+wintypes:
+{
+ normal = { fade = false; shadow = false; }
+ tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
+ dock = { shadow = false; }
+ dnd = { shadow = false; }
+ popup_menu = { opacity = 0.8; }
+ dropdown_menu = { opacity = 0.8; }
+};
diff --git a/stow/picom/dot-config/picom/picom.upstream.conf b/stow/picom/dot-config/picom/picom.upstream.conf
new file mode 100644
index 0000000..19c0d1b
--- /dev/null
+++ b/stow/picom/dot-config/picom/picom.upstream.conf
@@ -0,0 +1,235 @@
+# Thank you code_nomad: http://9m.no/ꪯ鵞
+# and Arch Wiki contributors: https://wiki.archlinux.org/index.php/Compton
+
+#################################
+#
+# Backend
+#
+#################################
+
+# Backend to use: "xrender" or "glx".
+# GLX backend is typically much faster but depends on a sane driver.
+backend = "glx";
+
+#################################
+#
+# GLX backend
+#
+#################################
+
+glx-no-stencil = true;
+
+# GLX backend: Copy unmodified regions from front buffer instead of redrawing them all.
+# My tests with nvidia-drivers show a 10% decrease in performance when the whole screen is modified,
+# but a 20% increase when only 1/4 is.
+# My tests on nouveau show terrible slowdown.
+glx-copy-from-front = false;
+
+# GLX backend: Use MESA_copy_sub_buffer to do partial screen update.
+# My tests on nouveau shows a 200% performance boost when only 1/4 of the screen is updated.
+# May break VSync and is not available on some drivers.
+# Overrides --glx-copy-from-front.
+# glx-use-copysubbuffermesa = true;
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe).
+# Recommended if it works.
+# glx-no-rebind-pixmap = true;
+
+# GLX backend: GLX buffer swap method we assume.
+# Could be undefined (0), copy (1), exchange (2), 3-6, or buffer-age (-1).
+# undefined is the slowest and the safest, and the default value.
+# copy is fastest, but may fail on some drivers,
+# 2-6 are gradually slower but safer (6 is still faster than 0).
+# Usually, double buffer means 2, triple buffer means 3.
+# buffer-age means auto-detect using GLX_EXT_buffer_age, supported by some drivers.
+# Useless with --glx-use-copysubbuffermesa.
+# Partially breaks --resize-damage.
+# Defaults to undefined.
+#glx-swap-method = "undefined";
+
+#################################
+#
+# Shadows
+#
+#################################
+
+# Enabled client-side shadows on windows.
+shadow = true;
+# The blur radius for shadows. (default 12)
+shadow-radius = 10;
+# The left offset for shadows. (default -15)
+shadow-offset-x = 0.25;
+# The top offset for shadows. (default -15)
+shadow-offset-y = 0.25;
+# The translucency for shadows. (default .75)
+shadow-opacity = 0.25;
+
+# Set if you want different colour shadows
+# shadow-red = 0.0;
+# shadow-green = 0.0;
+# shadow-blue = 0.0;
+
+# The shadow exclude options are helpful if you have shadows enabled. Due to the way picom draws its shadows, certain applications will have visual glitches
+# (most applications are fine, only apps that do weird things with xshapes or argb are affected).
+# This list includes all the affected apps I found in my testing. The "! name~=''" part excludes shadows on any "Unknown" windows, this prevents a visual glitch with the XFWM alt tab switcher.
+shadow-exclude = [
+ "! name~=''",
+ "name *= 'dunst'",
+ "name *= 'i3bar'",
+ "name = 'Notification'",
+ "name = 'Plank'",
+ "name = 'Docky'",
+ "name = 'Kupfer'",
+ "name = 'xfce4-notifyd'",
+ "name *= 'VLC'",
+ "name *= 'picom'",
+ "name *= 'Chromium'",
+ "name *= 'Chrome'",
+ "class_g = 'Firefox' && argb",
+ "class_g = 'Conky'",
+ "class_g = 'Kupfer'",
+ "class_g = 'Synapse'",
+ "class_g ?= 'Notify-osd'",
+ "class_g ?= 'Cairo-dock'",
+ "class_g ?= 'Xfce4-notifyd'",
+ "class_g ?= 'Xfce4-power-manager'",
+ "_GTK_FRAME_EXTENTS@:c",
+ "_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
+];
+# Avoid drawing shadow on all shaped windows (see also: --detect-rounded-corners)
+shadow-ignore-shaped = false;
+
+#################################
+#
+# Opacity
+#
+#################################
+
+#inactive-opacity = 0.8;
+active-opacity = 1;
+frame-opacity = 1;
+inactive-opacity-override = false;
+
+# Dim inactive windows. (0.0 - 1.0)
+inactive-dim = 0.1;
+# Do not let dimness adjust based on window opacity.
+# inactive-dim-fixed = true;
+# Blur background of transparent windows. Bad performance with X Render backend. GLX backend is preferred.
+# blur-background = true;
+# Blur background of opaque windows with transparent frames as well.
+# blur-background-frame = true;
+# Do not let blur radius adjust based on window opacity.
+blur-background-fixed = false;
+blur-background-exclude = [
+ "window_type = 'dock'",
+ "window_type = 'desktop'"
+];
+
+opacity-rule = [
+ "70:class_g = 'URxvt' && !focused",
+ "95:class_g = 'URxvt' && focused",
+ "80:class_g = 'dmenu'"
+ #"80:_NET_WM_NAME@:s = 'rofi'"
+];
+
+#################################
+#
+# Fading
+#
+#################################
+
+# Fade windows during opacity changes.
+fading = true;
+# The time between steps in a fade in milliseconds. (default 10).
+fade-delta = 2.8;
+# Opacity change between steps while fading in. (default 0.028).
+fade-in-step = 0.03;
+# Opacity change between steps while fading out. (default 0.03).
+fade-out-step = 0.03;
+# Fade windows in/out when opening/closing
+no-fading-openclose = false;
+
+# Specify a list of conditions of windows that should not be faded.
+fade-exclude = [ ];
+
+#################################
+#
+# Other
+#
+#################################
+
+# Try to detect WM windows and mark them as active.
+mark-wmwin-focused = true;
+# Mark all non-WM but override-redirect windows active (e.g. menus).
+mark-ovredir-focused = true;
+# Use EWMH _NET_WM_ACTIVE_WINDOW to determine which window is focused instead of using FocusIn/Out events.
+# Usually more reliable but depends on a EWMH-compliant WM.
+use-ewmh-active-win = true;
+# Detect rounded corners and treat them as rectangular when --shadow-ignore-shaped is on.
+detect-rounded-corners = true;
+
+# Detect _NET_WM_OPACITY on client windows, useful for window managers not passing _NET_WM_OPACITY of client windows to frame windows.
+# This prevents opacity being ignored for some apps.
+# For example without this enabled my xfce4-notifyd is 100% opacity no matter what.
+detect-client-opacity = true;
+
+# Specify refresh rate of the screen.
+# If not specified or 0, picom will try detecting this with X RandR extension.
+refresh-rate = 0;
+
+# Vertical synchronization: match the refresh rate of the monitor
+vsync = true;
+
+# Enable DBE painting mode, intended to use with VSync to (hopefully) eliminate tearing.
+# Reported to have no effect, though.
+dbe = false;
+
+# Limit picom to repaint at most once every 1 / refresh_rate second to boost performance.
+# This should not be used with --vsync drm/opengl/opengl-oml as they essentially does --sw-opti's job already,
+# unless you wish to specify a lower refresh rate than the actual value.
+#sw-opti = true;
+
+# Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows, like games.
+# Known to cause flickering when redirecting/unredirecting windows.
+unredir-if-possible = false;
+
+# Specify a list of conditions of windows that should always be considered focused.
+focus-exclude = [ "_NET_WM_NAME@:s = 'rofi'" ];
+
+# Use WM_TRANSIENT_FOR to group windows, and consider windows in the same group focused at the same time.
+detect-transient = false;
+# Use WM_CLIENT_LEADER to group windows, and consider windows in the same group focused at the same time.
+# WM_TRANSIENT_FOR has higher priority if --detect-transient is enabled, too.
+detect-client-leader = false;
+
+#################################
+#
+# Window type settings
+#
+#################################
+
+wintypes:
+{
+ tooltip =
+ {
+ # fade: Fade the particular type of windows.
+ fade = true;
+ # shadow: Give those windows shadow
+ shadow = false;
+ # opacity: Default opacity for the type of windows.
+ opacity = 0.85;
+ # focus: Whether to always consider windows of this type focused.
+ focus = true;
+ };
+};
+
+######################
+#
+# XSync
+# See: https://github.com/yshui/picom/commit/b18d46bcbdc35a3b5620d817dd46fbc76485c20d
+#
+######################
+
+# Use X Sync fence to sync clients' draw calls. Needed on nvidia-drivers with GLX backend for some users.
+xrender-sync-fence = true;
diff --git a/stow/qutebrowser/dot-config/qutebrowser/config.py b/stow/qutebrowser/dot-config/qutebrowser/config.py
new file mode 100644
index 0000000..433a9cc
--- /dev/null
+++ b/stow/qutebrowser/dot-config/qutebrowser/config.py
@@ -0,0 +1,1729 @@
+import dracula.draw
+
+## Autogenerated config.py
+## Documentation:
+## qute://help/configuring.html
+## qute://help/settings.html
+
+## This is here so configs done via the GUI are still loaded.
+## Remove it to not load settings done via the GUI.
+config.load_autoconfig()
+
+## Aliases for commands. The keys of the given dictionary are the
+## aliases, while the values are the commands they map to.
+## Type: Dict
+# c.aliases = {'w': 'session-save', 'q': 'quit', 'wq': 'quit --save'}
+
+## Time interval (in milliseconds) between auto-saves of
+## config/cookies/etc.
+## Type: Int
+# c.auto_save.interval = 15000
+
+## Always restore open sites when qutebrowser is reopened.
+## Type: Bool
+# c.auto_save.session = False
+
+## Backend to use to display websites. qutebrowser supports two different
+## web rendering engines / backends, QtWebKit and QtWebEngine. QtWebKit
+## was discontinued by the Qt project with Qt 5.6, but picked up as a
+## well maintained fork: https://github.com/annulen/webkit/wiki -
+## qutebrowser only supports the fork. QtWebEngine is Qt's official
+## successor to QtWebKit. It's slightly more resource hungry than
+## QtWebKit and has a couple of missing features in qutebrowser, but is
+## generally the preferred choice.
+## Type: String
+## Valid values:
+## - webengine: Use QtWebEngine (based on Chromium).
+## - webkit: Use QtWebKit (based on WebKit, similar to Safari).
+# c.backend = 'webengine'
+
+## This setting can be used to map keys to other keys. When the key used
+## as dictionary-key is pressed, the binding for the key used as
+## dictionary-value is invoked instead. This is useful for global
+## remappings of keys, for example to map Ctrl-[ to Escape. Note that
+## when a key is bound (via `bindings.default` or `bindings.commands`),
+## the mapping is ignored.
+## Type: Dict
+# c.bindings.key_mappings = {'<Ctrl-[>': '<Escape>', '<Ctrl-6>': '<Ctrl-^>', '<Ctrl-M>': '<Return>', '<Ctrl-J>': '<Return>', '<Shift-Return>': '<Return>', '<Enter>': '<Return>', '<Shift-Enter>': '<Return>', '<Ctrl-Enter>': '<Ctrl-Return>'}
+
+## Background color of the completion widget category headers.
+## Type: QssColor
+# c.colors.completion.category.bg = 'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #888888, stop:1 #505050)'
+
+## Bottom border color of the completion widget category headers.
+## Type: QssColor
+# c.colors.completion.category.border.bottom = 'black'
+
+## Top border color of the completion widget category headers.
+## Type: QssColor
+# c.colors.completion.category.border.top = 'black'
+
+## Foreground color of completion widget category headers.
+## Type: QtColor
+# c.colors.completion.category.fg = 'white'
+
+## Background color of the completion widget for even rows.
+## Type: QssColor
+# c.colors.completion.even.bg = '#333333'
+
+## Text color of the completion widget. May be a single color to use for
+## all columns or a list of three colors, one for each column.
+## Type: List of QtColor, or QtColor
+# c.colors.completion.fg = ['white', 'white', 'white']
+
+## Background color of the selected completion item.
+## Type: QssColor
+# c.colors.completion.item.selected.bg = '#e8c000'
+
+## Bottom border color of the selected completion item.
+## Type: QssColor
+# c.colors.completion.item.selected.border.bottom = '#bbbb00'
+
+## Top border color of the completion widget category headers.
+## Type: QssColor
+# c.colors.completion.item.selected.border.top = '#bbbb00'
+
+## Foreground color of the selected completion item.
+## Type: QtColor
+# c.colors.completion.item.selected.fg = 'black'
+
+## Foreground color of the matched text in the completion.
+## Type: QssColor
+# c.colors.completion.match.fg = '#ff4444'
+
+## Background color of the completion widget for odd rows.
+## Type: QssColor
+# c.colors.completion.odd.bg = '#444444'
+
+## Color of the scrollbar in the completion view.
+## Type: QssColor
+# c.colors.completion.scrollbar.bg = '#333333'
+
+## Color of the scrollbar handle in the completion view.
+## Type: QssColor
+# c.colors.completion.scrollbar.fg = 'white'
+
+## Background color for the download bar.
+## Type: QssColor
+# c.colors.downloads.bar.bg = 'black'
+
+## Background color for downloads with errors.
+## Type: QtColor
+# c.colors.downloads.error.bg = 'red'
+
+## Foreground color for downloads with errors.
+## Type: QtColor
+# c.colors.downloads.error.fg = 'white'
+
+## Color gradient start for download backgrounds.
+## Type: QtColor
+# c.colors.downloads.start.bg = '#0000aa'
+
+## Color gradient start for download text.
+## Type: QtColor
+# c.colors.downloads.start.fg = 'white'
+
+## Color gradient stop for download backgrounds.
+## Type: QtColor
+# c.colors.downloads.stop.bg = '#00aa00'
+
+## Color gradient end for download text.
+## Type: QtColor
+# c.colors.downloads.stop.fg = 'white'
+
+## Color gradient interpolation system for download backgrounds.
+## Type: ColorSystem
+## Valid values:
+## - rgb: Interpolate in the RGB color system.
+## - hsv: Interpolate in the HSV color system.
+## - hsl: Interpolate in the HSL color system.
+## - none: Don't show a gradient.
+# c.colors.downloads.system.bg = 'rgb'
+
+## Color gradient interpolation system for download text.
+## Type: ColorSystem
+## Valid values:
+## - rgb: Interpolate in the RGB color system.
+## - hsv: Interpolate in the HSV color system.
+## - hsl: Interpolate in the HSL color system.
+## - none: Don't show a gradient.
+# c.colors.downloads.system.fg = 'rgb'
+
+## Background color for hints. Note that you can use a `rgba(...)` value
+## for transparency.
+## Type: QssColor
+# c.colors.hints.bg = 'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 247, 133, 0.8), stop:1 rgba(255, 197, 66, 0.8))'
+c.colors.hints.bg = 'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 255, 0.8), stop:1 rgba(255, 0, 255, 0.8))'
+
+## Font color for hints.
+## Type: QssColor
+# c.colors.hints.fg = 'black'
+c.colors.hints.fg = 'white'
+
+## Font color for the matched part of hints.
+## Type: QssColor
+# c.colors.hints.match.fg = 'green'
+c.colors.hints.match.fg = 'yellow'
+
+## Background color of the keyhint widget.
+## Type: QssColor
+# c.colors.keyhint.bg = 'rgba(0, 0, 0, 80%)'
+
+## Text color for the keyhint widget.
+## Type: QssColor
+# c.colors.keyhint.fg = '#FFFFFF'
+
+## Highlight color for keys to complete the current keychain.
+## Type: QssColor
+# c.colors.keyhint.suffix.fg = '#FFFF00'
+
+## Background color of an error message.
+## Type: QssColor
+# c.colors.messages.error.bg = 'red'
+
+## Border color of an error message.
+## Type: QssColor
+# c.colors.messages.error.border = '#bb0000'
+
+## Foreground color of an error message.
+## Type: QssColor
+# c.colors.messages.error.fg = 'white'
+
+## Background color of an info message.
+## Type: QssColor
+# c.colors.messages.info.bg = 'black'
+
+## Border color of an info message.
+## Type: QssColor
+# c.colors.messages.info.border = '#333333'
+
+## Foreground color of an info message.
+## Type: QssColor
+# c.colors.messages.info.fg = 'white'
+
+## Background color of a warning message.
+## Type: QssColor
+# c.colors.messages.warning.bg = 'darkorange'
+
+## Border color of a warning message.
+## Type: QssColor
+# c.colors.messages.warning.border = '#d47300'
+
+## Foreground color of a warning message.
+## Type: QssColor
+# c.colors.messages.warning.fg = 'white'
+
+## Background color for prompts.
+## Type: QssColor
+# c.colors.prompts.bg = '#444444'
+
+## Border used around UI elements in prompts.
+## Type: String
+# c.colors.prompts.border = '1px solid gray'
+
+## Foreground color for prompts.
+## Type: QssColor
+# c.colors.prompts.fg = 'white'
+
+## Background color for the selected item in filename prompts.
+## Type: QssColor
+# c.colors.prompts.selected.bg = 'grey'
+
+## Background color of the statusbar in caret mode.
+## Type: QssColor
+# c.colors.statusbar.caret.bg = 'purple'
+
+## Foreground color of the statusbar in caret mode.
+## Type: QssColor
+# c.colors.statusbar.caret.fg = 'white'
+
+## Background color of the statusbar in caret mode with a selection.
+## Type: QssColor
+# c.colors.statusbar.caret.selection.bg = '#a12dff'
+
+## Foreground color of the statusbar in caret mode with a selection.
+## Type: QssColor
+# c.colors.statusbar.caret.selection.fg = 'white'
+
+## Background color of the statusbar in command mode.
+## Type: QssColor
+# c.colors.statusbar.command.bg = 'black'
+
+## Foreground color of the statusbar in command mode.
+## Type: QssColor
+# c.colors.statusbar.command.fg = 'white'
+
+## Background color of the statusbar in private browsing + command mode.
+## Type: QssColor
+# c.colors.statusbar.command.private.bg = 'grey'
+
+## Foreground color of the statusbar in private browsing + command mode.
+## Type: QssColor
+# c.colors.statusbar.command.private.fg = 'white'
+
+## Background color of the statusbar in insert mode.
+## Type: QssColor
+# c.colors.statusbar.insert.bg = 'darkgreen'
+c.colors.statusbar.insert.bg = '#3fff00' #Harlequin
+
+## Foreground color of the statusbar in insert mode.
+## Type: QssColor
+# c.colors.statusbar.insert.fg = 'white'
+c.colors.statusbar.insert.fg = 'black'
+
+## Background color of the statusbar.
+## Type: QssColor
+# c.colors.statusbar.normal.bg = 'black'
+
+## Foreground color of the statusbar.
+## Type: QssColor
+# c.colors.statusbar.normal.fg = 'white'
+
+## Background color of the statusbar in passthrough mode.
+## Type: QssColor
+# c.colors.statusbar.passthrough.bg = 'darkblue'
+c.colors.statusbar.passthrough.bg = '#3f00ff'
+
+## Foreground color of the statusbar in passthrough mode.
+## Type: QssColor
+# c.colors.statusbar.passthrough.fg = 'white'
+
+## Background color of the statusbar in private browsing mode.
+## Type: QssColor
+# c.colors.statusbar.private.bg = '#666666'
+
+## Foreground color of the statusbar in private browsing mode.
+## Type: QssColor
+# c.colors.statusbar.private.fg = 'white'
+
+## Background color of the progress bar.
+## Type: QssColor
+# c.colors.statusbar.progress.bg = 'white'
+
+## Foreground color of the URL in the statusbar on error.
+## Type: QssColor
+# c.colors.statusbar.url.error.fg = 'orange'
+
+## Default foreground color of the URL in the statusbar.
+## Type: QssColor
+# c.colors.statusbar.url.fg = 'white'
+
+## Foreground color of the URL in the statusbar for hovered links.
+## Type: QssColor
+# c.colors.statusbar.url.hover.fg = 'aqua'
+
+## Foreground color of the URL in the statusbar on successful load
+## (http).
+## Type: QssColor
+# c.colors.statusbar.url.success.http.fg = 'white'
+
+## Foreground color of the URL in the statusbar on successful load
+## (https).
+## Type: QssColor
+# c.colors.statusbar.url.success.https.fg = 'lime'
+c.colors.statusbar.url.success.https.fg = 'white'
+
+## Foreground color of the URL in the statusbar when there's a warning.
+## Type: QssColor
+# c.colors.statusbar.url.warn.fg = 'yellow'
+
+## Background color of the tab bar.
+## Type: QtColor
+# c.colors.tabs.bar.bg = '#555555'
+
+## Background color of unselected even tabs.
+## Type: QtColor
+# c.colors.tabs.even.bg = 'darkgrey'
+c.colors.tabs.even.bg = 'black'
+
+## Foreground color of unselected even tabs.
+## Type: QtColor
+# c.colors.tabs.even.fg = 'white'
+c.colors.tabs.even.fg = '#9F9F9F'
+
+## Color for the tab indicator on errors.
+## Type: QtColor
+# c.colors.tabs.indicator.error = '#ff0000'
+
+## Color gradient start for the tab indicator.
+## Type: QtColor
+# c.colors.tabs.indicator.start = '#0000aa'
+
+## Color gradient end for the tab indicator.
+## Type: QtColor
+# c.colors.tabs.indicator.stop = '#00aa00'
+
+## Color gradient interpolation system for the tab indicator.
+## Type: ColorSystem
+## Valid values:
+## - rgb: Interpolate in the RGB color system.
+## - hsv: Interpolate in the HSV color system.
+## - hsl: Interpolate in the HSL color system.
+## - none: Don't show a gradient.
+# c.colors.tabs.indicator.system = 'rgb'
+
+## Background color of unselected odd tabs.
+## Type: QtColor
+# c.colors.tabs.odd.bg = 'grey'
+#c.colors.tabs.odd.bg = '#5F5F5F'
+c.colors.tabs.odd.bg = 'black'
+
+## Foreground color of unselected odd tabs.
+## Type: QtColor
+# c.colors.tabs.odd.fg = 'white'
+c.colors.tabs.odd.fg = '#9F9F9F'
+
+## Background color of selected even tabs.
+## Type: QtColor
+# c.colors.tabs.selected.even.bg = 'black'
+
+## Foreground color of selected even tabs.
+## Type: QtColor
+# c.colors.tabs.selected.even.fg = 'white'
+
+## Background color of selected odd tabs.
+## Type: QtColor
+# c.colors.tabs.selected.odd.bg = 'black'
+
+## Foreground color of selected odd tabs.
+## Type: QtColor
+# c.colors.tabs.selected.odd.fg = 'white'
+
+## Background color for webpages if unset (or empty to use the theme's
+## color).
+## Type: QtColor
+# c.colors.webpage.bg = 'white'
+
+## Number of commands to save in the command history. 0: no history / -1:
+## unlimited
+## Type: Int
+# c.completion.cmd_history_max_items = 100
+
+## Delay (in milliseconds) before updating completions after typing a
+## character.
+## Type: Int
+# c.completion.delay = 0
+
+## Height (in pixels or as percentage of the window) of the completion.
+## Type: PercOrInt
+# c.completion.height = '50%'
+c.completion.height = '30%'
+
+## Minimum amount of characters needed to update completions.
+## Type: Int
+# c.completion.min_chars = 1
+
+## Move on to the next part when there's only one possible completion
+## left.
+## Type: Bool
+# c.completion.quick = True
+
+## Padding (in pixels) of the scrollbar handle in the completion window.
+## Type: Int
+# c.completion.scrollbar.padding = 2
+
+## Width (in pixels) of the scrollbar in the completion window.
+## Type: Int
+# c.completion.scrollbar.width = 12
+
+## When to show the autocompletion window.
+## Type: String
+## Valid values:
+## - always: Whenever a completion is available.
+## - auto: Whenever a completion is requested.
+## - never: Never.
+# c.completion.show = 'always'
+
+## Shrink the completion to be smaller than the configured size if there
+## are no scrollbars.
+## Type: Bool
+# c.completion.shrink = False
+
+## Format of timestamps (e.g. for the history completion).
+## Type: TimestampTemplate
+# c.completion.timestamp_format = '%Y-%m-%d'
+
+## Execute the best-matching command on a partial match.
+## Type: Bool
+# c.completion.use_best_match = False
+
+## Number of URLs to show in the web history. 0: no history / -1:
+## unlimited
+## Type: Int
+# c.completion.web_history_max_items = -1
+
+## Require a confirmation before quitting the application.
+## Type: ConfirmQuit
+## Valid values:
+## - always: Always show a confirmation.
+## - multiple-tabs: Show a confirmation if multiple tabs are opened.
+## - downloads: Show a confirmation if downloads are running
+## - never: Never show a confirmation.
+# c.confirm_quit = ['never']
+
+## Enable support for the HTML 5 web application cache feature. An
+## application cache acts like an HTTP cache in some sense. For documents
+## that use the application cache via JavaScript, the loader engine will
+## first ask the application cache for the contents, before hitting the
+## network.
+## Type: Bool
+# c.content.cache.appcache = True
+
+## Maximum number of pages to hold in the global memory page cache. The
+## page cache allows for a nicer user experience when navigating forth or
+## back to pages in the forward/back history, by pausing and resuming up
+## to _n_ pages. For more information about the feature, please refer to:
+## http://webkit.org/blog/427/webkit-page-cache-i-the-basics/
+## Type: Int
+# c.content.cache.maximum_pages = 0
+
+## Size (in bytes) of the HTTP network cache. Null to use the default
+## value. With QtWebEngine, the maximum supported value is 2147483647 (~2
+## GB).
+## Type: Int
+# c.content.cache.size = None
+
+## Which cookies to accept.
+## Type: String
+## Valid values:
+## - all: Accept all cookies.
+## - no-3rdparty: Accept cookies from the same origin only.
+## - no-unknown-3rdparty: Accept cookies from the same origin only, unless a cookie is already set for the domain.
+## - never: Don't accept cookies at all.
+# c.content.cookies.accept = 'no-3rdparty'
+
+## Store cookies. Note this option needs a restart with QtWebEngine on Qt
+## < 5.9.
+## Type: Bool
+# c.content.cookies.store = True
+
+## Default encoding to use for websites. The encoding must be a string
+## describing an encoding such as _utf-8_, _iso-8859-1_, etc.
+## Type: String
+# c.content.default_encoding = 'iso-8859-1'
+
+## Enable extra tools for Web developers. This needs to be enabled for
+## `:inspector` to work and also adds an _Inspect_ entry to the context
+## menu. For QtWebEngine, see `--enable-webengine-inspector` in
+## `qutebrowser --help` instead.
+## Type: Bool
+# c.content.developer_extras = False
+#c.content.developer_extras = True
+
+## Try to pre-fetch DNS entries to speed up browsing.
+## Type: Bool
+# c.content.dns_prefetch = True
+
+## Expand each subframe to its contents. This will flatten all the frames
+## to become one scrollable page.
+## Type: Bool
+# c.content.frame_flattening = False
+
+## Allow websites to request geolocations.
+## Type: BoolAsk
+## Valid values:
+## - true
+## - false
+## - ask
+# c.content.geolocation = 'ask'
+
+## Value to send in the `Accept-Language` header.
+## Type: String
+# c.content.headers.accept_language = 'en-US,en'
+
+## Custom headers for qutebrowser HTTP requests.
+## Type: Dict
+# c.content.headers.custom = {}
+
+## Value to send in the `DNT` header. When this is set to true,
+## qutebrowser asks websites to not track your identity. If set to null,
+## the DNT header is not sent at all.
+## Type: Bool
+# c.content.headers.do_not_track = True
+
+## When to send the Referer header. The Referer header tells websites
+## from which website you were coming from when visting them.
+## Type: String
+## Valid values:
+## - always: Always send the Referer.
+## - never: Never send the Referer. This is not recommended, as some sites may break.
+## - same-domain: Only send the Referer for the same domain. This will still protect your privacy, but shouldn't break any sites.
+# c.content.headers.referer = 'same-domain'
+
+## User agent to send. Unset to send the default.
+## Type: String
+# c.content.headers.user_agent = None
+
+## Enable host blocking.
+## Type: Bool
+# c.content.host_blocking.enabled = True
+
+## List of URLs of lists which contain hosts to block. The file can be
+## in one of the following formats: - An `/etc/hosts`-like file - One
+## host per line - A zip-file of any of the above, with either only one
+## file, or a file named `hosts` (with any extension).
+## Type: List of Url
+# c.content.host_blocking.lists = ['https://www.malwaredomainlist.com/hostslist/hosts.txt', 'http://someonewhocares.org/hosts/hosts', 'http://winhelp2002.mvps.org/hosts.zip', 'http://malwaredomains.lehigh.edu/files/justdomains.zip', 'https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext']
+
+## List of domains that should always be loaded, despite being ad-
+## blocked. Domains may contain * and ? wildcards and are otherwise
+## required to exactly match the requested domain. Local domains are
+## always exempt from hostblocking.
+## Type: List of String
+# c.content.host_blocking.whitelist = ['piwik.org']
+
+## Enable hyperlink auditing (`<a ping>`).
+## Type: Bool
+# c.content.hyperlink_auditing = False
+
+## Load images automatically in web pages.
+## Type: Bool
+# c.content.images = True
+
+## Show javascript alerts.
+## Type: Bool
+# c.content.javascript.alert = True
+
+## Allow JavaScript to read from or write to the clipboard. With
+## QtWebEngine, writing the clipboard as response to a user interaction
+## is always allowed.
+## Type: Bool
+# c.content.javascript.can_access_clipboard = False
+
+## Allow JavaScript to close tabs.
+## Type: Bool
+# c.content.javascript.can_close_tabs = False
+
+## Allow JavaScript to open new tabs without user interaction.
+## Type: Bool
+# c.content.javascript.can_open_tabs_automatically = False
+
+## Enable JavaScript.
+## Type: Bool
+# c.content.javascript.enabled = True
+
+## Log levels to use for JavaScript console logging messages. When a
+## JavaScript message with the level given in the dictionary key is
+## logged, the corresponding dictionary value selects the qutebrowser
+## logger to use. On QtWebKit, the "unknown" setting is always used.
+## Type: Dict
+# c.content.javascript.log = {'unknown': 'debug', 'info': 'debug', 'warning': 'debug', 'error': 'debug'}
+
+## Use the standard JavaScript modal dialog for `alert()` and
+## `confirm()`.
+## Type: Bool
+# c.content.javascript.modal_dialog = False
+
+## Show javascript prompts.
+## Type: Bool
+# c.content.javascript.prompt = True
+
+## Allow locally loaded documents to access other local URLs.
+## Type: Bool
+# c.content.local_content_can_access_file_urls = True
+
+## Allow locally loaded documents to access remote URLs.
+## Type: Bool
+# c.content.local_content_can_access_remote_urls = False
+
+## Enable support for HTML 5 local storage and Web SQL.
+## Type: Bool
+# c.content.local_storage = True
+
+## Allow websites to record audio/video.
+## Type: BoolAsk
+## Valid values:
+## - true
+## - false
+## - ask
+# c.content.media_capture = 'ask'
+
+## Netrc-file for HTTP authentication. If unset, `~/.netrc` is used.
+## Type: File
+# c.content.netrc_file = None
+
+## Allow websites to show notifications.
+## Type: BoolAsk
+## Valid values:
+## - true
+## - false
+## - ask
+# c.content.notifications = 'ask'
+
+## Allow pdf.js to view PDF files in the browser. Note that the files can
+## still be downloaded by clicking the download button in the pdf.js
+## viewer.
+## Type: Bool
+# c.content.pdfjs = False
+
+## Enable plugins in Web pages.
+## Type: Bool
+# c.content.plugins = False
+
+## Draw the background color and images also when the page is printed.
+## Type: Bool
+# c.content.print_element_backgrounds = True
+
+## Open new windows in private browsing mode which does not record
+## visited pages.
+## Type: Bool
+# c.content.private_browsing = False
+
+## Proxy to use. In addition to the listed values, you can use a
+## `socks://...` or `http://...` URL.
+## Type: Proxy
+## Valid values:
+## - system: Use the system wide proxy.
+## - none: Don't use any proxy
+# c.content.proxy = 'system'
+
+## Send DNS requests over the configured proxy.
+## Type: Bool
+# c.content.proxy_dns_requests = True
+
+## Validate SSL handshakes.
+## Type: BoolAsk
+## Valid values:
+## - true
+## - false
+## - ask
+# c.content.ssl_strict = 'ask'
+
+## List of user stylesheet filenames to use.
+## Type: List of File, or File
+# c.content.user_stylesheets = []
+
+## Enable WebGL.
+## Type: Bool
+# c.content.webgl = True
+
+## Limit fullscreen to the browser window (does not expand to fill the
+## screen).
+## Type: Bool
+c.content.fullscreen.window = True
+
+## Monitor load requests for cross-site scripting attempts. Suspicious
+## scripts will be blocked and reported in the inspector's JavaScript
+## console. Enabling this feature might have an impact on performance.
+## Type: Bool
+# c.content.xss_auditing = False
+
+## Directory to save downloads to. If unset, a sensible OS-specific
+## default is used.
+## Type: Directory
+# c.downloads.location.directory = None
+c.downloads.location.directory = "~/downloads"
+
+## Prompt the user for the download location. If set to false,
+## `downloads.location.directory` will be used.
+## Type: Bool
+# c.downloads.location.prompt = True
+
+## Remember the last used download directory.
+## Type: Bool
+# c.downloads.location.remember = True
+
+## What to display in the download filename input.
+## Type: String
+## Valid values:
+## - path: Show only the download path.
+## - filename: Show only download filename.
+## - both: Show download path and filename.
+# c.downloads.location.suggestion = 'path'
+
+## Default program used to open downloads. If null, the default internal
+## handler is used. Any `{}` in the string will be expanded to the
+## filename, else the filename will be appended.
+## Type: String
+# c.downloads.open_dispatcher = None
+c.downloads.open_dispatcher = "rifle"
+
+## Where to show the downloaded files.
+## Type: VerticalPosition
+## Valid values:
+## - top
+## - bottom
+# c.downloads.position = 'top'
+
+## Duration (in milliseconds) to wait before removing finished downloads.
+## If set to -1, downloads are never removed.
+## Type: Int
+# c.downloads.remove_finished = -1
+
+## Editor (and arguments) to use for the `open-editor` command. The
+## following placeholders are defined: * `{file}`: Filename of the file
+## to be edited. * `{line}`: Line in which the caret is found in the
+## text. * `{column}`: Column in which the caret is found in the text. *
+## `{line0}`: Same as `{line}`, but starting from index 0. * `{column0}`:
+## Same as `{column}`, but starting from index 0.
+## Type: ShellCommand
+# c.editor.command = ['gvim', '-f', '{file}', '-c', 'normal {line}G{column0}l']
+c.editor.command = ['urxvt', '-title', '"floating"', '-e', 'nvim', '{file}']
+
+## Encoding to use for the editor.
+## Type: Encoding
+# c.editor.encoding = 'utf-8'
+
+## Font used in the completion categories.
+## Type: Font
+# c.fonts.completion.category = 'bold 10pt monospace'
+c.fonts.completion.category = 'bold 10pt serif'
+
+## Font used in the completion widget.
+## Type: Font
+# c.fonts.completion.entry = '10pt monospace'
+c.fonts.completion.entry = '10pt serif'
+
+## Font used for the debugging console.
+## Type: QtFont
+# c.fonts.debug_console = '10pt monospace'
+c.fonts.debug_console = '10pt serif'
+
+## Font used for the downloadbar.
+## Type: Font
+# c.fonts.downloads = '10pt monospace'
+c.fonts.downloads = '10pt serif'
+
+## Font used for the hints.
+## Type: Font
+# c.fonts.hints = 'bold 10pt monospace'
+c.fonts.hints = 'bold 10pt Inconsolata'
+
+## Font used in the keyhint widget.
+## Type: Font
+# c.fonts.keyhint = '10pt monospace'
+c.fonts.keyhint = '10pt serif'
+
+## Font used for error messages.
+## Type: Font
+# c.fonts.messages.error = '10pt monospace'
+c.fonts.messages.error = '10pt serif'
+
+## Font used for info messages.
+## Type: Font
+# c.fonts.messages.info = '10pt monospace'
+c.fonts.messages.info = '10pt serif'
+
+## Font used for warning messages.
+## Type: Font
+# c.fonts.messages.warning = '10pt monospace'
+c.fonts.messages.warning = '10pt serif'
+
+## Default monospace fonts. Whenever "monospace" is used in a font
+## setting, it's replaced with the fonts listed here.
+## Type: Font
+# c.fonts.monospace = '"xos4 Terminus", Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal'
+
+## Font used for prompts.
+## Type: Font
+# c.fonts.prompts = '10pt sans-serif'
+
+## Font used in the statusbar.
+## Type: Font
+# c.fonts.statusbar = '10pt monospace'
+c.fonts.statusbar = '10pt Linux Libertine'
+
+## Font used in the tab bar.
+## Type: QtFont
+# c.fonts.tabs = '10pt monospace'
+# No longer exists (Thu 16 Jul 2020 01:39:46 AM CEST)
+c.fonts.tabs.selected = '10pt serif'
+c.fonts.tabs.unselected = '10pt serif'
+
+## Font family for cursive fonts.
+## Type: FontFamily
+# c.fonts.web.family.cursive = ''
+
+## Font family for fantasy fonts.
+## Type: FontFamily
+# c.fonts.web.family.fantasy = ''
+
+## Font family for fixed fonts.
+## Type: FontFamily
+# c.fonts.web.family.fixed = ''
+
+## Font family for sans-serif fonts.
+## Type: FontFamily
+# c.fonts.web.family.sans_serif = ''
+
+## Font family for serif fonts.
+## Type: FontFamily
+# c.fonts.web.family.serif = ''
+
+## Font family for standard fonts.
+## Type: FontFamily
+# c.fonts.web.family.standard = ''
+
+## Default font size (in pixels) for regular text.
+## Type: Int
+# c.fonts.web.size.default = 16
+
+## Default font size (in pixels) for fixed-pitch text.
+## Type: Int
+# c.fonts.web.size.default_fixed = 13
+
+## Hard minimum font size (in pixels).
+## Type: Int
+# c.fonts.web.size.minimum = 0
+
+## Minimum logical font size (in pixels) that is applied when zooming
+## out.
+## Type: Int
+# c.fonts.web.size.minimum_logical = 6
+
+## When a hint can be automatically followed without pressing Enter.
+## Type: String
+## Valid values:
+## - always: Auto-follow whenever there is only a single hint on a page.
+## - unique-match: Auto-follow whenever there is a unique non-empty match in either the hint string (word mode) or filter (number mode).
+## - full-match: Follow the hint when the user typed the whole hint (letter, word or number mode) or the element's text (only in number mode).
+## - never: The user will always need to press Enter to follow a hint.
+# c.hints.auto_follow = 'unique-match'
+
+## Duration (in milliseconds) to ignore normal-mode key bindings after a
+## successful auto-follow.
+## Type: Int
+# c.hints.auto_follow_timeout = 0
+
+## CSS border value for hints.
+## Type: String
+# c.hints.border = '1px solid #E3BE23'
+c.hints.border = '1px solid #000000'
+
+## Characters used for hint strings.
+## Type: UniqueCharString
+# c.hints.chars = 'asdfghjkl'
+c.hints.chars = 'asdfjklñ'
+
+## Dictionary file to be used by the word hints.
+## Type: File
+# c.hints.dictionary = '/usr/share/dict/words'
+
+## Which implementation to use to find elements to hint.
+## Type: String
+## Valid values:
+## - javascript: Better but slower
+## - python: Slightly worse but faster
+# c.hints.find_implementation = 'python'
+c.hints.find_implementation = 'javascript'
+
+## Hide unmatched hints in rapid mode.
+## Type: Bool
+# c.hints.hide_unmatched_rapid_hints = True
+
+## Minimum number of characters used for hint strings.
+## Type: Int
+# c.hints.min_chars = 1
+c.hints.min_chars = 2
+
+## Mode to use for hints.
+## Type: String
+## Valid values:
+## - number: Use numeric hints. (In this mode you can also type letters from the hinted element to filter and reduce the number of elements that are hinted.)
+## - letter: Use the characters in the `hints.chars` setting.
+## - word: Use hints words based on the html elements and the extra words.
+# c.hints.mode = 'letter'
+c.hints.mode = 'letter'
+
+## Comma-separated list of regular expressions to use for 'next' links.
+## Type: List of Regex
+# c.hints.next_regexes = ['\\bnext\\b', '\\bmore\\b', '\\bnewer\\b', '\\b[>→≫]\\b', '\\b(>>|»)\\b', '\\bcontinue\\b']
+
+## Comma-separated list of regular expressions to use for 'prev' links.
+## Type: List of Regex
+# c.hints.prev_regexes = ['\\bprev(ious)?\\b', '\\bback\\b', '\\bolder\\b', '\\b[<â†â‰ª]\\b', '\\b(<<|«)\\b']
+
+## Scatter hint key chains (like Vimium) or not (like dwb). Ignored for
+## number hints.
+## Type: Bool
+# c.hints.scatter = True
+
+## Make characters in hint strings uppercase.
+## Type: Bool
+# c.hints.uppercase = False
+
+## Maximum time (in minutes) between two history items for them to be
+## considered being from the same browsing session. Items with less time
+## between them are grouped when being displayed in `:history`. Use -1 to
+## disable separation.
+## Type: Int
+# c.history_gap_interval = 30
+
+## Which unbound keys to forward to the webview in normal mode.
+## Type: String
+## Valid values:
+## - all: Forward all unbound keys.
+## - auto: Forward unbound non-alphanumeric keys.
+## - none: Don't forward any keys.
+# c.input.forward_unbound_keys = 'auto'
+
+## Leave insert mode if a non-editable element is clicked.
+## Type: Bool
+# c.input.insert_mode.auto_leave = True
+
+## Automatically enter insert mode if an editable element is focused
+## after loading the page.
+## Type: Bool
+# c.input.insert_mode.auto_load = False
+c.input.insert_mode.auto_load = True
+
+## Switch to insert mode when clicking flash and other plugins.
+## Type: Bool
+# c.input.insert_mode.plugins = False
+
+## Include hyperlinks in the keyboard focus chain when tabbing.
+## Type: Bool
+# c.input.links_included_in_focus_chain = True
+
+## Timeout (in milliseconds) for partially typed key bindings. If the
+## current input forms only partial matches, the keystring will be
+## cleared after this time.
+## Type: Int
+# c.input.partial_timeout = 5000
+
+## Enable Opera-like mouse rocker gestures. This disables the context
+## menu.
+## Type: Bool
+# c.input.rocker_gestures = False
+
+## Enable spatial navigation. Spatial navigation consists in the ability
+## to navigate between focusable elements in a Web page, such as
+## hyperlinks and form controls, by using Left, Right, Up and Down arrow
+## keys. For example, if the user presses the Right key, heuristics
+## determine whether there is an element he might be trying to reach
+## towards the right and which element he probably wants.
+## Type: Bool
+# c.input.spatial_navigation = False
+
+## Keychains that shouldn't be shown in the keyhint dialog. Globs are
+## supported, so `;*` will blacklist all keychains starting with `;`. Use
+## `*` to disable keyhints.
+## Type: List of String
+# c.keyhint.blacklist = []
+
+## Time (in milliseconds) from pressing a key to seeing the keyhint
+## dialog.
+## Type: Int
+# c.keyhint.delay = 500
+
+## Rounding radius (in pixels) for the edges of the keyhint dialog.
+## Type: Int
+# c.keyhint.radius = 6
+
+## Duration (in milliseconds) to show messages in the statusbar for. Set
+## to 0 to never clear messages.
+## Type: Int
+# c.messages.timeout = 2000
+
+## How to open links in an existing instance if a new one is launched.
+## This happens when e.g. opening a link from a terminal. See
+## `new_instance_open_target_window` to customize in which window the
+## link is opened in.
+## Type: String
+## Valid values:
+## - tab: Open a new tab in the existing window and activate the window.
+## - tab-bg: Open a new background tab in the existing window and activate the window.
+## - tab-silent: Open a new tab in the existing window without activating the window.
+## - tab-bg-silent: Open a new background tab in the existing window without activating the window.
+## - window: Open in a new window.
+# c.new_instance_open_target = 'tab'
+c.new_instance_open_target = 'window'
+
+## Which window to choose when opening links as new tabs. When
+## `new_instance_open_target` is not set to `window`, this is ignored.
+## Type: String
+## Valid values:
+## - first-opened: Open new tabs in the first (oldest) opened window.
+## - last-opened: Open new tabs in the last (newest) opened window.
+## - last-focused: Open new tabs in the most recently focused window.
+## - last-visible: Open new tabs in the most recently visible window.
+# c.new_instance_open_target_window = 'last-focused'
+
+## Show a filebrowser in upload/download prompts.
+## Type: Bool
+# c.prompt.filebrowser = True
+
+## Rounding radius (in pixels) for the edges of prompts.
+## Type: Int
+# c.prompt.radius = 8
+
+## Additional arguments to pass to Qt, without leading `--`. With
+## QtWebEngine, some Chromium arguments (see
+## https://peter.sh/experiments/chromium-command-line-switches/ for a
+## list) will work.
+## Type: List of String
+# c.qt.args = []
+
+## Force a Qt platform to use. This sets the `QT_QPA_PLATFORM`
+## environment variable and is useful to force using the XCB plugin when
+## running QtWebEngine on Wayland.
+## Type: String
+# c.qt.force_platform = None
+
+## Force software rendering for QtWebEngine. This is needed for
+## QtWebEngine to work with Nouveau drivers.
+## Type: Bool
+# c.qt.force_software_rendering = False
+
+## Turn on Qt HighDPI scaling. This is equivalent to setting
+## QT_AUTO_SCREEN_SCALE_FACTOR=1 in the environment. It's off by default
+## as it can cause issues with some bitmap fonts. As an alternative to
+## this, it's possible to set font sizes and the `zoom.default` setting.
+## Type: Bool
+# c.qt.highdpi = False
+
+## Show a scrollbar.
+## Type: Bool
+# c.scrolling.bar = False
+
+## Enable smooth scrolling for web pages. Note smooth scrolling does not
+## work with the `:scroll-px` command.
+## Type: Bool
+# c.scrolling.smooth = False
+
+## When to find text on a page case-insensitively.
+## Type: String
+## Valid values:
+## - always: Search case-insensitively.
+## - never: Search case-sensitively.
+## - smart: Search case-sensitively if there are capital characters.
+# c.search.ignore_case = 'smart'
+
+## Find text on a page incrementally, renewing the search for each typed
+## character.
+## Type: Bool
+# c.search.incremental = True
+
+## Name of the session to save by default. If this is set to null, the
+## session which was last loaded is saved.
+## Type: SessionName
+# c.session.default_name = None
+
+## Load a restored tab as soon as it takes focus.
+## Type: Bool
+# c.session.lazy_restore = False
+
+## Languages to use for spell checking. You can check for available
+## languages and install dictionaries using scripts/dictcli.py. Run the
+## script with -h/--help for instructions.
+## Type: List of String
+## Valid values:
+## - af-ZA: Afrikaans (South Africa)
+## - bg-BG: Bulgarian (Bulgaria)
+## - ca-ES: Catalan (Spain)
+## - cs-CZ: Czech (Czech Republic)
+## - da-DK: Danish (Denmark)
+## - de-DE: German (Germany)
+## - el-GR: Greek (Greece)
+## - en-AU: English (Australia)
+## - en-CA: English (Canada)
+## - en-GB: English (United Kingdom)
+## - en-US: English (United States)
+## - es-ES: Spanish (Spain)
+## - et-EE: Estonian (Estonia)
+## - fa-IR: Farsi (Iran)
+## - fo-FO: Faroese (Faroe Islands)
+## - fr-FR: French (France)
+## - he-IL: Hebrew (Israel)
+## - hi-IN: Hindi (India)
+## - hr-HR: Croatian (Croatia)
+## - hu-HU: Hungarian (Hungary)
+## - id-ID: Indonesian (Indonesia)
+## - it-IT: Italian (Italy)
+## - ko: Korean
+## - lt-LT: Lithuanian (Lithuania)
+## - lv-LV: Latvian (Latvia)
+## - nb-NO: Norwegian (Norway)
+## - nl-NL: Dutch (Netherlands)
+## - pl-PL: Polish (Poland)
+## - pt-BR: Portuguese (Brazil)
+## - pt-PT: Portuguese (Portugal)
+## - ro-RO: Romanian (Romania)
+## - ru-RU: Russian (Russia)
+## - sh: Serbo-Croatian
+## - sk-SK: Slovak (Slovakia)
+## - sl-SI: Slovenian (Slovenia)
+## - sq: Albanian
+## - sr: Serbian
+## - sv-SE: Swedish (Sweden)
+## - ta-IN: Tamil (India)
+## - tg-TG: Tajik (Tajikistan)
+## - tr-TR: Turkish (Turkey)
+## - uk-UA: Ukrainian (Ukraine)
+## - vi-VN: Vietnamese (Viet Nam)
+# c.spellcheck.languages = []
+
+## Hide the statusbar unless a message is shown.
+## Type: Bool
+# c.statusbar.hide = False
+# No longer exists (Thu 16 Jul 2020 01:39:46 AM CEST)
+c.statusbar.show = "in-mode"
+
+## Padding (in pixels) for the statusbar.
+## Type: Padding
+# c.statusbar.padding = {'top': 1, 'bottom': 1, 'left': 0, 'right': 0}
+
+## Position of the status bar.
+## Type: VerticalPosition
+## Valid values:
+## - top
+## - bottom
+# c.statusbar.position = 'bottom'
+
+## Open new tabs (middleclick/ctrl+click) in the background.
+## Type: Bool
+# c.tabs.background = False
+
+## Mouse button with which to close tabs.
+## Type: String
+## Valid values:
+## - right: Close tabs on right-click.
+## - middle: Close tabs on middle-click.
+## - none: Don't close tabs using the mouse.
+# c.tabs.close_mouse_button = 'middle'
+
+## How to behave when the close mouse button is pressed on the tab bar.
+## Type: String
+## Valid values:
+## - new-tab: Open a new tab.
+## - close-current: Close the current tab.
+## - close-last: Close the last tab.
+## - ignore: Don't do anything.
+# c.tabs.close_mouse_button_on_bar = 'new-tab'
+
+## Scaling factor for favicons in the tab bar. The tab size is unchanged,
+## so big favicons also require extra `tabs.padding`.
+## Type: Float
+# c.tabs.favicons.scale = 1.0
+
+## Show favicons in the tab bar.
+## Type: Bool
+# c.tabs.favicons.show = True
+
+## Padding (in pixels) for tab indicators.
+## Type: Padding
+# c.tabs.indicator.padding = {'top': 2, 'bottom': 2, 'left': 0, 'right': 4}
+
+## Width (in pixels) of the progress indicator (0 to disable).
+## Type: Int
+# c.tabs.indicator.width = 3
+
+## How to behave when the last tab is closed.
+## Type: String
+## Valid values:
+## - ignore: Don't do anything.
+## - blank: Load a blank page.
+## - startpage: Load the start page.
+## - default-page: Load the default page.
+## - close: Close the window.
+# c.tabs.last_close = 'ignore'
+c.tabs.last_close = 'close'
+
+## Switch between tabs using the mouse wheel.
+## Type: Bool
+# c.tabs.mousewheel_switching = True
+
+## Position of new tabs opened from another tab.
+## Type: NewTabPosition
+## Valid values:
+## - prev: Before the current tab.
+## - next: After the current tab.
+## - first: At the beginning.
+## - last: At the end.
+# c.tabs.new_position.related = 'next'
+
+## Position of new tabs which aren't opened from another tab.
+## Type: NewTabPosition
+## Valid values:
+## - prev: Before the current tab.
+## - next: After the current tab.
+## - first: At the beginning.
+## - last: At the end.
+# c.tabs.new_position.unrelated = 'last'
+
+## Padding (in pixels) around text for tabs.
+## Type: Padding
+# c.tabs.padding = {'top': 0, 'bottom': 0, 'left': 5, 'right': 5}
+
+## Stay in insert/passthrough mode when switching tabs.
+## Type: Bool
+# c.tabs.persist_mode_on_change = False
+
+## Shrink pinned tabs down to their contents.
+## Type: Bool
+# c.tabs.pinned.shrink = True
+
+## Position of the tab bar.
+## Type: Position
+## Valid values:
+## - top
+## - bottom
+## - left
+## - right
+# c.tabs.position = 'top'
+
+## Which tab to select when the focused tab is removed.
+## Type: SelectOnRemove
+## Valid values:
+## - prev: Select the tab which came before the closed one (left in horizontal, above in vertical).
+## - next: Select the tab which came after the closed one (right in horizontal, below in vertical).
+## - last-used: Select the previously selected tab.
+# c.tabs.select_on_remove = 'next'
+
+## When to show the tab bar.
+## Type: String
+## Valid values:
+## - always: Always show the tab bar.
+## - never: Always hide the tab bar.
+## - multiple: Hide the tab bar if only one tab is open.
+## - switching: Show the tab bar when switching tabs.
+# c.tabs.show = 'always'
+c.tabs.show = 'multiple'
+
+## Duration (in milliseconds) to show the tab bar before hiding it when
+## tabs.show is set to 'switching'.
+## Type: Int
+# c.tabs.show_switching_delay = 800
+
+## Open a new window for every tab.
+## Type: Bool
+# c.tabs.tabs_are_windows = False
+
+## Alignment of the text inside of tabs.
+## Type: TextAlignment
+## Valid values:
+## - left
+## - right
+## - center
+# c.tabs.title.alignment = 'left'
+
+## Format to use for the tab title. The following placeholders are
+## defined: * `{perc}`: Percentage as a string like `[10%]`. *
+## `{perc_raw}`: Raw percentage, e.g. `10`. * `{title}`: Title of the
+## current web page. * `{title_sep}`: The string ` - ` if a title is set,
+## empty otherwise. * `{index}`: Index of this tab. * `{id}`: Internal
+## tab ID of this tab. * `{scroll_pos}`: Page scroll position. *
+## `{host}`: Host of the current web page. * `{backend}`: Either
+## ''webkit'' or ''webengine'' * `{private}`: Indicates when private mode
+## is enabled. * `{current_url}`: URL of the current web page. *
+## `{protocol}`: Protocol (http/https/...) of the current web page.
+## Type: FormatString
+# c.tabs.title.format = '{index}: {title}'
+
+## Format to use for the tab title for pinned tabs. The same placeholders
+## like for `tabs.title.format` are defined.
+## Type: FormatString
+# c.tabs.title.format_pinned = '{index}'
+
+## Width (in pixels or as percentage of the window) of the tab bar if
+## it's vertical.
+## Type: PercOrInt
+# c.tabs.width = '20%'
+
+## Wrap when changing tabs.
+## Type: Bool
+# c.tabs.wrap = True
+
+## What search to start when something else than a URL is entered.
+## Type: String
+## Valid values:
+## - naive: Use simple/naive check.
+## - dns: Use DNS requests (might be slow!).
+## - never: Never search automatically.
+# c.url.auto_search = 'naive'
+
+## Page to open if :open -t/-b/-w is used without URL. Use `about:blank`
+## for a blank page.
+## Type: FuzzyUrl
+# c.url.default_page = 'https://start.duckduckgo.com/'
+c.url.default_page = '/home/taamas/.mainpage/index.html'
+
+## URL segments where `:navigate increment/decrement` will search for a
+## number.
+## Type: FlagList
+## Valid values:
+## - host
+## - path
+## - query
+## - anchor
+# c.url.incdec_segments = ['path', 'query']
+
+## Search engines which can be used via the address bar. Maps a search
+## engine name (such as `DEFAULT`, or `ddg`) to a URL with a `{}`
+## placeholder. The placeholder will be replaced by the search term, use
+## `{{` and `}}` for literal `{`/`}` signs. The search engine named
+## `DEFAULT` is used when `url.auto_search` is turned on and something
+## else than a URL was entered to be opened. Other search engines can be
+## used by prepending the search engine name to the search term, e.g.
+## `:open google qutebrowser`.
+## Type: Dict
+c.url.searchengines = {
+ 'DEFAULT': 'https://duckduckgo.com/?q={}', #duckduckgo
+ 'ddg': 'https://duckduckgo.com/?q={}', #duckduckgo
+ 's': 'https://search.disroot.org/search?q={}', #Disroot SearX
+ 'y': 'https://www.youtube.com/results?search_query={}', #youtube
+ 'tw': 'https://twitter.com/{}', #twitter
+ 'w': 'https://en.wikipedia.org/?search={}', #wikipedia
+ 'we': 'https://es.wikipedia.org/?search={}', #wikipedia(ES)
+ 'aw': 'https://wiki.archlinux.org/?search={}', #archWiki
+ 'sf': 'https://scryfall.com/search?q={}', #scryfall
+ 'mkm': 'https://www.cardmarket.com/en/Magic/Products/Search?searchString={}', #mkm
+ 'gi': 'https://game-icons.net/search.html?q={}', #game-icons
+ 'bc': 'https://bandcamp.com/search?q={}', #bancamp
+ 'mdd': 'https://www.megadede.com/search/{}', #megadede
+ 'gh': 'https://www.github.com/search?q={}', #github
+ 'ghu': 'https://github.com/search?q={}&type=Users', #github users
+ 'ep': 'https://emojipedia.org/search/?q={}', #emojipedia
+ 'rae': 'https://dle.rae.es/?w={}', #rae
+ 'maps': 'https://www.google.com/maps/place/{}', #google maps
+ 'im': 'https://imagemagick.org/script/search.php?q={}', # ImageMagick
+ 'mc': 'https://minecraft.gamepedia.com/{}', # Minecraft wiki
+ 'mb': 'https://musicbrainz.org/search?query={}&type=artist&method=indexed', # MusicBrainz (Artist)
+ 'df': 'https://dwarffortresswiki.org/index.php/DF2014:{}', # Dwarf Fortress Wiki
+ 'sl': 'https://senseis.xmp.net/?{}', # Sensei's Library
+ 'sls': 'https://senseis.xmp.net/?search={}&searchtype=title', # Sensei's Library search
+ 'bs': 'https://bsaber.com/?s={}', # Beast Saber
+ 'bp': 'https://bulbapedia.bulbagarden.net/wiki/{}', # Bulbapedia
+ 'bpm': 'https://bulbapedia.bulbagarden.net/wiki/{}#Learnset', # Bulbapedia (Learnset)
+ 'bpe': 'https://bulbapedia.bulbagarden.net/wiki/{}#Evolution', # Bulbapedia (Evolution)
+ 'sg': 'https://www.smogon.com/dex/ss/pokemon/{}', # Smogon
+ 'tex': 'https://www.ctan.org/search?phrase={}', # CTAN
+ 'keys': 'https://keys.openpgp.org/search?q={}', # openPGP keys
+ 'fl': 'https://fallenlondon.wiki/w/index.php?search={}' # Fallen London wiki
+}
+
+## Page(s) to open at the start.
+## Type: List of FuzzyUrl, or FuzzyUrl
+# c.url.start_pages = ['https://start.duckduckgo.com']
+c.url.start_pages = ['file:///home/taamas/.mainpage/index.html']
+
+## URL parameters to strip with `:yank url`.
+## Type: List of String
+# c.url.yank_ignored_parameters = ['ref', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']
+
+## Hide the window decoration when using wayland.
+## Type: Bool
+# c.window.hide_wayland_decoration = False
+
+## Format to use for the window title. The same placeholders like for
+## `tabs.title.format` are defined.
+## Type: FormatString
+# c.window.title_format = '{perc}{title}{title_sep}qutebrowser'
+
+## Default zoom level.
+## Type: Perc
+# c.zoom.default = '100%'
+
+## Available zoom levels.
+## Type: List of Perc
+# c.zoom.levels = ['25%', '33%', '50%', '67%', '75%', '90%', '100%', '110%', '125%', '150%', '175%', '200%', '250%', '300%', '400%', '500%']
+
+## Number of zoom increments to divide the mouse wheel movements to.
+## Type: Int
+# c.zoom.mouse_divider = 512
+
+## Apply the zoom factor on a frame only to the text or to all content.
+## Type: Bool
+# c.zoom.text_only = False
+
+## Bindings for normal mode
+# config.bind("'", 'enter-mode jump_mark')
+# config.bind('+', 'zoom-in')
+# config.bind('-', 'zoom-out')
+# config.bind('.', 'repeat-command')
+# config.bind('/', 'set-cmd-text /')
+# config.bind(':', 'set-cmd-text :')
+# config.bind(';I', 'hint images tab')
+# config.bind(';O', 'hint links fill :open -t -r {hint-url}')
+# config.bind(';R', 'hint --rapid links window')
+# config.bind(';Y', 'hint links yank-primary')
+# config.bind(';b', 'hint all tab-bg')
+# config.bind(';f', 'hint all tab-fg')
+# config.bind(';h', 'hint all hover')
+# config.bind(';i', 'hint images')
+# config.bind(';o', 'hint links fill :open {hint-url}')
+# config.bind(';r', 'hint --rapid links tab-bg')
+# config.bind(';t', 'hint inputs')
+# config.bind(';y', 'hint links yank')
+# config.bind('<Alt-1>', 'tab-focus 1')
+# config.bind('<Alt-2>', 'tab-focus 2')
+# config.bind('<Alt-3>', 'tab-focus 3')
+# config.bind('<Alt-4>', 'tab-focus 4')
+# config.bind('<Alt-5>', 'tab-focus 5')
+# config.bind('<Alt-6>', 'tab-focus 6')
+# config.bind('<Alt-7>', 'tab-focus 7')
+# config.bind('<Alt-8>', 'tab-focus 8')
+# config.bind('<Alt-9>', 'tab-focus -1')
+# config.bind('<Ctrl-A>', 'navigate increment')
+# config.bind('<Ctrl-Alt-p>', 'print')
+# config.bind('<Ctrl-B>', 'scroll-page 0 -1')
+# config.bind('<Ctrl-D>', 'scroll-page 0 0.5')
+# config.bind('<Ctrl-F5>', 'reload -f')
+# config.bind('<Ctrl-F>', 'scroll-page 0 1')
+# config.bind('<Ctrl-N>', 'open -w')
+# config.bind('<Ctrl-PgDown>', 'tab-next')
+# config.bind('<Ctrl-PgUp>', 'tab-prev')
+# config.bind('<Ctrl-Q>', 'quit')
+# config.bind('<Ctrl-Return>', 'follow-selected -t')
+# config.bind('<Ctrl-Shift-N>', 'open -p')
+# config.bind('<Ctrl-Shift-T>', 'undo')
+# config.bind('<Ctrl-Shift-W>', 'close')
+# config.bind('<Ctrl-T>', 'open -t')
+# config.bind('<Ctrl-Tab>', 'tab-focus last')
+# config.bind('<Ctrl-U>', 'scroll-page 0 -0.5')
+# config.bind('<Ctrl-V>', 'enter-mode passthrough')
+# config.bind('<Ctrl-W>', 'tab-close')
+# config.bind('<Ctrl-X>', 'navigate decrement')
+# config.bind('<Ctrl-^>', 'tab-focus last')
+# config.bind('<Ctrl-h>', 'home')
+# config.bind('<Ctrl-p>', 'tab-pin')
+# config.bind('<Ctrl-s>', 'stop')
+# config.bind('<Escape>', 'clear-keychain ;; search ;; fullscreen --leave')
+# config.bind('<F11>', 'fullscreen')
+# config.bind('<F5>', 'reload')
+# config.bind('<Return>', 'follow-selected')
+# config.bind('<back>', 'back')
+# config.bind('<forward>', 'forward')
+# config.bind('=', 'zoom')
+# config.bind('?', 'set-cmd-text ?')
+# config.bind('@', 'run-macro')
+# config.bind('B', 'set-cmd-text -s :quickmark-load -t')
+# config.bind('D', 'tab-close -o')
+config.unbind('D')
+# config.bind('F', 'hint all tab')
+# config.bind('G', 'scroll-to-perc')
+# config.bind('H', 'back')
+# config.bind('J', 'tab-next')
+# config.bind('K', 'tab-prev')
+# config.bind('L', 'forward')
+# config.bind('M', 'bookmark-add')
+# config.bind('N', 'search-prev')
+# config.bind('O', 'set-cmd-text -s :open -t')
+# config.bind('PP', 'open -t -- {primary}')
+# config.bind('Pp', 'open -t -- {clipboard}')
+# config.bind('R', 'reload -f')
+# config.bind('Sb', 'open qute://bookmarks#bookmarks')
+# config.bind('Sh', 'open qute://history')
+# config.bind('Sq', 'open qute://bookmarks')
+# config.bind('Ss', 'open qute://settings')
+# config.bind('T', 'tab-focus')
+# config.bind('ZQ', 'quit')
+# config.bind('ZZ', 'quit --save')
+# config.bind('[[', 'navigate prev')
+# config.bind(']]', 'navigate next')
+# config.bind('`', 'enter-mode set_mark')
+# config.bind('ad', 'download-cancel')
+# config.bind('b', 'set-cmd-text -s :quickmark-load')
+# config.bind('cd', 'download-clear')
+# config.bind('co', 'tab-only')
+# config.bind('d', 'tab-close')
+config.bind('d', 'scroll-page 0 0.5')
+config.bind('u', 'scroll-page 0 -0.5')
+config.bind('q', 'tab-close')
+# config.bind('f', 'hint')
+# config.bind('g$', 'tab-focus -1')
+# config.bind('g0', 'tab-focus 1')
+# config.bind('gB', 'set-cmd-text -s :bookmark-load -t')
+# config.bind('gC', 'tab-clone')
+# config.bind('gO', 'set-cmd-text :open -t -r {url:pretty}')
+# config.bind('gU', 'navigate up -t')
+# config.bind('g^', 'tab-focus 1')
+# config.bind('ga', 'open -t')
+# config.bind('gb', 'set-cmd-text -s :bookmark-load')
+# config.bind('gd', 'download')
+# config.bind('gf', 'view-source')
+# config.bind('gg', 'scroll-to-perc 0')
+# config.bind('gl', 'tab-move -')
+# config.bind('gm', 'tab-move')
+# config.bind('go', 'set-cmd-text :open {url:pretty}')
+# config.bind('gr', 'tab-move +')
+# config.bind('gt', 'set-cmd-text -s :buffer')
+# config.bind('gu', 'navigate up')
+# config.bind('h', 'scroll left')
+# config.bind('i', 'enter-mode insert')
+# config.bind('j', 'scroll down')
+# config.bind('k', 'scroll up')
+# config.bind('l', 'scroll right')
+# config.bind('m', 'quickmark-save')
+# config.bind('n', 'search-next')
+# config.bind('o', 'set-cmd-text -s :open')
+# config.bind('pP', 'open -- {primary}')
+# config.bind('pp', 'open -- {clipboard}')
+# config.bind('q', 'record-macro')
+config.bind('Q', 'record-macro')
+# config.bind('r', 'reload')
+# config.bind('sf', 'save')
+# config.bind('sk', 'set-cmd-text -s :bind')
+# config.bind('sl', 'set-cmd-text -s :set -t')
+# config.bind('ss', 'set-cmd-text -s :set')
+# config.bind('th', 'back -t')
+# config.bind('tl', 'forward -t')
+# config.bind('u', 'undo')
+config.bind('U', 'undo')
+# config.bind('v', 'enter-mode caret')
+# config.bind('wB', 'set-cmd-text -s :bookmark-load -w')
+# config.bind('wO', 'set-cmd-text :open -w {url:pretty}')
+# config.bind('wP', 'open -w -- {primary}')
+# config.bind('wb', 'set-cmd-text -s :quickmark-load -w')
+# config.bind('wf', 'hint all window')
+# config.bind('wh', 'back -w')
+# config.bind('wi', 'inspector')
+# config.bind('wl', 'forward -w')
+# config.bind('wo', 'set-cmd-text -s :open -w')
+# config.bind('wp', 'open -w -- {clipboard}')
+# config.bind('xO', 'set-cmd-text :open -b -r {url:pretty}')
+# config.bind('xo', 'set-cmd-text -s :open -b')
+# config.bind('yD', 'yank domain -s')
+# config.bind('yP', 'yank pretty-url -s')
+# config.bind('yT', 'yank title -s')
+# config.bind('yY', 'yank -s')
+# config.bind('yd', 'yank domain')
+# config.bind('yp', 'yank pretty-url')
+# config.bind('yt', 'yank title')
+# config.bind('yy', 'yank')
+# config.bind('{{', 'navigate prev -t')
+# config.bind('}}', 'navigate next -t')
+
+## Custom normal mode bindings
+# open url with mpv
+config.bind(',v', 'spawn mpv {url}')
+config.bind(',fv', 'hint links spawn mpv {hint-url}')
+config.bind(',Fv', 'hint all spawn mpv {hint-url}')
+config.bind(',V', 'spawn /home/taamas/scripts/floats/mpvFloat.sh {url}')
+config.bind(',fV', 'hint links spawn mpvFloat.sh {hint-url}')
+config.bind(',FV', 'hint all spawn mpvFloat.sh {hint-url}')
+#download url's video
+config.bind(',d', 'spawn youtube-dl --all-subs --embed-subs -o ~/downloads/videos/%(title)s.%(ext)s {url}')
+# same as previous, but showing urxvt
+config.bind(',D', 'spawn /home/taamas/scripts/floats/youtube-dlFloat.sh {url}')
+# two same commands, using hints instead than current url
+config.bind(',fd', 'hint links spawn youtube-dl.sh {hint-url}')
+config.bind(',Fd', 'hint all spawn youtube-dl.sh {hint-url}')
+config.bind(',fD', 'hint links spawn /home/taamas/scripts/floats/youtube-dlFloat.sh {hint-url}')
+config.bind(',FD', 'hint all spawn /home/taamas/scripts/floats/youtube-dlFloat.sh {hint-url}')
+# download url's audio in mp3 in ~/music/qutebrowser
+config.bind(',a', 'spawn youtube-dl -o ~/music/qutebrowser/%(title)s.%(ext)s -x --audio-format mp3 --embed-thumbnail {url}')
+# same as previous, but showing urxvt
+config.bind(',A', 'spawn /home/taamas/scripts/floats/youtube-dlFloatAudio.sh {url}')
+# two same commands, using hints instead than current url
+config.bind(',fa', 'hint links spawn /home/taamas/scripts/floats/youtube-dlAudio.sh {hint-url}')
+config.bind(',Fa', 'hint all spawn /home/taamas/scripts/floats/youtube-dlAudio.sh {hint-url}')
+config.bind(',fA', 'hint links spawn /home/taamas/scripts/floats/youtube-dlFloatAudio.sh {hint-url}')
+config.bind(',FA', 'hint all spawn /home/taamas/scripts/floats/youtube-dlFloatAudio.sh {hint-url}')
+# open images with corresponding script
+config.bind(',i', 'spawn showURLimage.sh {url}')
+config.bind(',fi', 'hint images spawn showURLimage.sh {hint-url}')
+config.bind(',fI', 'hint all spawn showURLimage.sh {hint-url}')
+# ripme bulk image downloading
+config.bind(',rm', 'spawn ripme.sh {url}')
+config.bind(',frm', 'hint links spawn ripme.sh {hint-url}')
+
+## Bindings for caret mode
+# config.bind('$', 'move-to-end-of-line', mode='caret')
+# config.bind('0', 'move-to-start-of-line', mode='caret')
+# config.bind('<Ctrl-Space>', 'drop-selection', mode='caret')
+# config.bind('<Escape>', 'leave-mode', mode='caret')
+# config.bind('<Return>', 'yank selection', mode='caret')
+# config.bind('<Space>', 'toggle-selection', mode='caret')
+# config.bind('G', 'move-to-end-of-document', mode='caret')
+# config.bind('H', 'scroll left', mode='caret')
+# config.bind('J', 'scroll down', mode='caret')
+# config.bind('K', 'scroll up', mode='caret')
+# config.bind('L', 'scroll right', mode='caret')
+# config.bind('Y', 'yank selection -s', mode='caret')
+# config.bind('[', 'move-to-start-of-prev-block', mode='caret')
+# config.bind(']', 'move-to-start-of-next-block', mode='caret')
+# config.bind('b', 'move-to-prev-word', mode='caret')
+# config.bind('c', 'enter-mode normal', mode='caret')
+# config.bind('e', 'move-to-end-of-word', mode='caret')
+# config.bind('gg', 'move-to-start-of-document', mode='caret')
+# config.bind('h', 'move-to-prev-char', mode='caret')
+# config.bind('j', 'move-to-next-line', mode='caret')
+# config.bind('k', 'move-to-prev-line', mode='caret')
+# config.bind('l', 'move-to-next-char', mode='caret')
+# config.bind('v', 'toggle-selection', mode='caret')
+# config.bind('w', 'move-to-next-word', mode='caret')
+# config.bind('y', 'yank selection', mode='caret')
+# config.bind('{', 'move-to-end-of-prev-block', mode='caret')
+# config.bind('}', 'move-to-end-of-next-block', mode='caret')
+
+## Bindings for command mode
+# config.bind('<Alt-B>', 'rl-backward-word', mode='command')
+# config.bind('<Alt-Backspace>', 'rl-backward-kill-word', mode='command')
+# config.bind('<Alt-D>', 'rl-kill-word', mode='command')
+# config.bind('<Alt-F>', 'rl-forward-word', mode='command')
+# config.bind('<Ctrl-?>', 'rl-delete-char', mode='command')
+# config.bind('<Ctrl-A>', 'rl-beginning-of-line', mode='command')
+# config.bind('<Ctrl-B>', 'rl-backward-char', mode='command')
+# config.bind('<Ctrl-C>', 'completion-item-yank', mode='command')
+# config.bind('<Ctrl-D>', 'completion-item-del', mode='command')
+# config.bind('<Ctrl-E>', 'rl-end-of-line', mode='command')
+# config.bind('<Ctrl-F>', 'rl-forward-char', mode='command')
+# config.bind('<Ctrl-H>', 'rl-backward-delete-char', mode='command')
+# config.bind('<Ctrl-K>', 'rl-kill-line', mode='command')
+# config.bind('<Ctrl-N>', 'command-history-next', mode='command')
+# config.bind('<Ctrl-P>', 'command-history-prev', mode='command')
+# config.bind('<Ctrl-Return>', 'command-accept --rapid', mode='command')
+# config.bind('<Ctrl-Shift-C>', 'completion-item-yank --sel', mode='command')
+# config.bind('<Ctrl-Shift-Tab>', 'completion-item-focus prev-category', mode='command')
+# config.bind('<Ctrl-Tab>', 'completion-item-focus next-category', mode='command')
+# config.bind('<Ctrl-U>', 'rl-unix-line-discard', mode='command')
+# config.bind('<Ctrl-W>', 'rl-unix-word-rubout', mode='command')
+# config.bind('<Ctrl-Y>', 'rl-yank', mode='command')
+# config.bind('<Down>', 'completion-item-focus --history next', mode='command')
+# config.bind('<Escape>', 'leave-mode', mode='command')
+# config.bind('<Return>', 'command-accept', mode='command')
+# config.bind('<Shift-Delete>', 'completion-item-del', mode='command')
+# config.bind('<Shift-Tab>', 'completion-item-focus prev', mode='command')
+# config.bind('<Tab>', 'completion-item-focus next', mode='command')
+# config.bind('<Up>', 'completion-item-focus --history prev', mode='command')
+
+## Bindings for hint mode
+# config.bind('<Ctrl-B>', 'hint all tab-bg', mode='hint')
+# config.bind('<Ctrl-F>', 'hint links', mode='hint')
+# config.bind('<Ctrl-R>', 'hint --rapid links tab-bg', mode='hint')
+# config.bind('<Escape>', 'leave-mode', mode='hint')
+# config.bind('<Return>', 'follow-hint', mode='hint')
+
+## Bindings for insert mode
+# config.bind('<Ctrl-E>', 'open-editor', mode='insert')
+# config.bind('<Escape>', 'leave-mode', mode='insert')
+# config.bind('<Shift-Ins>', 'insert-text {primary}', mode='insert')
+
+## Bindings for passthrough mode
+# config.bind('<Ctrl-V>', 'leave-mode', mode='passthrough')
+
+## Bindings for prompt mode
+# config.bind('<Alt-B>', 'rl-backward-word', mode='prompt')
+# config.bind('<Alt-Backspace>', 'rl-backward-kill-word', mode='prompt')
+# config.bind('<Alt-D>', 'rl-kill-word', mode='prompt')
+# config.bind('<Alt-F>', 'rl-forward-word', mode='prompt')
+# config.bind('<Ctrl-?>', 'rl-delete-char', mode='prompt')
+# config.bind('<Ctrl-A>', 'rl-beginning-of-line', mode='prompt')
+# config.bind('<Ctrl-B>', 'rl-backward-char', mode='prompt')
+# config.bind('<Ctrl-E>', 'rl-end-of-line', mode='prompt')
+# config.bind('<Ctrl-F>', 'rl-forward-char', mode='prompt')
+# config.bind('<Ctrl-H>', 'rl-backward-delete-char', mode='prompt')
+# config.bind('<Ctrl-K>', 'rl-kill-line', mode='prompt')
+# config.bind('<Ctrl-U>', 'rl-unix-line-discard', mode='prompt')
+# config.bind('<Ctrl-W>', 'rl-unix-word-rubout', mode='prompt')
+# config.bind('<Ctrl-X>', 'prompt-open-download', mode='prompt')
+# config.bind('<Ctrl-Y>', 'rl-yank', mode='prompt')
+# config.bind('<Down>', 'prompt-item-focus next', mode='prompt')
+# config.bind('<Escape>', 'leave-mode', mode='prompt')
+# config.bind('<Return>', 'prompt-accept', mode='prompt')
+# config.bind('<Shift-Tab>', 'prompt-item-focus prev', mode='prompt')
+# config.bind('<Tab>', 'prompt-item-focus next', mode='prompt')
+# config.bind('<Up>', 'prompt-item-focus prev', mode='prompt')
+# config.bind('n', 'prompt-accept no', mode='prompt')
+# config.bind('y', 'prompt-accept yes', mode='prompt')
+
+## Bindings for register mode
+# config.bind('<Escape>', 'leave-mode', mode='register')
+
+dracula.draw.blood(c, {
+ 'spacing': {
+ 'vertical': 3,
+ 'horizontal': 8
+ }
+})
diff --git a/stow/ranger/dot-config/ranger/rc.conf b/stow/ranger/dot-config/ranger/rc.conf
new file mode 100644
index 0000000..4fe51db
--- /dev/null
+++ b/stow/ranger/dot-config/ranger/rc.conf
@@ -0,0 +1,640 @@
+# ===================================================================
+# This file contains the default startup commands for ranger.
+# To change them, it is recommended to create the file
+# ~/.config/ranger/rc.conf and add your custom commands there.
+#
+# If you copy this whole file there, you may want to set the environment
+# variable RANGER_LOAD_DEFAULT_RC to FALSE to avoid loading it twice.
+#
+# The purpose of this file is mainly to define keybindings and settings.
+# For running more complex python code, please create a plugin in "plugins/" or
+# a command in "commands.py".
+#
+# Each line is a command that will be run before the user interface
+# is initialized. As a result, you can not use commands which rely
+# on the UI such as :delete or :mark.
+# ===================================================================
+
+# ===================================================================
+# == Options
+# ===================================================================
+
+# Which viewmode should be used? Possible values are:
+# miller: Use miller columns which show multiple levels of the hierarchy
+# multipane: Midnight-commander like multipane view showing all tabs next
+# to each other
+set viewmode miller
+#set viewmode multipane
+
+# How many columns are there, and what are their relative widths?
+set column_ratios 1,3,4
+
+# Which files should be hidden? (regular expression)
+set hidden_filter ^\.|\.(?:pyc|pyo|bak|swp)$|^lost\+found$|^__(py)?cache__$
+
+# Show hidden files? You can toggle this by typing 'zh'
+set show_hidden false
+
+# Ask for a confirmation when running the "delete" command?
+# Valid values are "always", "never", "multiple" (default)
+# With "multiple", ranger will ask only if you delete multiple files at once.
+set confirm_on_delete multiple
+
+# Which script is used to generate file previews?
+# ranger ships with scope.sh, a script that calls external programs (see
+# README.md for dependencies) to preview images, archives, etc.
+set preview_script ~/.config/ranger/scope.sh
+
+# Use the external preview script or display simple plain text or image previews?
+set use_preview_script true
+
+# Automatically count files in the directory, even before entering them?
+set automatically_count_files true
+
+# Open all images in this directory when running certain image viewers
+# like feh or sxiv? You can still open selected files by marking them.
+set open_all_images true
+
+# Be aware of version control systems and display information.
+set vcs_aware true
+
+# State of the three backends git, hg, bzr. The possible states are
+# disabled, local (only show local info), enabled (show local and remote
+# information).
+set vcs_backend_git enabled
+set vcs_backend_hg disabled
+set vcs_backend_bzr disabled
+
+# Use one of the supported image preview protocols
+# set preview_images false
+set preview_images true
+
+# Set the preview image method. Supported methods:
+#
+# * w3m (default):
+# Preview images in full color with the external command "w3mimgpreview"?
+# This requires the console web browser "w3m" and a supported terminal.
+# It has been successfully tested with "xterm" and "urxvt" without tmux.
+#
+# * iterm2:
+# Preview images in full color using iTerm2 image previews
+# (http://iterm2.com/images.html). This requires using iTerm2 compiled
+# with image preview support.
+#
+# * urxvt:
+# Preview images in full color using urxvt image backgrounds. This
+# requires using urxvt compiled with pixbuf support.
+#
+# * urxvt-full:
+# The same as urxvt but utilizing not only the preview pane but the
+# whole terminal window.
+set preview_images_method w3m
+
+# Use a unicode "..." character to mark cut-off filenames?
+set unicode_ellipsis false
+
+# Show dotfiles in the bookmark preview box?
+set show_hidden_bookmarks true
+
+# Which colorscheme to use? These colorschemes are available by default:
+# default, jungle, snow, solarized
+set colorscheme default
+
+# Preview files on the rightmost column?
+# And collapse (shrink) the last column if there is nothing to preview?
+set preview_files true
+set preview_directories true
+set collapse_preview true
+
+# Save the console history on exit?
+set save_console_history true
+
+# Draw the status bar on top of the browser window (default: bottom)
+set status_bar_on_top false
+
+# Draw a progress bar in the status bar which displays the average state of all
+# currently running tasks which support progress bars?
+set draw_progress_bar_in_status_bar true
+
+# Draw borders around columns?
+set draw_borders true
+
+# Display the directory name in tabs?
+set dirname_in_tabs false
+
+# Enable the mouse support?
+set mouse_enabled true
+
+# Display the file size in the main column or status bar?
+set display_size_in_main_column true
+set display_size_in_status_bar true
+
+# Display files tags in all columns or only in main column?
+set display_tags_in_all_columns true
+
+# Set a title for the window?
+set update_title true
+
+# Set the title to "ranger" in the tmux program?
+set update_tmux_title true
+
+# Shorten the title if it gets long? The number defines how many
+# directories are displayed at once, 0 turns off this feature.
+set shorten_title 3
+
+# Abbreviate $HOME with ~ in the titlebar (first line) of ranger?
+set tilde_in_titlebar true
+
+# How many directory-changes or console-commands should be kept in history?
+set max_history_size 20
+set max_console_history_size 50
+
+# Try to keep so much space between the top/bottom border when scrolling:
+set scroll_offset 8
+
+# Flush the input after each key hit? (Noticeable when ranger lags)
+set flushinput true
+
+# Padding on the right when there's no preview?
+# This allows you to click into the space to run the file.
+set padding_right false
+
+# Save bookmarks (used with mX and `X) instantly?
+# This helps to synchronize bookmarks between multiple ranger
+# instances but leads to *slight* performance loss.
+# When false, bookmarks are saved when ranger is exited.
+set autosave_bookmarks true
+
+# You can display the "real" cumulative size of directories by using the
+# command :get_cumulative_size or typing "dc". The size is expensive to
+# calculate and will not be updated automatically. You can choose
+# to update it automatically though by turning on this option:
+set autoupdate_cumulative_size false
+
+# Turning this on makes sense for screen readers:
+set show_cursor false
+
+# One of: size, natural, basename, atime, ctime, mtime, type, random
+set sort natural
+
+# Additional sorting options
+set sort_reverse false
+set sort_case_insensitive true
+set sort_directories_first true
+set sort_unicode false
+
+# Enable this if key combinations with the Alt Key don't work for you.
+# (Especially on xterm)
+set xterm_alt_key false
+
+# Whether to include bookmarks in cd command
+set cd_bookmarks true
+
+# Avoid previewing files larger than this size, in bytes. Use a value of 0 to
+# disable this feature.
+set preview_max_size 0
+
+# Add the highlighted file to the path in the titlebar
+set show_selection_in_titlebar true
+
+# The delay that ranger idly waits for user input, in milliseconds, with a
+# resolution of 100ms. Lower delay reduces lag between directory updates but
+# increases CPU load.
+set idle_delay 2000
+
+# When the metadata manager module looks for metadata, should it only look for
+# a ".metadata.json" file in the current directory, or do a deep search and
+# check all directories above the current one as well?
+set metadata_deep_search false
+
+# Clear all existing filters when leaving a directory
+set clear_filters_on_dir_change false
+
+# Disable displaying line numbers in main column
+set line_numbers false
+
+# ===================================================================
+# == Local Options
+# ===================================================================
+# You can set local options that only affect a single directory.
+
+# Examples:
+setlocal path=~/downloads sort mtime
+setlocal path=~/downloads sort_reverse True
+setlocal path=~/downloads/videos sort mtime
+setlocal path=~/downloads/videos sort_reverse True
+setlocal path=~/downloads/audios sort mtime
+setlocal path=~/downloads/audios sort_reverse True
+setlocal path=~/scp sort mtime
+setlocal path=~/scp sort_reverse True
+
+# ===================================================================
+# == Command Aliases in the Console
+# ===================================================================
+
+alias e edit
+alias q quit
+alias q! quitall
+alias qa quitall
+alias qall quitall
+alias setl setlocal
+
+alias filter scout -prt
+alias find scout -aeit
+alias mark scout -mr
+alias unmark scout -Mr
+alias search scout -rs
+alias search_inc scout -rts
+alias travel scout -aefiklst
+
+# ===================================================================
+# == Define keys for the browser
+# ===================================================================
+
+# Custom
+map ,b bulkrename
+map ,d shell rm.sh %s
+map ,s shell tmux split-window -p 60 -c %d &
+map ,S shell tmux new-window -a -n shell -c %d &
+map ,e shell tmux new-window -a -n $EDITOR "$EDITOR %s"
+map ,w shell setBG.sh %f &
+map ,W console shell ~/scripts/floats/floatBgBrowser.sh %d/ &
+map ,p shell asPDF.sh %f
+map ,P shell lowriter --convert-to pdf %f
+map ,um shell sudo umount %p
+map ,z shell zip -r %f.zip %s
+map ,uz shell 7z x %f
+map ,ur shell unrar x %f
+map ,ut shell tar -xvzf %f
+map ,m shell move.sh %s
+map ,c shell copy.sh %s
+map ,C shell scp.sh %s
+#map ,g shell cd $(selectPathFzf.sh)
+
+# Basic
+map Q quit!
+map q quit
+copymap q ZZ ZQ
+
+map R reload_cwd
+map <C-r> reset
+map <C-l> redraw_window
+map <C-c> abort
+map <esc> change_mode normal
+map ~ set viewmode!
+
+map i display_file
+map ? help
+map W display_log
+map w taskview_open
+map S shell $SHELL
+
+map : console
+map ; console
+map ! console shell%space
+map @ console -p6 shell %%s
+map # console shell -p%space
+map s console shell%space
+map r chain draw_possible_programs; console open_with%space
+map f console find%space
+map cd console cd%space
+
+# Change the line mode
+map Mf linemode filename
+map Mi linemode fileinfo
+map Mm linemode mtime
+map Mp linemode permissions
+map Ms linemode sizemtime
+map Mt linemode metatitle
+
+# Tagging / Marking
+map t tag_toggle
+map ut tag_remove
+map "<any> tag_toggle tag=%any
+map <Space> mark_files toggle=True
+map v mark_files all=True toggle=True
+map uv mark_files all=True val=False
+map V toggle_visual_mode
+map uV toggle_visual_mode reverse=True
+
+# For the nostalgics: Midnight Commander bindings
+map <F1> help
+map <F3> display_file
+map <F4> edit
+map <F5> copy
+map <F6> cut
+map <F7> console mkdir%space
+map <F8> console delete
+map <F10> exit
+
+# In case you work on a keyboard with dvorak layout
+map <UP> move up=1
+map <DOWN> move down=1
+map <LEFT> move left=1
+map <RIGHT> move right=1
+map <HOME> move to=0
+map <END> move to=-1
+map <PAGEDOWN> move down=1 pages=True
+map <PAGEUP> move up=1 pages=True
+map <CR> move right=1
+#map <DELETE> console delete
+map <INSERT> console touch%space
+
+# VIM-like
+copymap <UP> k
+copymap <DOWN> j
+copymap <LEFT> h
+copymap <RIGHT> l
+copymap <HOME> gg
+copymap <END> G
+copymap <PAGEDOWN> <C-F>
+copymap <PAGEUP> <C-B>
+
+map J move down=0.5 pages=True
+map K move up=0.5 pages=True
+copymap J <C-D>
+copymap K <C-U>
+
+# Jumping around
+map H history_go -1
+map L history_go 1
+map ] move_parent 1
+map [ move_parent -1
+map } traverse
+
+map gh cd ~
+map ge cd /etc
+map gu cd /usr
+map gd cd /dev
+map gl cd -r .
+map gL cd -r %f
+map go cd /opt
+map gv cd /var
+map gm cd /media
+map gM cd /mnt
+map gs cd /srv
+map gr cd /
+map gR eval fm.cd(ranger.RANGERDIR)
+map g/ cd /
+map g? cd /usr/share/doc/ranger
+
+# External Programs
+map E edit
+map du shell -p du --max-depth=1 -h --apparent-size
+map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh
+map yp shell -f echo -n %d/%f | xsel -i; xsel -o | xsel -i -b
+map yd shell -f echo -n %d | xsel -i; xsel -o | xsel -i -b
+map yn shell -f echo -n %f | xsel -i; xsel -o | xsel -i -b
+
+# Filesystem Operations
+map = chmod
+
+map cw console rename%space
+map a rename_append
+map A eval fm.open_console('rename ' + fm.thisfile.relative_path.replace("%", "%%"))
+map I eval fm.open_console('rename ' + fm.thisfile.relative_path.replace("%", "%%"), position=7)
+
+map pp paste
+map po paste overwrite=True
+map pP paste append=True
+map pO paste overwrite=True append=True
+map pl paste_symlink relative=False
+map pL paste_symlink relative=True
+map phl paste_hardlink
+map pht paste_hardlinked_subtree
+
+map dD console delete
+
+map dd cut
+map ud uncut
+map da cut mode=add
+map dr cut mode=remove
+map dt cut mode=toggle
+
+map yy copy
+map uy uncut
+map ya copy mode=add
+map yr copy mode=remove
+map yt copy mode=toggle
+
+# Temporary workarounds
+map dgg eval fm.cut(dirarg=dict(to=0), narg=quantifier)
+map dG eval fm.cut(dirarg=dict(to=-1), narg=quantifier)
+map dj eval fm.cut(dirarg=dict(down=1), narg=quantifier)
+map dk eval fm.cut(dirarg=dict(up=1), narg=quantifier)
+map ygg eval fm.copy(dirarg=dict(to=0), narg=quantifier)
+map yG eval fm.copy(dirarg=dict(to=-1), narg=quantifier)
+map yj eval fm.copy(dirarg=dict(down=1), narg=quantifier)
+map yk eval fm.copy(dirarg=dict(up=1), narg=quantifier)
+
+# Searching
+map / console search%space
+map n search_next
+map N search_next forward=False
+map ct search_next order=tag
+map cs search_next order=size
+map ci search_next order=mimetype
+map cc search_next order=ctime
+map cm search_next order=mtime
+map ca search_next order=atime
+
+# Tabs
+map <C-n> tab_new ~
+map <C-w> tab_close
+map <TAB> tab_move 1
+map <S-TAB> tab_move -1
+map <A-Right> tab_move 1
+map <A-Left> tab_move -1
+map gt tab_move 1
+map gT tab_move -1
+map gn tab_new ~
+map gc tab_close
+map uq tab_restore
+map <a-1> tab_open 1
+map <a-2> tab_open 2
+map <a-3> tab_open 3
+map <a-4> tab_open 4
+map <a-5> tab_open 5
+map <a-6> tab_open 6
+map <a-7> tab_open 7
+map <a-8> tab_open 8
+map <a-9> tab_open 9
+
+# Sorting
+map or set sort_reverse!
+map oz set sort=random
+map os chain set sort=size; set sort_reverse=False
+map ob chain set sort=basename; set sort_reverse=False
+map on chain set sort=natural; set sort_reverse=False
+map om chain set sort=mtime; set sort_reverse=False
+map oc chain set sort=ctime; set sort_reverse=False
+map oa chain set sort=atime; set sort_reverse=False
+map ot chain set sort=type; set sort_reverse=False
+map oe chain set sort=extension; set sort_reverse=False
+
+map oS chain set sort=size; set sort_reverse=True
+map oB chain set sort=basename; set sort_reverse=True
+map oN chain set sort=natural; set sort_reverse=True
+map oM chain set sort=mtime; set sort_reverse=True
+map oC chain set sort=ctime; set sort_reverse=True
+map oA chain set sort=atime; set sort_reverse=True
+map oT chain set sort=type; set sort_reverse=True
+map oE chain set sort=extension; set sort_reverse=True
+
+map dc get_cumulative_size
+
+# Settings
+map zc set collapse_preview!
+map zd set sort_directories_first!
+map zh set show_hidden!
+map <C-h> set show_hidden!
+map zI set flushinput!
+map zi set preview_images!
+map zm set mouse_enabled!
+map zp set preview_files!
+map zP set preview_directories!
+map zs set sort_case_insensitive!
+map zu set autoupdate_cumulative_size!
+map zv set use_preview_script!
+map zf console filter%space
+
+# Bookmarks
+map `<any> enter_bookmark %any
+map '<any> enter_bookmark %any
+map m<any> set_bookmark %any
+map um<any> unset_bookmark %any
+
+map m<bg> draw_bookmarks
+copymap m<bg> um<bg> `<bg> '<bg>
+
+# Generate all the chmod bindings with some python help:
+eval for arg in "rwxXst": cmd("map +u{0} shell -f chmod u+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +g{0} shell -f chmod g+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +o{0} shell -f chmod o+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +a{0} shell -f chmod a+{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map +{0} shell -f chmod u+{0} %s".format(arg))
+
+eval for arg in "rwxXst": cmd("map -u{0} shell -f chmod u-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -g{0} shell -f chmod g-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -o{0} shell -f chmod o-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -a{0} shell -f chmod a-{0} %s".format(arg))
+eval for arg in "rwxXst": cmd("map -{0} shell -f chmod u-{0} %s".format(arg))
+
+# ===================================================================
+# == Define keys for the console
+# ===================================================================
+# Note: Unmapped keys are passed directly to the console.
+
+# Basic
+cmap <tab> eval fm.ui.console.tab()
+cmap <s-tab> eval fm.ui.console.tab(-1)
+cmap <ESC> eval fm.ui.console.close()
+cmap <CR> eval fm.ui.console.execute()
+cmap <C-l> redraw_window
+
+copycmap <ESC> <C-c>
+copycmap <CR> <C-j>
+
+# Move around
+cmap <up> eval fm.ui.console.history_move(-1)
+cmap <down> eval fm.ui.console.history_move(1)
+cmap <left> eval fm.ui.console.move(left=1)
+cmap <right> eval fm.ui.console.move(right=1)
+cmap <home> eval fm.ui.console.move(right=0, absolute=True)
+cmap <end> eval fm.ui.console.move(right=-1, absolute=True)
+cmap <a-left> eval fm.ui.console.move_word(left=1)
+cmap <a-right> eval fm.ui.console.move_word(right=1)
+
+# Line Editing
+cmap <backspace> eval fm.ui.console.delete(-1)
+cmap <delete> eval fm.ui.console.delete(0)
+cmap <C-w> eval fm.ui.console.delete_word()
+cmap <A-d> eval fm.ui.console.delete_word(backward=False)
+cmap <C-k> eval fm.ui.console.delete_rest(1)
+cmap <C-u> eval fm.ui.console.delete_rest(-1)
+cmap <C-y> eval fm.ui.console.paste()
+
+# And of course the emacs way
+copycmap <up> <C-p>
+copycmap <down> <C-n>
+copycmap <left> <C-b>
+copycmap <right> <C-f>
+copycmap <home> <C-a>
+copycmap <end> <C-e>
+copycmap <delete> <C-d>
+copycmap <backspace> <C-h>
+
+# Note: There are multiple ways to express backspaces. <backspace> (code 263)
+# and <backspace2> (code 127). To be sure, use both.
+copycmap <backspace> <backspace2>
+
+# This special expression allows typing in numerals:
+cmap <allow_quantifiers> false
+
+# ===================================================================
+# == Pager Keybindings
+# ===================================================================
+
+# Movement
+pmap <down> pager_move down=1
+pmap <up> pager_move up=1
+pmap <left> pager_move left=4
+pmap <right> pager_move right=4
+pmap <home> pager_move to=0
+pmap <end> pager_move to=-1
+pmap <pagedown> pager_move down=1.0 pages=True
+pmap <pageup> pager_move up=1.0 pages=True
+pmap <C-d> pager_move down=0.5 pages=True
+pmap <C-u> pager_move up=0.5 pages=True
+
+copypmap <UP> k <C-p>
+copypmap <DOWN> j <C-n> <CR>
+copypmap <LEFT> h
+copypmap <RIGHT> l
+copypmap <HOME> g
+copypmap <END> G
+copypmap <C-d> d
+copypmap <C-u> u
+copypmap <PAGEDOWN> n f <C-F> <Space>
+copypmap <PAGEUP> p b <C-B>
+
+# Basic
+pmap <C-l> redraw_window
+pmap <ESC> pager_close
+copypmap <ESC> q Q i <F3>
+pmap E edit_file
+
+# ===================================================================
+# == Taskview Keybindings
+# ===================================================================
+
+# Movement
+tmap <up> taskview_move up=1
+tmap <down> taskview_move down=1
+tmap <home> taskview_move to=0
+tmap <end> taskview_move to=-1
+tmap <pagedown> taskview_move down=1.0 pages=True
+tmap <pageup> taskview_move up=1.0 pages=True
+tmap <C-d> taskview_move down=0.5 pages=True
+tmap <C-u> taskview_move up=0.5 pages=True
+
+copytmap <UP> k <C-p>
+copytmap <DOWN> j <C-n> <CR>
+copytmap <HOME> g
+copytmap <END> G
+copytmap <C-u> u
+copytmap <PAGEDOWN> n f <C-F> <Space>
+copytmap <PAGEUP> p b <C-B>
+
+# Changing priority and deleting tasks
+tmap J eval -q fm.ui.taskview.task_move(-1)
+tmap K eval -q fm.ui.taskview.task_move(0)
+tmap dd eval -q fm.ui.taskview.task_remove()
+tmap <pagedown> eval -q fm.ui.taskview.task_move(-1)
+tmap <pageup> eval -q fm.ui.taskview.task_move(0)
+tmap <delete> eval -q fm.ui.taskview.task_remove()
+
+# Basic
+tmap <C-l> redraw_window
+tmap <ESC> taskview_close
+copytmap <ESC> q Q w <C-c>
diff --git a/stow/ranger/dot-config/ranger/rifle.conf b/stow/ranger/dot-config/ranger/rifle.conf
new file mode 100644
index 0000000..35a10a0
--- /dev/null
+++ b/stow/ranger/dot-config/ranger/rifle.conf
@@ -0,0 +1,226 @@
+# vim: ft=cfg
+#
+# This is the configuration file of "rifle", ranger's file executor/opener.
+# Each line consists of conditions and a command. For each line the conditions
+# are checked and if they are met, the respective command is run.
+#
+# Syntax:
+# <condition1> , <condition2> , ... = command
+#
+# The command can contain these environment variables:
+# $1-$9 | The n-th selected file
+# $@ | All selected files
+#
+# If you use the special command "ask", rifle will ask you what program to run.
+#
+# Prefixing a condition with "!" will negate its result.
+# These conditions are currently supported:
+# match <regexp> | The regexp matches $1
+# ext <regexp> | The regexp matches the extension of $1
+# mime <regexp> | The regexp matches the mime type of $1
+# name <regexp> | The regexp matches the basename of $1
+# path <regexp> | The regexp matches the absolute path of $1
+# has <program> | The program is installed (i.e. located in $PATH)
+# env <variable> | The environment variable "variable" is non-empty
+# file | $1 is a file
+# directory | $1 is a directory
+# number <n> | change the number of this command to n
+# terminal | stdin, stderr and stdout are connected to a terminal
+# X | $DISPLAY is not empty (i.e. Xorg runs)
+#
+# There are also pseudo-conditions which have a "side effect":
+# flag <flags> | Change how the program is run. See below.
+# label <label> | Assign a label or name to the command so it can
+# | be started with :open_with <label> in ranger
+# | or `rifle -p <label>` in the standalone executable.
+# else | Always true.
+#
+# Flags are single characters which slightly transform the command:
+# f | Fork the program, make it run in the background.
+# | New command = setsid $command >& /dev/null &
+# r | Execute the command with root permissions
+# | New command = sudo $command
+# t | Run the program in a new terminal. If $TERMCMD is not defined,
+# | rifle will attempt to extract it from $TERM.
+# | New command = $TERMCMD -e $command
+# Note: The "New command" serves only as an illustration, the exact
+# implementation may differ.
+# Note: When using rifle in ranger, there is an additional flag "c" for
+# only running the current file even if you have marked multiple files.
+
+#-------------------------------------------
+# Websites
+#-------------------------------------------
+# Rarely installed browsers get higher priority; It is assumed that if you
+# install a rare browser, you probably use it. Firefox/konqueror/w3m on the
+# other hand are often only installed as fallback browsers.
+ext x?html?, has surf, X, flag f = surf -- file://"$1"
+ext x?html?, has vimprobable, X, flag f = vimprobable -- "$@"
+ext x?html?, has vimprobable2, X, flag f = vimprobable2 -- "$@"
+ext x?html?, has qutebrowser, X, flag f = qutebrowser -- "$@"
+ext x?html?, has dwb, X, flag f = dwb -- "$@"
+ext x?html?, has jumanji, X, flag f = jumanji -- "$@"
+ext x?html?, has luakit, X, flag f = luakit -- "$@"
+ext x?html?, has uzbl, X, flag f = uzbl -- "$@"
+ext x?html?, has uzbl-tabbed, X, flag f = uzbl-tabbed -- "$@"
+ext x?html?, has uzbl-browser, X, flag f = uzbl-browser -- "$@"
+ext x?html?, has uzbl-core, X, flag f = uzbl-core -- "$@"
+ext x?html?, has midori, X, flag f = midori -- "$@"
+ext x?html?, has chromium, X, flag f = chromium -- "$@"
+ext x?html?, has opera, X, flag f = opera -- "$@"
+ext x?html?, has firefox, X, flag f = firefox -- "$@"
+ext x?html?, has seamonkey, X, flag f = seamonkey -- "$@"
+ext x?html?, has iceweasel, X, flag f = iceweasel -- "$@"
+ext x?html?, has epiphany, X, flag f = epiphany -- "$@"
+ext x?html?, has konqueror, X, flag f = konqueror -- "$@"
+ext x?html?, has elinks, terminal = elinks "$@"
+ext x?html?, has links2, terminal = links2 "$@"
+ext x?html?, has links, terminal = links "$@"
+ext x?html?, has lynx, terminal = lynx -- "$@"
+ext x?html?, has w3m, terminal = w3m "$@"
+
+#-------------------------------------------
+# Misc
+#-------------------------------------------
+# Define the "editor" for text files as first action
+mime ^text, label editor = $EDITOR -- "$@"
+mime ^text, label pager = "$PAGER" -- "$@"
+!mime ^text, label editor, ext xml|json|csv|tex|py|pl|rb|js|sh|php = $EDITOR -- "$@"
+!mime ^text, label pager, ext xml|json|csv|tex|py|pl|rb|js|sh|php = "$PAGER" -- "$@"
+
+ext 1 = man "$1"
+ext s[wmf]c, has zsnes, X = zsnes "$1"
+ext s[wmf]c, has snes9x-gtk,X = snes9x-gtk "$1"
+ext nes, has fceux, X = fceux "$1"
+ext exe = wine "$1"
+name ^[mM]akefile$ = make
+
+#--------------------------------------------
+# Code
+#-------------------------------------------
+ext py = python -- "$1"
+ext pl = perl -- "$1"
+ext rb = ruby -- "$1"
+ext js = node -- "$1"
+ext sh = sh -- "$1"
+ext php = php -- "$1"
+ext ino = arduino & -- "$@"
+
+#--------------------------------------------
+# Audio without X
+#-------------------------------------------
+mime ^audio|ogg$, terminal, has mpv = urxvt -title "floating" -geometry "120x4-0-0" -e mpv "$@" &
+#mime ^audio|ogg$, terminal, has mpv = urxvt -title "floating" -geometry "120x4-0-0" -e mpv --no-audio-display "$@" &
+mime ^audio|ogg$, terminal, has mpv = urxvt -e mpv "$@" &
+#mime ^audio|ogg$, terminal, has mplayer2 = mplayer2 -- "$@"
+#mime ^audio|ogg$, terminal, has mplayer = mplayer -- "$@"
+#ext midi?, terminal, has wildmidi = wildmidi -- "$@"
+
+#--------------------------------------------
+# Video/Audio with a GUI
+#-------------------------------------------
+mime ^video, has mpv, X, flag f = bash ~/scripts/floats/mpvFloat.sh "$@"
+mime ^video, has mpv, X, flag f = bash ~/scripts/floats/mpvFloatLoop.sh "$@"
+mime ^video, has mpv, X, flag f = mpv --fs -- "$@"
+mime ^video, has mpv, X, flag f = mpv -- "$@"
+mime ^video, has mpv, X, flag f = mpv -vo caca -- "$@"
+mime ^video|audio, has vlc, X, flag f = vlc -- "$@"
+#mime ^video|audio, has gmplayer, X, flag f = gmplayer -- "$@"
+#mime ^video|audio, has smplayer, X, flag f = smplayer "$@"
+#mime ^video, has mplayer2, X, flag f = mplayer2 -- "$@"
+#mime ^video, has mplayer2, X, flag f = mplayer2 -fs -- "$@"
+#mime ^video, has mplayer, X, flag f = mplayer -- "$@"
+#mime ^video, has mplayer, X, flag f = mplayer -fs -- "$@"
+#mime ^video|audio, has totem, X, flag f = totem -- "$@"
+#mime ^video|audio, has totem, X, flag f = totem --fullscreen -- "$@"
+
+#--------------------------------------------
+# Video without X:
+#-------------------------------------------
+mime ^video, terminal, !X, has mpv = mpv -vo caca -- "$@"
+mime ^video, terminal, !X, has mplayer2 = mplayer2 -- "$@"
+mime ^video, terminal, !X, has mplayer = mplayer -- "$@"
+
+#-------------------------------------------
+# Documents
+#-------------------------------------------
+ext pdf, has zathura, X, flag f = zathura -- "$@"
+ext pdf, has mupdf, X, flag f = mupdf "$@"
+ext pdf, has mupdf-x11,X, flag f = mupdf-x11 "$@"
+#ext pdf, has llpp, X, flag f = llpp "$@"
+#ext pdf, has apvlv, X, flag f = apvlv -- "$@"
+#ext pdf, has xpdf, X, flag f = xpdf -- "$@"
+#ext pdf, has evince, X, flag f = evince -- "$@"
+#ext pdf, has atril, X, flag f = atril -- "$@"
+#ext pdf, has okular, X, flag f = okular -- "$@"
+#ext pdf, has epdfview, X, flag f = epdfview -- "$@"
+#ext pdf, has qpdfview, X, flag f = qpdfview "$@"
+
+ext docx?, has catdoc, terminal = catdoc -- "$@" | "$PAGER"
+
+ext sxc|xlsx?|xlt|xlw|gnm|gnumeric, has gnumeric, X, flag f = gnumeric -- "$@"
+ext sxc|xlsx?|xlt|xlw|gnm|gnumeric, has kspread, X, flag f = kspread -- "$@"
+ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has libreoffice, X, flag f = libreoffice "$@"
+ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has soffice, X, flag f = soffice "$@"
+ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has ooffice, X, flag f = ooffice "$@"
+
+ext djvu, has zathura,X, flag f = zathura -- "$@"
+ext djvu, has evince, X, flag f = evince -- "$@"
+ext djvu, has atril, X, flag f = atril -- "$@"
+
+#-------------------------------------------
+# Image Viewing:
+#-------------------------------------------
+
+# Gifs
+ext gif, has mpv, X, flag f = mpv --loop-playlist "$@"
+ext webm, has mpv, X, flag f = mpv --loop-playlist "$@"
+
+mime ^image/svg, has inkscape, X, flag f = inkscape -- "$@"
+mime ^image/svg, has display, X, flag f = display -- "$@"
+
+mime ^image, has sxiv, X, flag f = sxiv -b -- "$@"
+mime ^image, has pqiv, X, flag f = pqiv -i -- "$@"
+mime ^image, has feh, X, flag f = feh -- "$@"
+mime ^image, has gimp, X, flag f = gimp -- "$@"
+#mime ^image, has mirage, X, flag f = mirage -- "$@"
+#mime ^image, has ristretto, X, flag f = ristretto "$@"
+#mime ^image, has eog, X, flag f = eog -- "$@"
+#mime ^image, has eom, X, flag f = eom -- "$@"
+#ext xcf, X, flag f = gimp -- "$@"
+
+#-------------------------------------------
+# Archives
+#-------------------------------------------
+
+# avoid password prompt by providing empty password
+ext 7z, has 7z = 7z -p l "$@" | "$PAGER"
+# This requires atool
+ext ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz, has als = als -- "$@" | "$PAGER"
+ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has als = als -- "$@" | "$PAGER"
+ext 7z|ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz, has aunpack = aunpack -- "$@"
+ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has aunpack = aunpack -- "$@"
+
+# Fallback:
+ext tar|gz, has tar = tar vvtf "$@" | "$PAGER"
+ext tar|gz, has tar = tar vvxf "$@"
+
+#-------------------------------------------
+# Misc
+#-------------------------------------------
+label wallpaper, number 11, mime ^image, has feh, X = feh --bg-scale "$1"
+label wallpaper, number 12, mime ^image, has feh, X = feh --bg-tile "$1"
+label wallpaper, number 13, mime ^image, has feh, X = feh --bg-center "$1"
+label wallpaper, number 14, mime ^image, has feh, X = feh --bg-fill "$1"
+
+# Custom
+ext torrent, has transmission-gtk = transmission-gtk "$@"
+
+# Define the editor for non-text files + pager as last action
+ !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = ask
+label editor, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = $EDITOR -- "$@"
+label pager, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = "$PAGER" -- "$@"
+
+
+# The very last action, so that it's never triggered accidentally, is to execute a program:
+mime application/x-executable = "$1"
diff --git a/stow/ranger/dot-config/ranger/scope.sh b/stow/ranger/dot-config/ranger/scope.sh
new file mode 100755
index 0000000..f403ed8
--- /dev/null
+++ b/stow/ranger/dot-config/ranger/scope.sh
@@ -0,0 +1,350 @@
+#!/usr/bin/env bash
+
+set -o noclobber -o noglob -o nounset -o pipefail
+IFS=$'\n'
+
+## If the option `use_preview_script` is set to `true`,
+## then this script will be called and its output will be displayed in ranger.
+## ANSI color codes are supported.
+## STDIN is disabled, so interactive scripts won't work properly
+
+## This script is considered a configuration file and must be updated manually.
+## It will be left untouched if you upgrade ranger.
+
+## Because of some automated testing we do on the script #'s for comments need
+## to be doubled up. Code that is commented out, because it's an alternative for
+## example, gets only one #.
+
+## Meanings of exit codes:
+## code | meaning | action of ranger
+## -----+------------+-------------------------------------------
+## 0 | success | Display stdout as preview
+## 1 | no preview | Display no preview at all
+## 2 | plain text | Display the plain content of the file
+## 3 | fix width | Don't reload when width changes
+## 4 | fix height | Don't reload when height changes
+## 5 | fix both | Don't ever reload
+## 6 | image | Display the image `$IMAGE_CACHE_PATH` points to as an image preview
+## 7 | image | Display the file directly as an image
+
+## Script arguments
+FILE_PATH="${1}" # Full path of the highlighted file
+PV_WIDTH="${2}" # Width of the preview pane (number of fitting characters)
+## shellcheck disable=SC2034 # PV_HEIGHT is provided for convenience and unused
+PV_HEIGHT="${3}" # Height of the preview pane (number of fitting characters)
+IMAGE_CACHE_PATH="${4}" # Full path that should be used to cache image preview
+PV_IMAGE_ENABLED="${5}" # 'True' if image previews are enabled, 'False' otherwise.
+
+FILE_EXTENSION="${FILE_PATH##*.}"
+FILE_EXTENSION_LOWER="$(printf "%s" "${FILE_EXTENSION}" | tr '[:upper:]' '[:lower:]')"
+
+## Settings
+HIGHLIGHT_SIZE_MAX=262143 # 256KiB
+HIGHLIGHT_TABWIDTH=${HIGHLIGHT_TABWIDTH:-8}
+HIGHLIGHT_STYLE=${HIGHLIGHT_STYLE:-pablo}
+HIGHLIGHT_OPTIONS="--replace-tabs=${HIGHLIGHT_TABWIDTH} --style=${HIGHLIGHT_STYLE} ${HIGHLIGHT_OPTIONS:-}"
+PYGMENTIZE_STYLE=${PYGMENTIZE_STYLE:-autumn}
+OPENSCAD_IMGSIZE=${RNGR_OPENSCAD_IMGSIZE:-1000,1000}
+OPENSCAD_COLORSCHEME=${RNGR_OPENSCAD_COLORSCHEME:-Tomorrow Night}
+
+handle_extension() {
+ case "${FILE_EXTENSION_LOWER}" in
+ ## Archive
+ a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
+ rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)
+ atool --list -- "${FILE_PATH}" && exit 5
+ bsdtar --list --file "${FILE_PATH}" && exit 5
+ exit 1;;
+ rar)
+ ## Avoid password prompt by providing empty password
+ unrar lt -p- -- "${FILE_PATH}" && exit 5
+ exit 1;;
+ 7z)
+ ## Avoid password prompt by providing empty password
+ 7z l -p -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## PDF
+ pdf)
+ ## Preview as text conversion
+ pdftotext -l 10 -nopgbrk -q -- "${FILE_PATH}" - | \
+ fmt -w "${PV_WIDTH}" && exit 5
+ mutool draw -F txt -i -- "${FILE_PATH}" 1-10 | \
+ fmt -w "${PV_WIDTH}" && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## BitTorrent
+ torrent)
+ transmission-show -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## OpenDocument
+ odt|ods|odp|sxw)
+ ## Preview as text conversion
+ odt2txt "${FILE_PATH}" && exit 5
+ ## Preview as markdown conversion
+ pandoc -s -t markdown -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## XLSX
+ xlsx)
+ ## Preview as csv conversion
+ ## Uses: https://github.com/dilshod/xlsx2csv
+ xlsx2csv -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## HTML
+ htm|html|xhtml)
+ ## Preview as text conversion
+ w3m -dump "${FILE_PATH}" && exit 5
+ lynx -dump -- "${FILE_PATH}" && exit 5
+ elinks -dump "${FILE_PATH}" && exit 5
+ pandoc -s -t markdown -- "${FILE_PATH}" && exit 5
+ ;;
+
+ ## JSON
+ json)
+ jq --color-output . "${FILE_PATH}" && exit 5
+ python -m json.tool -- "${FILE_PATH}" && exit 5
+ ;;
+
+ ## Direct Stream Digital/Transfer (DSDIFF) and wavpack aren't detected
+ ## by file(1).
+ dff|dsf|wv|wvc)
+ mediainfo "${FILE_PATH}" && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ ;; # Continue with next handler on failure
+ esac
+}
+
+handle_image() {
+ ## Size of the preview if there are multiple options or it has to be
+ ## rendered from vector graphics. If the conversion program allows
+ ## specifying only one dimension while keeping the aspect ratio, the width
+ ## will be used.
+ local DEFAULT_SIZE="1920x1080"
+
+ local mimetype="${1}"
+ case "${mimetype}" in
+ ## SVG
+ # image/svg+xml|image/svg)
+ # convert -- "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
+ # exit 1;;
+
+ ## DjVu
+ # image/vnd.djvu)
+ # ddjvu -format=tiff -quality=90 -page=1 -size="${DEFAULT_SIZE}" \
+ # - "${IMAGE_CACHE_PATH}" < "${FILE_PATH}" \
+ # && exit 6 || exit 1;;
+
+ ## Image
+ image/*)
+ local orientation
+ orientation="$( identify -format '%[EXIF:Orientation]\n' -- "${FILE_PATH}" )"
+ ## If orientation data is present and the image actually
+ ## needs rotating ("1" means no rotation)...
+ if [[ -n "$orientation" && "$orientation" != 1 ]]; then
+ ## ...auto-rotate the image according to the EXIF data.
+ convert -- "${FILE_PATH}" -auto-orient "${IMAGE_CACHE_PATH}" && exit 6
+ fi
+
+ ## `w3mimgdisplay` will be called for all images (unless overriden
+ ## as above), but might fail for unsupported types.
+ exit 7;;
+
+ ## Video
+ # video/*)
+ # # Thumbnail
+ # ffmpegthumbnailer -i "${FILE_PATH}" -o "${IMAGE_CACHE_PATH}" -s 0 && exit 6
+ # exit 1;;
+
+ ## PDF
+ # application/pdf)
+ # pdftoppm -f 1 -l 1 \
+ # -scale-to-x "${DEFAULT_SIZE%x*}" \
+ # -scale-to-y -1 \
+ # -singlefile \
+ # -jpeg -tiffcompression jpeg \
+ # -- "${FILE_PATH}" "${IMAGE_CACHE_PATH%.*}" \
+ # && exit 6 || exit 1;;
+
+
+ ## ePub, MOBI, FB2 (using Calibre)
+ # application/epub+zip|application/x-mobipocket-ebook|\
+ # application/x-fictionbook+xml)
+ # # ePub (using https://github.com/marianosimone/epub-thumbnailer)
+ # epub-thumbnailer "${FILE_PATH}" "${IMAGE_CACHE_PATH}" \
+ # "${DEFAULT_SIZE%x*}" && exit 6
+ # ebook-meta --get-cover="${IMAGE_CACHE_PATH}" -- "${FILE_PATH}" \
+ # >/dev/null && exit 6
+ # exit 1;;
+
+ ## Font
+ application/font*|application/*opentype)
+ preview_png="/tmp/$(basename "${IMAGE_CACHE_PATH%.*}").png"
+ if fontimage -o "${preview_png}" \
+ --pixelsize "120" \
+ --fontname \
+ --pixelsize "80" \
+ --text " ABCDEFGHIJKLMNOPQRSTUVWXYZ " \
+ --text " abcdefghijklmnopqrstuvwxyz " \
+ --text " 0123456789.:,;(*!?') ff fl fi ffi ffl " \
+ --text " The quick brown fox jumps over the lazy dog. " \
+ "${FILE_PATH}";
+ then
+ convert -- "${preview_png}" "${IMAGE_CACHE_PATH}" \
+ && rm "${preview_png}" \
+ && exit 6
+ else
+ exit 1
+ fi
+ ;;
+
+ ## Preview archives using the first image inside.
+ ## (Very useful for comic book collections for example.)
+ # application/zip|application/x-rar|application/x-7z-compressed|\
+ # application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar)
+ # local fn=""; local fe=""
+ # local zip=""; local rar=""; local tar=""; local bsd=""
+ # case "${mimetype}" in
+ # application/zip) zip=1 ;;
+ # application/x-rar) rar=1 ;;
+ # application/x-7z-compressed) ;;
+ # *) tar=1 ;;
+ # esac
+ # { [ "$tar" ] && fn=$(tar --list --file "${FILE_PATH}"); } || \
+ # { fn=$(bsdtar --list --file "${FILE_PATH}") && bsd=1 && tar=""; } || \
+ # { [ "$rar" ] && fn=$(unrar lb -p- -- "${FILE_PATH}"); } || \
+ # { [ "$zip" ] && fn=$(zipinfo -1 -- "${FILE_PATH}"); } || return
+ #
+ # fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \
+ # [ print(l, end='') for l in sys.stdin if \
+ # (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\
+ # sort -V | head -n 1)
+ # [ "$fn" = "" ] && return
+ # [ "$bsd" ] && fn=$(printf '%b' "$fn")
+ #
+ # [ "$tar" ] && tar --extract --to-stdout \
+ # --file "${FILE_PATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6
+ # fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g')
+ # [ "$bsd" ] && bsdtar --extract --to-stdout \
+ # --file "${FILE_PATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}"
+ # [ "$rar" ] && unrar p -p- -inul -- "${FILE_PATH}" "$fn" > \
+ # "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$zip" ] && unzip -pP "" -- "${FILE_PATH}" "$fe" > \
+ # "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}"
+ # ;;
+ esac
+
+ # openscad_image() {
+ # TMPPNG="$(mktemp -t XXXXXX.png)"
+ # openscad --colorscheme="${OPENSCAD_COLORSCHEME}" \
+ # --imgsize="${OPENSCAD_IMGSIZE/x/,}" \
+ # -o "${TMPPNG}" "${1}"
+ # mv "${TMPPNG}" "${IMAGE_CACHE_PATH}"
+ # }
+
+ # case "${FILE_EXTENSION_LOWER}" in
+ # ## 3D models
+ # ## OpenSCAD only supports png image output, and ${IMAGE_CACHE_PATH}
+ # ## is hardcoded as jpeg. So we make a tempfile.png and just
+ # ## move/rename it to jpg. This works because image libraries are
+ # ## smart enough to handle it.
+ # csg|scad)
+ # openscad_image "${FILE_PATH}" && exit 6
+ # ;;
+ # 3mf|amf|dxf|off|stl)
+ # openscad_image <(echo "import(\"${FILE_PATH}\");") && exit 6
+ # ;;
+ # esac
+}
+
+handle_mime() {
+ local mimetype="${1}"
+ case "${mimetype}" in
+ ## RTF and DOC
+ text/rtf|*msword)
+ ## Preview as text conversion
+ ## note: catdoc does not always work for .doc files
+ ## catdoc: http://www.wagner.pp.ru/~vitus/software/catdoc/
+ catdoc -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## DOCX, ePub, FB2 (using markdown)
+ ## You might want to remove "|epub" and/or "|fb2" below if you have
+ ## uncommented other methods to preview those formats
+ *wordprocessingml.document|*/epub+zip|*/x-fictionbook+xml)
+ ## Preview as markdown conversion
+ pandoc -s -t markdown -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## XLS
+ *ms-excel)
+ ## Preview as csv conversion
+ ## xls2csv comes with catdoc:
+ ## http://www.wagner.pp.ru/~vitus/software/catdoc/
+ xls2csv -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## Text
+ text/* | */xml)
+ ## Syntax highlight
+ if [[ "$( stat --printf='%s' -- "${FILE_PATH}" )" -gt "${HIGHLIGHT_SIZE_MAX}" ]]; then
+ exit 2
+ fi
+ if [[ "$( tput colors )" -ge 256 ]]; then
+ local pygmentize_format='terminal256'
+ local highlight_format='xterm256'
+ else
+ local pygmentize_format='terminal'
+ local highlight_format='ansi'
+ fi
+ env HIGHLIGHT_OPTIONS="${HIGHLIGHT_OPTIONS}" highlight \
+ --out-format="${highlight_format}" \
+ --force -- "${FILE_PATH}" && exit 5
+ env COLORTERM=8bit bat --color=always --style="plain" \
+ -- "${FILE_PATH}" && exit 5
+ pygmentize -f "${pygmentize_format}" -O "style=${PYGMENTIZE_STYLE}"\
+ -- "${FILE_PATH}" && exit 5
+ exit 2;;
+
+ ## DjVu
+ image/vnd.djvu)
+ ## Preview as text conversion (requires djvulibre)
+ djvutxt "${FILE_PATH}" | fmt -w "${PV_WIDTH}" && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## Image
+ image/*)
+ ## Preview as text conversion
+ # img2txt --gamma=0.6 --width="${PV_WIDTH}" -- "${FILE_PATH}" && exit 4
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ ## Video and audio
+ video/* | audio/*)
+ mediainfo "${FILE_PATH}" && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+ esac
+}
+
+handle_fallback() {
+ echo '----- File Type Classification -----' && file --dereference --brief -- "${FILE_PATH}" && exit 5
+ exit 1
+}
+
+
+MIMETYPE="$( file --dereference --brief --mime-type -- "${FILE_PATH}" )"
+if [[ "${PV_IMAGE_ENABLED}" == 'True' ]]; then
+ handle_image "${MIMETYPE}"
+fi
+handle_extension
+handle_mime "${MIMETYPE}"
+handle_fallback
+
+exit 1
diff --git a/stow/rcs/dot-Xresources b/stow/rcs/dot-Xresources
new file mode 100644
index 0000000..4157806
--- /dev/null
+++ b/stow/rcs/dot-Xresources
@@ -0,0 +1,40 @@
+! To load into RESOURCE_MANAGER, run xrdb ~/.Xresources
+
+! *background and foreground are read by sxiv
+
+!#include "/home/taamas/.config/colorschemes/customBlue"
+#include "/home/taamas/.config/colorschemes/nord"
+
+! Man pages
+URxvt.colorIT: #ff0000
+
+URxvt.scrollBar: false
+
+! These options are not used when configuration is through picom
+!URxvt.fading: 18
+!*.alpha: 120
+
+! Fake transparency (copies root window, aka wallpaper)
+! Overrides real transparency if not commented
+!URxvt.transparent: true
+!URxvt.shading: 12
+
+! Real transparency (needs a running composite manager such as picom)
+!URxvt.depth: 32
+!URxvt.background: [90]#050505
+
+!*.font: inconsolata:size=12
+*.font: mononoki:size=12
+! URxvt.font: xft:inconsolata:size=10,xft:symbola
+URxvt.font: xft:mononoki:size=10,xft:symbola
+! URxvt.font: xft:envy code r:size=9,xft:symbola
+
+! Leaving this blank stops perl from being initialized
+!URxvt.perl-ext:
+URxvt.perl-ext-common: default,font-size
+URxvt.keysym.C-equal: font-size:reset
+
+!Xcursor.size: 32
+Xcursor.theme: pTux-32
+!Xcursor.theme: Bibata_Dodger_Blue
+!Xcursor.theme: Bibata_Turquoise
diff --git a/stow/rcs/dot-bash_aliases b/stow/rcs/dot-bash_aliases
new file mode 100644
index 0000000..d229fc4
--- /dev/null
+++ b/stow/rcs/dot-bash_aliases
@@ -0,0 +1,44 @@
+# General abreviations
+alias r='tmux rename-window ranger; ranger; tmux rename-window bash'
+alias v='nvim'
+alias vim='nvim'
+alias cdf='cd "$(fzfPath.sh)"'
+alias cdr='route="$(fzfPath.sh)" && [ -n "$route" ] && ranger "$route"'
+alias mail='mail.sh'
+alias ls='ls -hN --color=auto --group-directories-first'
+alias cp='cp -iv'
+alias grep='grep --color=auto'
+alias fgrep='fgrep --color=auto'
+alias egrep='egrep --color=auto'
+alias nb='newsboat'
+alias z='cd ~/docs/zettelkasten && vim "$(./porTema.sh)"'
+alias zn='cd ~/docs/zettelkasten && vim "$(./nuevo.sh)"'
+alias o='rifle'
+
+# config files
+alias cb="vim ~/.bashrc"
+alias cba="vim ~/.bash_aliases"
+alias cx="vim ~/.Xresources"
+alias ci3="vim ~/.config/i3/config"
+alias ci3b="vim ~/.config/i3blocks/config"
+alias csx="vim ~/.config/sxhkd/sxhkdrc"
+alias cbs="vim ~/.config/bspwm/bspwmrc"
+alias csxbs="vim ~/.config/sxhkd/sxhkdrcbspc"
+alias cdunst="vim ~/.config/dunst/dunstrc"
+alias cv="vim ~/.vimrc"
+alias cqb="vim ~/.config/qutebrowser/config.py"
+alias cr="vim ~/.config/ranger/rc.conf"
+alias crifle="vim ~/.config/ranger/rifle.conf"
+alias ccmus="vim ~/.config/cmus/autosave"
+alias cg="vim ~/.gitconfig"
+alias cnb="vim ~/.config/newsboat/config"
+alias cnbu="vim ~/.config/newsboat/urls"
+alias ctmux="vim ~/.tmux.conf"
+
+alias upgrade="sudo pacman -Syu"
+
+alias starwars="telnet towel.blinkenlights.nl"
+alias adormir="mpv -vo caca ~/videos/Adormir.mp4 && clear 2>/dev/null"
+alias adormirloop="mpv --loop-file -vo caca ~/videos/Adormir.mp4 2>/dev/null && clear"
+alias nintendos="mpv -vo caca https://www.youtube.com/watch?v=hYNcJj2QDhk 2>/dev/null"
+alias netris="ssh netris.rocketnine.space"
diff --git a/stow/rcs/dot-bash_vars b/stow/rcs/dot-bash_vars
new file mode 100644
index 0000000..7dc9b53
--- /dev/null
+++ b/stow/rcs/dot-bash_vars
@@ -0,0 +1,87 @@
+# $HOME/.bash_vars: Non-standard file for environment variables agruppation.
+
+###########
+# General #
+###########
+
+export XDG_CONFIG_HOME="$HOME/.config"
+
+export EDITOR="nvim"
+export TERM="rxvt-256color"
+export TERMINAL="urxvt"
+export BROWSER="qutebrowser"
+export READER="zathura"
+export VIEWER="sxiv"
+
+export PLANTUML="$HOME/programs/plantUML/plantuml.jar"
+
+export LEDGER_FILE="$HOME/docs/ledger.dat"
+
+export CONFIG_VOL_CHANGE="2"
+export CONFIG_BIG_VOL_CHANGE="10"
+
+export CONFIG_BRIGHT_CHANGE="4"
+export CONFIG_BIG_BRIGHT_CHANGE="60"
+
+export CONFIG_DIC_ES="/usr/share/dict/spanish"
+export CONFIG_DIC_EN="/usr/share/dict/british"
+export CONFIG_DIC_PL="/usr/share/dict/słowa.txt"
+
+export CONFIG_FOLDER_RANDOMBG="$HOME/images/wallpapers/shufs/current"
+export CONFIG_FOLDER_LOGS="$HOME/logs"
+
+export CONFIG_WIFI_DEV="wlp2s0"
+export CONFIG_ETH_DEV=""
+
+export CONFIG_BATTERY="BAT1"
+export CONFIG_SENSORS_TEMP_REGEX="^Package"
+export CONFIG_SENSORS_TEMP_COL="4"
+
+export CONFIG_FOLDER_DISROOT_INBOX="$HOME/.mail/disroot/INBOX/new"
+export CONFIG_FOLDER_GMAIL_INBOX="$HOME/.mail/gmail/[Gmail]/INBOX/new"
+export CONFIG_FOLDER_UNIOVI_INBOX="$HOME/.mail/uniovi/INBOX/new"
+
+# export ARDUINO_DIR="/usr/share/arduino"
+# export ARDMK_DIR="/usr/share/arduino"
+# export AVR_TOOLS_DIR="/usr"
+
+# Colors for grep
+export GREP_COLORS='ms=04;32;49:mc=04;32;49:sl=:cx=:fn=01;34;49:ln=34:bn=34:se=36'
+
+# Colors for gcc
+export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
+
+# Colors for less
+export LESS_TERMCAP_mb=$'\E[01;31m'
+export LESS_TERMCAP_md=$(tput bold; tput setaf 14)
+export LESS_TERMCAP_me=$'\E[0m'
+export LESS_TERMCAP_so=$(tput setab 2; tput setaf 0)
+export LESS_TERMCAP_se=$'\E[0m'
+export LESS_TERMCAP_us=$(tput smul; tput setaf 3)
+export LESS_TERMCAP_ue=$'\E[0m'
+
+#########
+# $PATH #
+#########
+
+# java
+export JAVA_HOME="/usr/lib/jvm/default/"
+PATH="${PATH}:${JAVA_HOME}/bin"
+
+# add scripts folder to path plus its direct subfolders
+for dir in $(find "$HOME/scripts" -type d | grep -v '/\.'); do
+ PATH="${PATH}:${dir}"
+done
+
+# add ruby bins (gems) folder to path
+PATH="${PATH}:$(ruby -e 'puts Gem.user_dir')/bin"
+
+# add cargo bins folder to path
+PATH="${PATH}:${HOME}/.cargo/bin"
+
+# add pip local bins to path
+PATH="${PATH}:${HOME}/.local/bin"
+
+# export final PATH
+export PATH
+
diff --git a/stow/rcs/dot-bashrc b/stow/rcs/dot-bashrc
new file mode 100644
index 0000000..5c0c5bb
--- /dev/null
+++ b/stow/rcs/dot-bashrc
@@ -0,0 +1,66 @@
+# $HOME/.bashrc: executed by bash(1) for non-login shells.
+
+# If not running interactively, don't do anything
+[[ $- != *i* ]] && return
+
+# [[ -z "$TMUX" ]] && exec tmux # Doesn't allow to automatically start X.
+
+# Activate vi mode with <Escape>.
+set -o vi
+
+# Don't put duplicate lines or lines starting with space in the history.
+# See bash(1) for more options.
+HISTCONTROL=ignoreboth
+
+# Append to the history file, don't overwrite it.
+shopt -s histappend
+
+# For setting history length see HISTSIZE and HISTFILESIZE in bash(1).
+HISTSIZE=-1
+HISTFILESIZE=-1
+
+# Check the window size after each command and, if necessary,
+# update the values of LINES and COLUMNS.
+[[ $DISPLAY ]] && shopt -s checkwinsize
+
+# Disable ctrl-s and ctrl-q (Enables XON/XOFF flow control).
+stty -ixon
+
+# If set, the pattern "**" used in a pathname expansion context will
+# match all files and zero or more directories and subdirectories.
+#shopt -s globstar
+
+# make less more friendly for non-text input files, see lesspipe(1)
+[ -x "/usr/bin/lesspipe" ] && eval "$(SHELL=/bin/sh lesspipe)"
+
+# enable color support of ls
+if [ -x /usr/bin/dircolors ]; then
+ test -r "$HOME/.dircolors" && eval "$(dircolors -b $HOME/.dircolors)" || eval "$(dircolors -b)"
+ #alias dir='dir --color=auto'
+ #alias vdir='vdir --color=auto'
+fi
+
+if [ -r "/usr/share/bash-completion/bash_completion" ]; then
+ . "/usr/share/bash-completion/bash_completion"
+fi
+
+# Set custom PS1 (prompt text variable)
+def="\[\e[0;00m\]"
+#red="\[\e[1;31m\]"
+#green="\[\e[1;32m\]"
+#yel="\[\e[1;33m\]"
+blue="\[\e[1;34m\]"
+pur="\[\e[1;35m\]"
+#cyan="\[\e[1;36m\]"
+#white="\[\e[1;37m\]"
+keys="$blue"
+wd="$pur"
+umode="$blue"
+PS1="${keys}[${wd}\u${keys}@${wd}\h${keys}]${wd}\w${umode}\$${def} "
+
+# Aliases are defined in $HOME/.bash_aliases for convenience.
+[ -f "$HOME/.bash_aliases" ] && . "$HOME/.bash_aliases"
+
+# fzf goodies
+. /usr/share/fzf/key-bindings.bash
+. /usr/share/fzf/completion.bash
diff --git a/stow/rcs/dot-config/ttymaps.kmap b/stow/rcs/dot-config/ttymaps.kmap
new file mode 100644
index 0000000..51a7042
--- /dev/null
+++ b/stow/rcs/dot-config/ttymaps.kmap
@@ -0,0 +1,2 @@
+keycode 1 = Caps_Lock
+keycode 58 = Escape
diff --git a/stow/rcs/dot-inputrc b/stow/rcs/dot-inputrc
new file mode 100644
index 0000000..202ca16
--- /dev/null
+++ b/stow/rcs/dot-inputrc
@@ -0,0 +1,16 @@
+# Read by readline (the program bash and probably other shells use for reading input).
+
+# Disable bell
+set bell-style none
+
+# Set vi editing mode
+set editing-mode vi
+set show-mode-in-prompt on
+set vi-ins-mode-string \1\e[6 q\2
+set vi-cmd-mode-string \1\e[2 q\2
+
+# Shows all possible matches the first time TAB is pressed instead of beeping.
+set show-all-if-ambiguous on
+
+# Shows file type (directory or executable) as a character when showing autocomplete targets.
+set visible-stats on
diff --git a/stow/rcs/dot-profile b/stow/rcs/dot-profile
new file mode 100644
index 0000000..d461cce
--- /dev/null
+++ b/stow/rcs/dot-profile
@@ -0,0 +1,46 @@
+# $HOME/.profile: executed by the command interpreter for login shells.
+# This file is not read by bash(1) if ~/.bash_profile or ~/.bash_login
+# exists.
+
+# the default umask is set in /etc/profile; for setting the umask
+# for ssh logins, install and configure the libpam-umask package.
+#umask 022
+
+# if running bash
+if [ -n "$BASH_VERSION" ]; then
+ # include .bashrc if it exists
+ [ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"
+fi
+
+# Source environment variables
+[ -f "$HOME/.bash_vars" ] && . "$HOME/.bash_vars"
+
+# Login commands
+sudo loadkeys es
+sudo -n loadkeys "$XDG_CONFIG_HOME/ttymaps.kmap"
+[[ $(tty) = "/dev/tty6" ]] && setfont alt-8x8
+
+xinitrc=""
+
+if [ "$(tty)" = "/dev/tty1" ] && ! pgrep -x i3; then
+ export STATUS_BAR="polybar"
+ xinitrc="$HOME/.xinitrcbspwm"
+elif [ "$(tty)" = "/dev/tty2" ] && ! pgrep -x dwm; then
+ export STATUS_BAR="dwmblocks"
+ xinitrc="$HOME/.xinitrcdwm"
+elif [ "$(tty)" = "/dev/tty3" ] && ! pgrep -x bspwm; then
+ export STATUS_BAR="i3blocks"
+ xinitrc="$HOME/.xinitrci3"
+elif [ "$(tty)" = "/dev/tty4" ] && ! pgrep -x gnome-session; then
+ xinitrc="$HOME/.xinitrcgnome"
+elif tty | grep -q '/dev/tty.*' ; then
+ tmux new -s "$(tty | cut -d'/' -f3)"
+fi
+
+if [ -n "$xinitrc" ]; then
+ echo ""
+ cat "$HOME/.xinitrcprev" "$xinitrc" "$HOME/.xinitrcpost" > "$HOME/.xinitrc"
+ neofetch --config "$XDG_CONFIG_HOME/neofetch/neofetchLogin.conf"
+ #figlet "Starting i3-gaps..." | lolcat -a -d 1 -s 10 -p 1 -F 0.05
+ exec startx >"$CONFIG_FOLDER_LOGS/startx.log" 2>"$CONFIG_FOLDER_LOGS/startx.err"
+fi
diff --git a/stow/sxhkd/dot-config/sxhkd/sxhkdrc b/stow/sxhkd/dot-config/sxhkd/sxhkdrc
new file mode 100644
index 0000000..f726802
--- /dev/null
+++ b/stow/sxhkd/dot-config/sxhkd/sxhkdrc
@@ -0,0 +1,129 @@
+# _ _ _
+# _____ _| |__ | | ____| |_ __ ___
+# / __\ \/ / '_ \| |/ / _` | '__/ __|
+# \__ \> <| | | | < (_| | | | (__
+# |___/_/\_\_| |_|_|\_\__,_|_| \___|
+#
+# Config file for the Simple X HotKey Daemon
+
+#super + Return
+# urxvt -title "floating" -e tmux
+super + control + Return
+ urxvt -title "floating" -e tmux
+
+super + shift + Return
+ urxvt -e tmux
+
+super + x
+ dmenu_run -c -bw 3 -l 8 -p "~$"
+
+# Jump to window
+super + ctrl + j
+ rofi -show window
+
+super + ctrl + x
+ sxivslideshot
+
+# system control
+super + ctrl + s
+ ~/scripts/systemControl.sh
+
+# lock
+super + ctrl + shift + l
+ ~/scripts/i3lock.sh
+
+# touchpad
+# super + t
+# touchpad.sh
+
+# From https://faq.i3wm.org/question/3747/enabling-multimedia-keys/?answer=3759#post-id-3759
+# Pulse Audio controls
+#increase sound volume
+XF86Audio{Lower,Raise}Volume
+ ~/scripts/vol.sh "{-,+}$CONFIG_VOL_CHANGE"
+super + XF86Audio{Lower,Raise}Volume
+ ~/scripts/vol.sh "{-,+}$CONFIG_BIG_VOL_CHANGE"
+#mute sound
+XF86AudioMute
+ ~/scripts/vol.sh "mute"
+
+# Sreen brightness controls
+# increase screen brightness
+XF86MonBrightness{Up,Down}
+ ~/scripts/brightness.sh "{_,-}$CONFIG_BRIGHT_CHANGE"
+super + XF86MonBrightness{Up,Down}
+ ~/scripts/brightness.sh "{_,-}$CONFIG_BIG_BRIGHT_CHANGE"
+
+# Media player controls
+XF86AudioPlay
+ ~/scripts/cmusPlay.sh
+XF86Audio{Prev,Next}
+ cmus-remote -{r,n} && pkill -SIGRTMIN+12 i3blocks
+
+## Programs
+
+super + q
+ qutebrowser
+
+super + shift + f
+ firefox
+
+# super + shift + w
+# prevBG
+
+super + w
+ setRandomBG.sh
+
+Print
+ screenshot.sh
+
+super + s
+ screenshot.sh
+
+super + a
+ screenshot.sh -a
+
+super + shift + a
+ screenshot.sh -c
+
+super + r
+ urxvt -title "floating" -e ranger
+
+super + shift + c
+ ~/scripts/dmenuTerm.sh
+
+super + d
+ ~/scripts/dmenuDic.sh
+
+super + shift + t
+ dmenutranslate
+
+super + e
+ ~/scripts/emoji.sh
+
+super + shift + m
+ ~/scripts/dmenuMount.sh
+
+super + control + m
+ ~/scripts/dmenuUmount.sh
+
+super + control + shift + c
+ notify-send "$(fortune)"
+
+super + y
+ surfraw.sh
+
+super + ctrl + f
+ screenRecord.sh
+
+super + ctrl + n
+ ~/scripts/quicktext.sh
+
+super + ctrl + p
+ passmenu
+
+super + z
+ docs.sh
+
+super + ctrl + space
+ toggleKBLayouts.sh
diff --git a/stow/sxhkd/dot-config/sxhkd/sxhkdrcbspc b/stow/sxhkd/dot-config/sxhkd/sxhkdrcbspc
new file mode 100644
index 0000000..192698d
--- /dev/null
+++ b/stow/sxhkd/dot-config/sxhkd/sxhkdrcbspc
@@ -0,0 +1,92 @@
+# _ _ _ _
+# _____ _| |__ | | ____| |_ __ ___| |__ ___ _ ____ ___ __ ___
+# / __\ \/ / '_ \| |/ / _` | '__/ __| '_ \/ __| '_ \ \ /\ / / '_ ` _ \
+# \__ \> <| | | | < (_| | | | (__| |_) \__ \ |_) \ V V /| | | | | |
+# |___/_/\_\_| |_|_|\_\__,_|_| \___|_.__/|___/ .__/ \_/\_/ |_| |_| |_|
+# |_|
+# Config file for the Simple X HotKey Daemon with bspwm
+
+# Select next or previous monitor
+super + {period,comma}
+ bspc monitor {next,prev} -f
+
+# Move node to next or previous monitor and keep it focused
+super + shift + {period,comma}
+ bspc node -m {next,prev} --follow
+
+# Select next or previous desktop on focused monitor
+super + {n,p}
+ bspc desktop {next,prev}.local -f;\
+ dunstify -r "$(dunstifyIDs.sh bspwmDesktop)" -t 500 \
+ "$(bspc query -D -d focused.local --names)"
+
+# Send node to next or previous desktop on focused monitor
+super + shift + {n,p}
+ bspc node -d {next,prev}.local
+
+# Select a specific desktop on focused monitor
+super + {0-9}
+ bspc desktop 'focused:^{0-9}' -f
+
+# Send node to specific desktop on focused monitor
+super + shift + {0-9}
+ bspc node -d 'focused:^{0-9}'
+
+# Directionally select node with same floating state
+super + {h,j,k,l}
+ direction={west,south,north,east}; \
+ if bspc wm -g | grep -q ':TF:'; then \
+ bspc node -f "$direction".floating; \
+ elif bspc wm -g | grep -q ':LM:'; then \
+ bspc node -f next.local.window.!floating; \
+ else \
+ bspc node -f "$direction".!floating; \
+ fi
+
+# Swap node with adjacent node
+super + shift + {h,j,k,l}
+ bspc node -s {west,south,north,east}
+
+# Resize node
+#super + alt + {h,j,k,l}
+# bspc node --resize {right,bottom,bottom,right} {-5,0,0,5} {0,5,-5,0}
+super + alt + h
+ bspc node --resize right -5 0
+super + alt + j
+ bspc node --resize bottom 0 5
+super + alt + k
+ bspc node --resize bottom 0 -5
+super + alt + l
+ bspc node --resize right 5 0
+
+# Select split direction
+super + {v,b}
+ bspc node --presel-dir ~{south,east}
+
+# Swap focus between tiled and floating nodes
+super + space
+ if bspc wm -g | grep -q ':TF:'; then \
+ bspc node -f last.local.leaf.!floating; \
+ else \
+ bspc node -f last.local.leaf.floating; \
+ fi
+
+# Switch focused node floating state
+super + shift + space
+ bspc node -t ~floating
+
+# Jump to next or previous global window
+super + ctrl + {h,l}
+ bspc node -f {next,prev}.window
+
+# Change layout (m for monocle)
+super + m
+ bspc desktop -l next
+
+# Close window on focused node
+super + shift + q
+ bspc node -c
+
+# Notify number of windows
+super + c
+ dunstify -r "$(dunstifyIDs.sh "bspwmRunningStuff")" -t 1000 "Running stuff" "bspwm nodes:\n$(bspc query -N any.leaf.window | wc -l)\ntmux sessions:\n$(tmux list-sessions | cut -d':' -f1)"
diff --git a/stow/sxiv/dot-config/sxiv/exec/image-info b/stow/sxiv/dot-config/sxiv/exec/image-info
new file mode 100755
index 0000000..be503d8
--- /dev/null
+++ b/stow/sxiv/dot-config/sxiv/exec/image-info
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# The output is displayed in sxiv's status bar.
+# Arguments:
+# $1: path to image file
+# $2: image width
+# $3: image height
+
+s=" " # field separator
+
+exec 2>/dev/null
+
+filename=$(basename -- "$1")
+filesize=$(du -Hh -- "$1" | cut -f 1)
+geometry="${2}x${3}"
+
+echo "${filename}${s}${geometry}${s}${filesize}"
+
diff --git a/stow/sxiv/dot-config/sxiv/exec/key-handler b/stow/sxiv/dot-config/sxiv/exec/key-handler
new file mode 100755
index 0000000..ef81c79
--- /dev/null
+++ b/stow/sxiv/dot-config/sxiv/exec/key-handler
@@ -0,0 +1,128 @@
+#!/bin/bash
+
+case "$1" in
+ "s")
+ width=$(echo "" | dmenu -p "Width of new files?")
+ [ "$width" ] || exit 0
+ height=$(echo "" | dmenu -p "Height of new files?")
+ [ "$height" ] || exit 0
+ modifier=""
+ [ "$(printf "Yes\nNo" | dmenu -i -p "Maintain aspect ratio?")" = "No" ] && modifier="!"
+ while read -r file
+ do
+ convert "$file" -resize "$width"x"$height""$modifier" "$width"x"$height""$modifier"_"$file"
+ done ;;
+ "y")
+ text=""
+ while read -r file
+ do
+ text="$text""$file""\n"
+ done
+ echo -en "$text" | xsel -ib &&
+ dunstify "$(xsel -ob) copied to clipboard" ;;
+ "Y")
+ text=""
+ while read -r file
+ do
+ text="$text""$(readlink -f "$file")""\n"
+ done
+ echo -en "$text" | xsel -ib &&
+ dunstify "$(xsel -ob) copied to clipboard" ;;
+ "G")
+ images=""
+ tmpFolder="/tmp/sxiv/gifs/"
+ mkdir -p "$tmpFolder"
+ res=$(echo "" | dmenu -p "Resolution of GIF?")
+ [ -n "$res" ] && res="-resize $res"
+ while read -r file
+ do
+ if ! convert "$file" $res "$tmpFolder$file"; then
+ dunstify "Error converting files (Wrong resolution?)"
+ exit 1
+ fi
+ images="$images $tmpFolder$file"
+ done
+ delay=$(echo "" | dmenu -p "Delay of GIF?")
+ [ -n "$delay" ] || exit 0
+ name=$(echo "" | dmenu -p "Name of GIF?")
+ [ -n "$name" ] || exit 0
+ convert -delay "$delay" -loop 10 $images "$name.gif"
+ dunstify "Created $name.gif" ;;
+
+ "h")
+ dunstify "\
+w: Set as wallpaper. .
+r: Rotate clockwise. .
+R: Rotate counter-clockwise. .
+f: Flop (horizontally). .
+F: Flip (vertically). .
+y: Yank filename to clipboard. .
+Y: Yank file path to clipboard. .
+i: Yank image to clipboard. .
+d: Move file to litter. .
+n: Rename file. .
+N: Open in a new instance of sxiv. .
+g: Open in gimp. .
+G: Make a gif out of selected images. .
+s: Create new image with different size. .
+S: Search image online. .
+c: Copy images to a chosen folder. .
+m: Move images to a chosen folder. .
+h: Show help. ." ;;
+esac
+
+while read -r file
+do
+ case "$1" in
+ "w") setBG.sh "$file" ;;
+ "W") wal.sh "$file" ;;
+ "r")
+ convert -rotate 90 "$file" "$file" &&
+ dunstify "$file rotated clockwise" ;;
+ "R")
+ convert -rotate -90 "$file" "$file" &&
+ dunstify "$file rotated counter-clockwise" ;;
+ "f")
+ convert -flop "$file" "$file" &&
+ dunstify "$file flopped" ;;
+ "F")
+ convert -flip "$file" "$file" &&
+ dunstify "$file flipped" ;;
+ "d")
+ rm.sh "$file" && dunstify -i "$file" "$file moved to litter." ;;
+ "n")
+ newname="$(dmenu -p "New name of $file?")"
+ [ "$newname" ] && newname="$newname"'.'"${file##*.}" && [ "$(printf "No\nYes" | dmenu -i -p "Rename $file to $newname?")" = "Yes" ] && mv "$file" "$newname" && dunstify "$file renamed to $newname." ;;
+ "N")
+ sxiv -b "$file" & ;;
+ "g")
+ gimp "$file" & ;;
+ "S")
+ echo -n "$(readlink -f "$file")" | xsel -ib && dunstify "$(xsel -ob) copied to clipboard"
+ $BROWSER "https://tineye.com/" ;;
+ "i")
+ xclip -selection clipboard -t image/png "$(readlink -f "$file")" && dunstify "$file copied to clipboard as image."
+ ;;
+ "c")
+ [ -z "$destdir" ] && destdir="$(selectPath.sh "Copy image(s) to:")"
+ [ -z "$destdir" ] && exit
+ replace="Yes"
+ if [ -e "$destdir""$file" ]; then
+ dunstify -i "$destdir""$file" "Old:"
+ dunstify -i "$(readlink -f "$file")" "New:"
+ replace="$(echo -e "No\nYes" | dmenu -i -p "Replace $file in $destdir?")"
+ fi
+ [ "$replace" = "Yes" ] && cp "$file" "$destdir" && dunstify -t 2000 -i "$(readlink -f "$file")" "$file copied to $destdir" & ;;
+ "m")
+ [ -z "$destdir" ] && destdir="$(selectPath.sh "Move image(s) to:")"
+ [ -z "$destdir" ] && exit
+ replace="Yes"
+ if [ -e "$destdir""$file" ]; then
+ dunstify -i "$destdir""$file" "Old:"
+ dunstify -i "$(readlink -f "$file")" "New:"
+ replace="$(echo -e "No\nYes" | dmenu -i -p "Replace $file in $destdir?")"
+ fi
+ [ "$replace" = "Yes" ] && mv "$file" "$destdir" && dunstify -t 2000 -i "$destdir$file" "$file moved to $destdir" & ;;
+ esac
+done
+
diff --git a/stow/tmux/dot-tmux.conf b/stow/tmux/dot-tmux.conf
new file mode 100644
index 0000000..0f65fa6
--- /dev/null
+++ b/stow/tmux/dot-tmux.conf
@@ -0,0 +1,70 @@
+unbind C-b
+set -g prefix m-a
+bind m-a send-prefix
+set-option -g default-terminal "screen-256color"
+set -g xterm-keys on
+#setw -g monitor-activity on
+#set -g visual-activity on
+set -g mode-keys vi
+set -g status-keys vi
+
+# Theme
+#set-option -g status-style attr=default
+# status bar
+set-option -g status-style "fg=colour4 bg=default"
+# status bar windows
+set-window-option -g window-status-style "fg=colour4 bg=default"
+# status bar selected window
+set -g window-status-current-style "fg=colour13 bg=default"
+#set-window-option -g window-status-current-attr bold default
+# pane borders
+set-option -g pane-border-style "fg=colour0 bg=default"
+set-option -g pane-active-border-style "fg=colour4 bg=default"
+# pane number display
+set-option -g display-panes-colour colour4
+set-option -g display-panes-active-colour colour5
+# messages
+set-option -g message-style "fg=colour2 bg=default"
+# clock
+set-window-option -g clock-mode-colour colour4
+# sizes
+set -g status-interval 60
+set -g status-left-length 20
+#set -g status-left '#[fg=green](#S) #(whoami) '
+set -g status-left '(#S) '
+#zoom
+#{?window_zoomed_flag, ,ðŸ”}
+
+set -g base-index 0
+set -s escape-time 0
+bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
+bind-key C command-prompt -p "Name of new window:" "new-window -n '%%'"
+
+# moving between panes with vim movement keys
+bind h select-pane -L
+bind j select-pane -D
+bind k select-pane -U
+bind l select-pane -R
+
+# moving between windows with vim movement keys
+#bind -r H select-window -t :-
+#bind -r L select-window -t :+
+
+# resize panes with vim movement keys
+bind -r m-h resize-pane -L 1
+bind -r m-j resize-pane -D 1
+bind -r m-k resize-pane -U 1
+bind -r m-l resize-pane -R 1
+bind -r m-H resize-pane -L 10
+bind -r m-J resize-pane -D 10
+bind -r m-K resize-pane -U 10
+bind -r m-L resize-pane -R 10
+
+# cursor shape in nvim
+set -ga terminal-overrides '*:Ss=\E[%p1%d q:Se=\E[ q'
+
+# recommended by nvim :healthcheck
+set-option -sa terminal-overrides ',rxvt-256color:RGB'
+
+# tmux-url-select
+bind u run tmux-url-select
diff --git a/stow/vim/dot-vim/autoload/plug.vim b/stow/vim/dot-vim/autoload/plug.vim
new file mode 100644
index 0000000..9c296ac
--- /dev/null
+++ b/stow/vim/dot-vim/autoload/plug.vim
@@ -0,0 +1,2788 @@
+" vim-plug: Vim plugin manager
+" ============================
+"
+" Download plug.vim and put it in ~/.vim/autoload
+"
+" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
+" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
+"
+" Edit your .vimrc
+"
+" call plug#begin('~/.vim/plugged')
+"
+" " Make sure you use single quotes
+"
+" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
+" Plug 'junegunn/vim-easy-align'
+"
+" " Any valid git URL is allowed
+" Plug 'https://github.com/junegunn/vim-github-dashboard.git'
+"
+" " Multiple Plug commands can be written in a single line using | separators
+" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
+"
+" " On-demand loading
+" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
+" Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
+"
+" " Using a non-default branch
+" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
+"
+" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
+" Plug 'fatih/vim-go', { 'tag': '*' }
+"
+" " Plugin options
+" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
+"
+" " Plugin outside ~/.vim/plugged with post-update hook
+" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
+"
+" " Unmanaged plugin (manually installed and updated)
+" Plug '~/my-prototype-plugin'
+"
+" " Initialize plugin system
+" call plug#end()
+"
+" Then reload .vimrc and :PlugInstall to install plugins.
+"
+" Plug options:
+"
+"| Option | Description |
+"| ----------------------- | ------------------------------------------------ |
+"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use |
+"| `rtp` | Subdirectory that contains Vim plugin |
+"| `dir` | Custom directory for the plugin |
+"| `as` | Use different name for the plugin |
+"| `do` | Post-update hook (string or funcref) |
+"| `on` | On-demand loading: Commands or `<Plug>`-mappings |
+"| `for` | On-demand loading: File types |
+"| `frozen` | Do not update unless explicitly specified |
+"
+" More information: https://github.com/junegunn/vim-plug
+"
+"
+" Copyright (c) 2017 Junegunn Choi
+"
+" MIT License
+"
+" 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.
+
+if exists('g:loaded_plug')
+ finish
+endif
+let g:loaded_plug = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
+let s:plug_tab = get(s:, 'plug_tab', -1)
+let s:plug_buf = get(s:, 'plug_buf', -1)
+let s:mac_gui = has('gui_macvim') && has('gui_running')
+let s:is_win = has('win32')
+let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
+let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
+if s:is_win && &shellslash
+ set noshellslash
+ let s:me = resolve(expand('<sfile>:p'))
+ set shellslash
+else
+ let s:me = resolve(expand('<sfile>:p'))
+endif
+let s:base_spec = { 'branch': '', 'frozen': 0 }
+let s:TYPE = {
+\ 'string': type(''),
+\ 'list': type([]),
+\ 'dict': type({}),
+\ 'funcref': type(function('call'))
+\ }
+let s:loaded = get(s:, 'loaded', {})
+let s:triggers = get(s:, 'triggers', {})
+
+function! s:isabsolute(dir) abort
+ return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
+endfunction
+
+function! s:git_dir(dir) abort
+ let gitdir = s:trim(a:dir) . '/.git'
+ if isdirectory(gitdir)
+ return gitdir
+ endif
+ if !filereadable(gitdir)
+ return ''
+ endif
+ let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
+ if len(gitdir) && !s:isabsolute(gitdir)
+ let gitdir = a:dir . '/' . gitdir
+ endif
+ return isdirectory(gitdir) ? gitdir : ''
+endfunction
+
+function! s:git_origin_url(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let config = gitdir . '/config'
+ if empty(gitdir) || !filereadable(config)
+ return ''
+ endif
+ return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
+endfunction
+
+function! s:git_revision(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let head = gitdir . '/HEAD'
+ if empty(gitdir) || !filereadable(head)
+ return ''
+ endif
+
+ let line = get(readfile(head), 0, '')
+ let ref = matchstr(line, '^ref: \zs.*')
+ if empty(ref)
+ return line
+ endif
+
+ if filereadable(gitdir . '/' . ref)
+ return get(readfile(gitdir . '/' . ref), 0, '')
+ endif
+
+ if filereadable(gitdir . '/packed-refs')
+ for line in readfile(gitdir . '/packed-refs')
+ if line =~# ' ' . ref
+ return matchstr(line, '^[0-9a-f]*')
+ endif
+ endfor
+ endif
+
+ return ''
+endfunction
+
+function! s:git_local_branch(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let head = gitdir . '/HEAD'
+ if empty(gitdir) || !filereadable(head)
+ return ''
+ endif
+ let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
+ return len(branch) ? branch : 'HEAD'
+endfunction
+
+function! s:git_origin_branch(spec)
+ if len(a:spec.branch)
+ return a:spec.branch
+ endif
+
+ " The file may not be present if this is a local repository
+ let gitdir = s:git_dir(a:spec.dir)
+ let origin_head = gitdir.'/refs/remotes/origin/HEAD'
+ if len(gitdir) && filereadable(origin_head)
+ return matchstr(get(readfile(origin_head), 0, ''),
+ \ '^ref: refs/remotes/origin/\zs.*')
+ endif
+
+ " The command may not return the name of a branch in detached HEAD state
+ let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
+ return v:shell_error ? '' : result[-1]
+endfunction
+
+if s:is_win
+ function! s:plug_call(fn, ...)
+ let shellslash = &shellslash
+ try
+ set noshellslash
+ return call(a:fn, a:000)
+ finally
+ let &shellslash = shellslash
+ endtry
+ endfunction
+else
+ function! s:plug_call(fn, ...)
+ return call(a:fn, a:000)
+ endfunction
+endif
+
+function! s:plug_getcwd()
+ return s:plug_call('getcwd')
+endfunction
+
+function! s:plug_fnamemodify(fname, mods)
+ return s:plug_call('fnamemodify', a:fname, a:mods)
+endfunction
+
+function! s:plug_expand(fmt)
+ return s:plug_call('expand', a:fmt, 1)
+endfunction
+
+function! s:plug_tempname()
+ return s:plug_call('tempname')
+endfunction
+
+function! plug#begin(...)
+ if a:0 > 0
+ let s:plug_home_org = a:1
+ let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
+ elseif exists('g:plug_home')
+ let home = s:path(g:plug_home)
+ elseif !empty(&rtp)
+ let home = s:path(split(&rtp, ',')[0]) . '/plugged'
+ else
+ return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
+ endif
+ if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
+ return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
+ endif
+
+ let g:plug_home = home
+ let g:plugs = {}
+ let g:plugs_order = []
+ let s:triggers = {}
+
+ call s:define_commands()
+ return 1
+endfunction
+
+function! s:define_commands()
+ command! -nargs=+ -bar Plug call plug#(<args>)
+ if !executable('git')
+ return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
+ endif
+ if has('win32')
+ \ && &shellslash
+ \ && (&shell =~# 'cmd\(\.exe\)\?$' || &shell =~# 'powershell\(\.exe\)\?$')
+ return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
+ endif
+ if !has('nvim')
+ \ && (has('win32') || has('win32unix'))
+ \ && !has('multi_byte')
+ return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
+ endif
+ command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
+ command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
+ command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
+ command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
+ command! -nargs=0 -bar PlugStatus call s:status()
+ command! -nargs=0 -bar PlugDiff call s:diff()
+ command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
+endfunction
+
+function! s:to_a(v)
+ return type(a:v) == s:TYPE.list ? a:v : [a:v]
+endfunction
+
+function! s:to_s(v)
+ return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
+endfunction
+
+function! s:glob(from, pattern)
+ return s:lines(globpath(a:from, a:pattern))
+endfunction
+
+function! s:source(from, ...)
+ let found = 0
+ for pattern in a:000
+ for vim in s:glob(a:from, pattern)
+ execute 'source' s:esc(vim)
+ let found = 1
+ endfor
+ endfor
+ return found
+endfunction
+
+function! s:assoc(dict, key, val)
+ let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
+endfunction
+
+function! s:ask(message, ...)
+ call inputsave()
+ echohl WarningMsg
+ let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
+ echohl None
+ call inputrestore()
+ echo "\r"
+ return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
+endfunction
+
+function! s:ask_no_interrupt(...)
+ try
+ return call('s:ask', a:000)
+ catch
+ return 0
+ endtry
+endfunction
+
+function! s:lazy(plug, opt)
+ return has_key(a:plug, a:opt) &&
+ \ (empty(s:to_a(a:plug[a:opt])) ||
+ \ !isdirectory(a:plug.dir) ||
+ \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
+ \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
+endfunction
+
+function! plug#end()
+ if !exists('g:plugs')
+ return s:err('plug#end() called without calling plug#begin() first')
+ endif
+
+ if exists('#PlugLOD')
+ augroup PlugLOD
+ autocmd!
+ augroup END
+ augroup! PlugLOD
+ endif
+ let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
+
+ if exists('g:did_load_filetypes')
+ filetype off
+ endif
+ for name in g:plugs_order
+ if !has_key(g:plugs, name)
+ continue
+ endif
+ let plug = g:plugs[name]
+ if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
+ let s:loaded[name] = 1
+ continue
+ endif
+
+ if has_key(plug, 'on')
+ let s:triggers[name] = { 'map': [], 'cmd': [] }
+ for cmd in s:to_a(plug.on)
+ if cmd =~? '^<Plug>.\+'
+ if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
+ call s:assoc(lod.map, cmd, name)
+ endif
+ call add(s:triggers[name].map, cmd)
+ elseif cmd =~# '^[A-Z]'
+ let cmd = substitute(cmd, '!*$', '', '')
+ if exists(':'.cmd) != 2
+ call s:assoc(lod.cmd, cmd, name)
+ endif
+ call add(s:triggers[name].cmd, cmd)
+ else
+ call s:err('Invalid `on` option: '.cmd.
+ \ '. Should start with an uppercase letter or `<Plug>`.')
+ endif
+ endfor
+ endif
+
+ if has_key(plug, 'for')
+ let types = s:to_a(plug.for)
+ if !empty(types)
+ augroup filetypedetect
+ call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
+ augroup END
+ endif
+ for type in types
+ call s:assoc(lod.ft, type, name)
+ endfor
+ endif
+ endfor
+
+ for [cmd, names] in items(lod.cmd)
+ execute printf(
+ \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
+ \ cmd, string(cmd), string(names))
+ endfor
+
+ for [map, names] in items(lod.map)
+ for [mode, map_prefix, key_prefix] in
+ \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
+ execute printf(
+ \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
+ \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
+ endfor
+ endfor
+
+ for [ft, names] in items(lod.ft)
+ augroup PlugLOD
+ execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
+ \ ft, string(ft), string(names))
+ augroup END
+ endfor
+
+ call s:reorg_rtp()
+ filetype plugin indent on
+ if has('vim_starting')
+ if has('syntax') && !exists('g:syntax_on')
+ syntax enable
+ end
+ else
+ call s:reload_plugins()
+ endif
+endfunction
+
+function! s:loaded_names()
+ return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
+endfunction
+
+function! s:load_plugin(spec)
+ call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
+endfunction
+
+function! s:reload_plugins()
+ for name in s:loaded_names()
+ call s:load_plugin(g:plugs[name])
+ endfor
+endfunction
+
+function! s:trim(str)
+ return substitute(a:str, '[\/]\+$', '', '')
+endfunction
+
+function! s:version_requirement(val, min)
+ for idx in range(0, len(a:min) - 1)
+ let v = get(a:val, idx, 0)
+ if v < a:min[idx] | return 0
+ elseif v > a:min[idx] | return 1
+ endif
+ endfor
+ return 1
+endfunction
+
+function! s:git_version_requirement(...)
+ if !exists('s:git_version')
+ let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
+ endif
+ return s:version_requirement(s:git_version, a:000)
+endfunction
+
+function! s:progress_opt(base)
+ return a:base && !s:is_win &&
+ \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
+endfunction
+
+function! s:rtp(spec)
+ return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
+endfunction
+
+if s:is_win
+ function! s:path(path)
+ return s:trim(substitute(a:path, '/', '\', 'g'))
+ endfunction
+
+ function! s:dirpath(path)
+ return s:path(a:path) . '\'
+ endfunction
+
+ function! s:is_local_plug(repo)
+ return a:repo =~? '^[a-z]:\|^[%~]'
+ endfunction
+
+ " Copied from fzf
+ function! s:wrap_cmds(cmds)
+ let cmds = [
+ \ '@echo off',
+ \ 'setlocal enabledelayedexpansion']
+ \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
+ \ + ['endlocal']
+ if has('iconv')
+ if !exists('s:codepage')
+ let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
+ endif
+ return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
+ endif
+ return map(cmds, 'v:val."\r"')
+ endfunction
+
+ function! s:batchfile(cmd)
+ let batchfile = s:plug_tempname().'.bat'
+ call writefile(s:wrap_cmds(a:cmd), batchfile)
+ let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
+ if &shell =~# 'powershell\(\.exe\)\?$'
+ let cmd = '& ' . cmd
+ endif
+ return [batchfile, cmd]
+ endfunction
+else
+ function! s:path(path)
+ return s:trim(a:path)
+ endfunction
+
+ function! s:dirpath(path)
+ return substitute(a:path, '[/\\]*$', '/', '')
+ endfunction
+
+ function! s:is_local_plug(repo)
+ return a:repo[0] =~ '[/$~]'
+ endfunction
+endif
+
+function! s:err(msg)
+ echohl ErrorMsg
+ echom '[vim-plug] '.a:msg
+ echohl None
+endfunction
+
+function! s:warn(cmd, msg)
+ echohl WarningMsg
+ execute a:cmd 'a:msg'
+ echohl None
+endfunction
+
+function! s:esc(path)
+ return escape(a:path, ' ')
+endfunction
+
+function! s:escrtp(path)
+ return escape(a:path, ' ,')
+endfunction
+
+function! s:remove_rtp()
+ for name in s:loaded_names()
+ let rtp = s:rtp(g:plugs[name])
+ execute 'set rtp-='.s:escrtp(rtp)
+ let after = globpath(rtp, 'after')
+ if isdirectory(after)
+ execute 'set rtp-='.s:escrtp(after)
+ endif
+ endfor
+endfunction
+
+function! s:reorg_rtp()
+ if !empty(s:first_rtp)
+ execute 'set rtp-='.s:first_rtp
+ execute 'set rtp-='.s:last_rtp
+ endif
+
+ " &rtp is modified from outside
+ if exists('s:prtp') && s:prtp !=# &rtp
+ call s:remove_rtp()
+ unlet! s:middle
+ endif
+
+ let s:middle = get(s:, 'middle', &rtp)
+ let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
+ let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
+ let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
+ \ . ','.s:middle.','
+ \ . join(map(afters, 'escape(v:val, ",")'), ',')
+ let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
+ let s:prtp = &rtp
+
+ if !empty(s:first_rtp)
+ execute 'set rtp^='.s:first_rtp
+ execute 'set rtp+='.s:last_rtp
+ endif
+endfunction
+
+function! s:doautocmd(...)
+ if exists('#'.join(a:000, '#'))
+ execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
+ endif
+endfunction
+
+function! s:dobufread(names)
+ for name in a:names
+ let path = s:rtp(g:plugs[name])
+ for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
+ if len(finddir(dir, path))
+ if exists('#BufRead')
+ doautocmd BufRead
+ endif
+ return
+ endif
+ endfor
+ endfor
+endfunction
+
+function! plug#load(...)
+ if a:0 == 0
+ return s:err('Argument missing: plugin name(s) required')
+ endif
+ if !exists('g:plugs')
+ return s:err('plug#begin was not called')
+ endif
+ let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
+ let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
+ if !empty(unknowns)
+ let s = len(unknowns) > 1 ? 's' : ''
+ return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
+ end
+ let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
+ if !empty(unloaded)
+ for name in unloaded
+ call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ endfor
+ call s:dobufread(unloaded)
+ return 1
+ end
+ return 0
+endfunction
+
+function! s:remove_triggers(name)
+ if !has_key(s:triggers, a:name)
+ return
+ endif
+ for cmd in s:triggers[a:name].cmd
+ execute 'silent! delc' cmd
+ endfor
+ for map in s:triggers[a:name].map
+ execute 'silent! unmap' map
+ execute 'silent! iunmap' map
+ endfor
+ call remove(s:triggers, a:name)
+endfunction
+
+function! s:lod(names, types, ...)
+ for name in a:names
+ call s:remove_triggers(name)
+ let s:loaded[name] = 1
+ endfor
+ call s:reorg_rtp()
+
+ for name in a:names
+ let rtp = s:rtp(g:plugs[name])
+ for dir in a:types
+ call s:source(rtp, dir.'/**/*.vim')
+ endfor
+ if a:0
+ if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
+ execute 'runtime' a:1
+ endif
+ call s:source(rtp, a:2)
+ endif
+ call s:doautocmd('User', name)
+ endfor
+endfunction
+
+function! s:lod_ft(pat, names)
+ let syn = 'syntax/'.a:pat.'.vim'
+ call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
+ execute 'autocmd! PlugLOD FileType' a:pat
+ call s:doautocmd('filetypeplugin', 'FileType')
+ call s:doautocmd('filetypeindent', 'FileType')
+endfunction
+
+function! s:lod_cmd(cmd, bang, l1, l2, args, names)
+ call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ call s:dobufread(a:names)
+ execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
+endfunction
+
+function! s:lod_map(map, names, with_prefix, prefix)
+ call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ call s:dobufread(a:names)
+ let extra = ''
+ while 1
+ let c = getchar(0)
+ if c == 0
+ break
+ endif
+ let extra .= nr2char(c)
+ endwhile
+
+ if a:with_prefix
+ let prefix = v:count ? v:count : ''
+ let prefix .= '"'.v:register.a:prefix
+ if mode(1) == 'no'
+ if v:operator == 'c'
+ let prefix = "\<esc>" . prefix
+ endif
+ let prefix .= v:operator
+ endif
+ call feedkeys(prefix, 'n')
+ endif
+ call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
+endfunction
+
+function! plug#(repo, ...)
+ if a:0 > 1
+ return s:err('Invalid number of arguments (1..2)')
+ endif
+
+ try
+ let repo = s:trim(a:repo)
+ let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
+ let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
+ let spec = extend(s:infer_properties(name, repo), opts)
+ if !has_key(g:plugs, name)
+ call add(g:plugs_order, name)
+ endif
+ let g:plugs[name] = spec
+ let s:loaded[name] = get(s:loaded, name, 0)
+ catch
+ return s:err(repo . ' ' . v:exception)
+ endtry
+endfunction
+
+function! s:parse_options(arg)
+ let opts = copy(s:base_spec)
+ let type = type(a:arg)
+ let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
+ if type == s:TYPE.string
+ if empty(a:arg)
+ throw printf(opt_errfmt, 'tag', 'string')
+ endif
+ let opts.tag = a:arg
+ elseif type == s:TYPE.dict
+ for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
+ if has_key(a:arg, opt)
+ \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
+ throw printf(opt_errfmt, opt, 'string')
+ endif
+ endfor
+ for opt in ['on', 'for']
+ if has_key(a:arg, opt)
+ \ && type(a:arg[opt]) != s:TYPE.list
+ \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
+ throw printf(opt_errfmt, opt, 'string or list')
+ endif
+ endfor
+ if has_key(a:arg, 'do')
+ \ && type(a:arg.do) != s:TYPE.funcref
+ \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
+ throw printf(opt_errfmt, 'do', 'string or funcref')
+ endif
+ call extend(opts, a:arg)
+ if has_key(opts, 'dir')
+ let opts.dir = s:dirpath(s:plug_expand(opts.dir))
+ endif
+ else
+ throw 'Invalid argument type (expected: string or dictionary)'
+ endif
+ return opts
+endfunction
+
+function! s:infer_properties(name, repo)
+ let repo = a:repo
+ if s:is_local_plug(repo)
+ return { 'dir': s:dirpath(s:plug_expand(repo)) }
+ else
+ if repo =~ ':'
+ let uri = repo
+ else
+ if repo !~ '/'
+ throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
+ endif
+ let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
+ let uri = printf(fmt, repo)
+ endif
+ return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
+ endif
+endfunction
+
+function! s:install(force, names)
+ call s:update_impl(0, a:force, a:names)
+endfunction
+
+function! s:update(force, names)
+ call s:update_impl(1, a:force, a:names)
+endfunction
+
+function! plug#helptags()
+ if !exists('g:plugs')
+ return s:err('plug#begin was not called')
+ endif
+ for spec in values(g:plugs)
+ let docd = join([s:rtp(spec), 'doc'], '/')
+ if isdirectory(docd)
+ silent! execute 'helptags' s:esc(docd)
+ endif
+ endfor
+ return 1
+endfunction
+
+function! s:syntax()
+ syntax clear
+ syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
+ syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
+ syn match plugNumber /[0-9]\+[0-9.]*/ contained
+ syn match plugBracket /[[\]]/ contained
+ syn match plugX /x/ contained
+ syn match plugDash /^-\{1}\ /
+ syn match plugPlus /^+/
+ syn match plugStar /^*/
+ syn match plugMessage /\(^- \)\@<=.*/
+ syn match plugName /\(^- \)\@<=[^ ]*:/
+ syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
+ syn match plugTag /(tag: [^)]\+)/
+ syn match plugInstall /\(^+ \)\@<=[^:]*/
+ syn match plugUpdate /\(^* \)\@<=[^:]*/
+ syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
+ syn match plugEdge /^ \X\+$/
+ syn match plugEdge /^ \X*/ contained nextgroup=plugSha
+ syn match plugSha /[0-9a-f]\{7,9}/ contained
+ syn match plugRelDate /([^)]*)$/ contained
+ syn match plugNotLoaded /(not loaded)$/
+ syn match plugError /^x.*/
+ syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
+ syn match plugH2 /^.*:\n-\+$/
+ syn match plugH2 /^-\{2,}/
+ syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
+ hi def link plug1 Title
+ hi def link plug2 Repeat
+ hi def link plugH2 Type
+ hi def link plugX Exception
+ hi def link plugBracket Structure
+ hi def link plugNumber Number
+
+ hi def link plugDash Special
+ hi def link plugPlus Constant
+ hi def link plugStar Boolean
+
+ hi def link plugMessage Function
+ hi def link plugName Label
+ hi def link plugInstall Function
+ hi def link plugUpdate Type
+
+ hi def link plugError Error
+ hi def link plugDeleted Ignore
+ hi def link plugRelDate Comment
+ hi def link plugEdge PreProc
+ hi def link plugSha Identifier
+ hi def link plugTag Constant
+
+ hi def link plugNotLoaded Comment
+endfunction
+
+function! s:lpad(str, len)
+ return a:str . repeat(' ', a:len - len(a:str))
+endfunction
+
+function! s:lines(msg)
+ return split(a:msg, "[\r\n]")
+endfunction
+
+function! s:lastline(msg)
+ return get(s:lines(a:msg), -1, '')
+endfunction
+
+function! s:new_window()
+ execute get(g:, 'plug_window', 'vertical topleft new')
+endfunction
+
+function! s:plug_window_exists()
+ let buflist = tabpagebuflist(s:plug_tab)
+ return !empty(buflist) && index(buflist, s:plug_buf) >= 0
+endfunction
+
+function! s:switch_in()
+ if !s:plug_window_exists()
+ return 0
+ endif
+
+ if winbufnr(0) != s:plug_buf
+ let s:pos = [tabpagenr(), winnr(), winsaveview()]
+ execute 'normal!' s:plug_tab.'gt'
+ let winnr = bufwinnr(s:plug_buf)
+ execute winnr.'wincmd w'
+ call add(s:pos, winsaveview())
+ else
+ let s:pos = [winsaveview()]
+ endif
+
+ setlocal modifiable
+ return 1
+endfunction
+
+function! s:switch_out(...)
+ call winrestview(s:pos[-1])
+ setlocal nomodifiable
+ if a:0 > 0
+ execute a:1
+ endif
+
+ if len(s:pos) > 1
+ execute 'normal!' s:pos[0].'gt'
+ execute s:pos[1] 'wincmd w'
+ call winrestview(s:pos[2])
+ endif
+endfunction
+
+function! s:finish_bindings()
+ nnoremap <silent> <buffer> R :call <SID>retry()<cr>
+ nnoremap <silent> <buffer> D :PlugDiff<cr>
+ nnoremap <silent> <buffer> S :PlugStatus<cr>
+ nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
+ xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
+ nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
+ nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
+endfunction
+
+function! s:prepare(...)
+ if empty(s:plug_getcwd())
+ throw 'Invalid current working directory. Cannot proceed.'
+ endif
+
+ for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
+ if exists(evar)
+ throw evar.' detected. Cannot proceed.'
+ endif
+ endfor
+
+ call s:job_abort()
+ if s:switch_in()
+ if b:plug_preview == 1
+ pc
+ endif
+ enew
+ else
+ call s:new_window()
+ endif
+
+ nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
+ if a:0 == 0
+ call s:finish_bindings()
+ endif
+ let b:plug_preview = -1
+ let s:plug_tab = tabpagenr()
+ let s:plug_buf = winbufnr(0)
+ call s:assign_name()
+
+ for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
+ execute 'silent! unmap <buffer>' k
+ endfor
+ setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
+ if exists('+colorcolumn')
+ setlocal colorcolumn=
+ endif
+ setf vim-plug
+ if exists('g:syntax_on')
+ call s:syntax()
+ endif
+endfunction
+
+function! s:assign_name()
+ " Assign buffer name
+ let prefix = '[Plugins]'
+ let name = prefix
+ let idx = 2
+ while bufexists(name)
+ let name = printf('%s (%s)', prefix, idx)
+ let idx = idx + 1
+ endwhile
+ silent! execute 'f' fnameescape(name)
+endfunction
+
+function! s:chsh(swap)
+ let prev = [&shell, &shellcmdflag, &shellredir]
+ if !s:is_win
+ set shell=sh
+ endif
+ if a:swap
+ if &shell =~# 'powershell\(\.exe\)\?$' || &shell =~# 'pwsh$'
+ let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
+ elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
+ set shellredir=>%s\ 2>&1
+ endif
+ endif
+ return prev
+endfunction
+
+function! s:bang(cmd, ...)
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(a:0)
+ " FIXME: Escaping is incomplete. We could use shellescape with eval,
+ " but it won't work on Windows.
+ let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
+ if s:is_win
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
+ execute "normal! :execute g:_plug_bang\<cr>\<cr>"
+ finally
+ unlet g:_plug_bang
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+ return v:shell_error ? 'Exit status: ' . v:shell_error : ''
+endfunction
+
+function! s:regress_bar()
+ let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
+ call s:progress_bar(2, bar, len(bar))
+endfunction
+
+function! s:is_updated(dir)
+ return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
+endfunction
+
+function! s:do(pull, force, todo)
+ for [name, spec] in items(a:todo)
+ if !isdirectory(spec.dir)
+ continue
+ endif
+ let installed = has_key(s:update.new, name)
+ let updated = installed ? 0 :
+ \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
+ if a:force || installed || updated
+ execute 'cd' s:esc(spec.dir)
+ call append(3, '- Post-update hook for '. name .' ... ')
+ let error = ''
+ let type = type(spec.do)
+ if type == s:TYPE.string
+ if spec.do[0] == ':'
+ if !get(s:loaded, name, 0)
+ let s:loaded[name] = 1
+ call s:reorg_rtp()
+ endif
+ call s:load_plugin(spec)
+ try
+ execute spec.do[1:]
+ catch
+ let error = v:exception
+ endtry
+ if !s:plug_window_exists()
+ cd -
+ throw 'Warning: vim-plug was terminated by the post-update hook of '.name
+ endif
+ else
+ let error = s:bang(spec.do)
+ endif
+ elseif type == s:TYPE.funcref
+ try
+ call s:load_plugin(spec)
+ let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
+ call spec.do({ 'name': name, 'status': status, 'force': a:force })
+ catch
+ let error = v:exception
+ endtry
+ else
+ let error = 'Invalid hook type'
+ endif
+ call s:switch_in()
+ call setline(4, empty(error) ? (getline(4) . 'OK')
+ \ : ('x' . getline(4)[1:] . error))
+ if !empty(error)
+ call add(s:update.errors, name)
+ call s:regress_bar()
+ endif
+ cd -
+ endif
+ endfor
+endfunction
+
+function! s:hash_match(a, b)
+ return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
+endfunction
+
+function! s:checkout(spec)
+ let sha = a:spec.commit
+ let output = s:git_revision(a:spec.dir)
+ if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
+ let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
+ let output = s:system(
+ \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
+ endif
+ return output
+endfunction
+
+function! s:finish(pull)
+ let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
+ if new_frozen
+ let s = new_frozen > 1 ? 's' : ''
+ call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
+ endif
+ call append(3, '- Finishing ... ') | 4
+ redraw
+ call plug#helptags()
+ call plug#end()
+ call setline(4, getline(4) . 'Done!')
+ redraw
+ let msgs = []
+ if !empty(s:update.errors)
+ call add(msgs, "Press 'R' to retry.")
+ endif
+ if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
+ \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
+ call add(msgs, "Press 'D' to see the updated changes.")
+ endif
+ echo join(msgs, ' ')
+ call s:finish_bindings()
+endfunction
+
+function! s:retry()
+ if empty(s:update.errors)
+ return
+ endif
+ echo
+ call s:update_impl(s:update.pull, s:update.force,
+ \ extend(copy(s:update.errors), [s:update.threads]))
+endfunction
+
+function! s:is_managed(name)
+ return has_key(g:plugs[a:name], 'uri')
+endfunction
+
+function! s:names(...)
+ return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
+endfunction
+
+function! s:check_ruby()
+ silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
+ if !exists('g:plug_ruby')
+ redraw!
+ return s:warn('echom', 'Warning: Ruby interface is broken')
+ endif
+ let ruby_version = split(g:plug_ruby, '\.')
+ unlet g:plug_ruby
+ return s:version_requirement(ruby_version, [1, 8, 7])
+endfunction
+
+function! s:update_impl(pull, force, args) abort
+ let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
+ let args = filter(copy(a:args), 'v:val != "--sync"')
+ let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
+ \ remove(args, -1) : get(g:, 'plug_threads', 16)
+
+ let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
+ let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
+ \ filter(managed, 'index(args, v:key) >= 0')
+
+ if empty(todo)
+ return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
+ endif
+
+ if !s:is_win && s:git_version_requirement(2, 3)
+ let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
+ let $GIT_TERMINAL_PROMPT = 0
+ for plug in values(todo)
+ let plug.uri = substitute(plug.uri,
+ \ '^https://git::@github\.com', 'https://github.com', '')
+ endfor
+ endif
+
+ if !isdirectory(g:plug_home)
+ try
+ call mkdir(g:plug_home, 'p')
+ catch
+ return s:err(printf('Invalid plug directory: %s. '.
+ \ 'Try to call plug#begin with a valid directory', g:plug_home))
+ endtry
+ endif
+
+ if has('nvim') && !exists('*jobwait') && threads > 1
+ call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
+ endif
+
+ let use_job = s:nvim || s:vim8
+ let python = (has('python') || has('python3')) && !use_job
+ let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
+
+ let s:update = {
+ \ 'start': reltime(),
+ \ 'all': todo,
+ \ 'todo': copy(todo),
+ \ 'errors': [],
+ \ 'pull': a:pull,
+ \ 'force': a:force,
+ \ 'new': {},
+ \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
+ \ 'bar': '',
+ \ 'fin': 0
+ \ }
+
+ call s:prepare(1)
+ call append(0, ['', ''])
+ normal! 2G
+ silent! redraw
+
+ let s:clone_opt = []
+ if get(g:, 'plug_shallow', 1)
+ call extend(s:clone_opt, ['--depth', '1'])
+ if s:git_version_requirement(1, 7, 10)
+ call add(s:clone_opt, '--no-single-branch')
+ endif
+ endif
+
+ if has('win32unix') || has('wsl')
+ call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
+ endif
+
+ let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
+
+ " Python version requirement (>= 2.7)
+ if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
+ redir => pyv
+ silent python import platform; print platform.python_version()
+ redir END
+ let python = s:version_requirement(
+ \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
+ endif
+
+ if (python || ruby) && s:update.threads > 1
+ try
+ let imd = &imd
+ if s:mac_gui
+ set noimd
+ endif
+ if ruby
+ call s:update_ruby()
+ else
+ call s:update_python()
+ endif
+ catch
+ let lines = getline(4, '$')
+ let printed = {}
+ silent! 4,$d _
+ for line in lines
+ let name = s:extract_name(line, '.', '')
+ if empty(name) || !has_key(printed, name)
+ call append('$', line)
+ if !empty(name)
+ let printed[name] = 1
+ if line[0] == 'x' && index(s:update.errors, name) < 0
+ call add(s:update.errors, name)
+ end
+ endif
+ endif
+ endfor
+ finally
+ let &imd = imd
+ call s:update_finish()
+ endtry
+ else
+ call s:update_vim()
+ while use_job && sync
+ sleep 100m
+ if s:update.fin
+ break
+ endif
+ endwhile
+ endif
+endfunction
+
+function! s:log4(name, msg)
+ call setline(4, printf('- %s (%s)', a:msg, a:name))
+ redraw
+endfunction
+
+function! s:update_finish()
+ if exists('s:git_terminal_prompt')
+ let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
+ endif
+ if s:switch_in()
+ call append(3, '- Updating ...') | 4
+ for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
+ let [pos, _] = s:logpos(name)
+ if !pos
+ continue
+ endif
+ if has_key(spec, 'commit')
+ call s:log4(name, 'Checking out '.spec.commit)
+ let out = s:checkout(spec)
+ elseif has_key(spec, 'tag')
+ let tag = spec.tag
+ if tag =~ '\*'
+ let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
+ if !v:shell_error && !empty(tags)
+ let tag = tags[0]
+ call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
+ call append(3, '')
+ endif
+ endif
+ call s:log4(name, 'Checking out '.tag)
+ let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
+ else
+ let branch = s:git_origin_branch(spec)
+ call s:log4(name, 'Merging origin/'.s:esc(branch))
+ let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
+ \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
+ endif
+ if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
+ \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
+ call s:log4(name, 'Updating submodules. This may take a while.')
+ let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
+ endif
+ let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
+ if v:shell_error
+ call add(s:update.errors, name)
+ call s:regress_bar()
+ silent execute pos 'd _'
+ call append(4, msg) | 4
+ elseif !empty(out)
+ call setline(pos, msg[0])
+ endif
+ redraw
+ endfor
+ silent 4 d _
+ try
+ call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
+ catch
+ call s:warn('echom', v:exception)
+ call s:warn('echo', '')
+ return
+ endtry
+ call s:finish(s:update.pull)
+ call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
+ call s:switch_out('normal! gg')
+ endif
+endfunction
+
+function! s:job_abort()
+ if (!s:nvim && !s:vim8) || !exists('s:jobs')
+ return
+ endif
+
+ for [name, j] in items(s:jobs)
+ if s:nvim
+ silent! call jobstop(j.jobid)
+ elseif s:vim8
+ silent! call job_stop(j.jobid)
+ endif
+ if j.new
+ call s:rm_rf(g:plugs[name].dir)
+ endif
+ endfor
+ let s:jobs = {}
+endfunction
+
+function! s:last_non_empty_line(lines)
+ let len = len(a:lines)
+ for idx in range(len)
+ let line = a:lines[len-idx-1]
+ if !empty(line)
+ return line
+ endif
+ endfor
+ return ''
+endfunction
+
+function! s:job_out_cb(self, data) abort
+ let self = a:self
+ let data = remove(self.lines, -1) . a:data
+ let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
+ call extend(self.lines, lines)
+ " To reduce the number of buffer updates
+ let self.tick = get(self, 'tick', -1) + 1
+ if !self.running || self.tick % len(s:jobs) == 0
+ let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
+ let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
+ call s:log(bullet, self.name, result)
+ endif
+endfunction
+
+function! s:job_exit_cb(self, data) abort
+ let a:self.running = 0
+ let a:self.error = a:data != 0
+ call s:reap(a:self.name)
+ call s:tick()
+endfunction
+
+function! s:job_cb(fn, job, ch, data)
+ if !s:plug_window_exists() " plug window closed
+ return s:job_abort()
+ endif
+ call call(a:fn, [a:job, a:data])
+endfunction
+
+function! s:nvim_cb(job_id, data, event) dict abort
+ return (a:event == 'stdout' || a:event == 'stderr') ?
+ \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
+ \ s:job_cb('s:job_exit_cb', self, 0, a:data)
+endfunction
+
+function! s:spawn(name, cmd, opts)
+ let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
+ \ 'new': get(a:opts, 'new', 0) }
+ let s:jobs[a:name] = job
+
+ if s:nvim
+ if has_key(a:opts, 'dir')
+ let job.cwd = a:opts.dir
+ endif
+ let argv = a:cmd
+ call extend(job, {
+ \ 'on_stdout': function('s:nvim_cb'),
+ \ 'on_stderr': function('s:nvim_cb'),
+ \ 'on_exit': function('s:nvim_cb'),
+ \ })
+ let jid = s:plug_call('jobstart', argv, job)
+ if jid > 0
+ let job.jobid = jid
+ else
+ let job.running = 0
+ let job.error = 1
+ let job.lines = [jid < 0 ? argv[0].' is not executable' :
+ \ 'Invalid arguments (or job table is full)']
+ endif
+ elseif s:vim8
+ let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
+ if has_key(a:opts, 'dir')
+ let cmd = s:with_cd(cmd, a:opts.dir, 0)
+ endif
+ let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
+ let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
+ \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
+ \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
+ \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
+ \ 'err_mode': 'raw',
+ \ 'out_mode': 'raw'
+ \})
+ if job_status(jid) == 'run'
+ let job.jobid = jid
+ else
+ let job.running = 0
+ let job.error = 1
+ let job.lines = ['Failed to start job']
+ endif
+ else
+ let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
+ let job.error = v:shell_error != 0
+ let job.running = 0
+ endif
+endfunction
+
+function! s:reap(name)
+ let job = s:jobs[a:name]
+ if job.error
+ call add(s:update.errors, a:name)
+ elseif get(job, 'new', 0)
+ let s:update.new[a:name] = 1
+ endif
+ let s:update.bar .= job.error ? 'x' : '='
+
+ let bullet = job.error ? 'x' : '-'
+ let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
+ call s:log(bullet, a:name, empty(result) ? 'OK' : result)
+ call s:bar()
+
+ call remove(s:jobs, a:name)
+endfunction
+
+function! s:bar()
+ if s:switch_in()
+ let total = len(s:update.all)
+ call setline(1, (s:update.pull ? 'Updating' : 'Installing').
+ \ ' plugins ('.len(s:update.bar).'/'.total.')')
+ call s:progress_bar(2, s:update.bar, total)
+ call s:switch_out()
+ endif
+endfunction
+
+function! s:logpos(name)
+ let max = line('$')
+ for i in range(4, max > 4 ? max : 4)
+ if getline(i) =~# '^[-+x*] '.a:name.':'
+ for j in range(i + 1, max > 5 ? max : 5)
+ if getline(j) !~ '^ '
+ return [i, j - 1]
+ endif
+ endfor
+ return [i, i]
+ endif
+ endfor
+ return [0, 0]
+endfunction
+
+function! s:log(bullet, name, lines)
+ if s:switch_in()
+ let [b, e] = s:logpos(a:name)
+ if b > 0
+ silent execute printf('%d,%d d _', b, e)
+ if b > winheight('.')
+ let b = 4
+ endif
+ else
+ let b = 4
+ endif
+ " FIXME For some reason, nomodifiable is set after :d in vim8
+ setlocal modifiable
+ call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
+ call s:switch_out()
+ endif
+endfunction
+
+function! s:update_vim()
+ let s:jobs = {}
+
+ call s:bar()
+ call s:tick()
+endfunction
+
+function! s:tick()
+ let pull = s:update.pull
+ let prog = s:progress_opt(s:nvim || s:vim8)
+while 1 " Without TCO, Vim stack is bound to explode
+ if empty(s:update.todo)
+ if empty(s:jobs) && !s:update.fin
+ call s:update_finish()
+ let s:update.fin = 1
+ endif
+ return
+ endif
+
+ let name = keys(s:update.todo)[0]
+ let spec = remove(s:update.todo, name)
+ let new = empty(globpath(spec.dir, '.git', 1))
+
+ call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
+ redraw
+
+ let has_tag = has_key(spec, 'tag')
+ if !new
+ let [error, _] = s:git_validate(spec, 0)
+ if empty(error)
+ if pull
+ let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
+ if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
+ call extend(cmd, ['--depth', '99999999'])
+ endif
+ if !empty(prog)
+ call add(cmd, prog)
+ endif
+ call s:spawn(name, cmd, { 'dir': spec.dir })
+ else
+ let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
+ endif
+ else
+ let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
+ endif
+ else
+ let cmd = ['git', 'clone']
+ if !has_tag
+ call extend(cmd, s:clone_opt)
+ endif
+ if !empty(prog)
+ call add(cmd, prog)
+ endif
+ call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
+ endif
+
+ if !s:jobs[name].running
+ call s:reap(name)
+ endif
+ if len(s:jobs) >= s:update.threads
+ break
+ endif
+endwhile
+endfunction
+
+function! s:update_python()
+let py_exe = has('python') ? 'python' : 'python3'
+execute py_exe "<< EOF"
+import datetime
+import functools
+import os
+try:
+ import queue
+except ImportError:
+ import Queue as queue
+import random
+import re
+import shutil
+import signal
+import subprocess
+import tempfile
+import threading as thr
+import time
+import traceback
+import vim
+
+G_NVIM = vim.eval("has('nvim')") == '1'
+G_PULL = vim.eval('s:update.pull') == '1'
+G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
+G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
+G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
+G_PROGRESS = vim.eval('s:progress_opt(1)')
+G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
+G_STOP = thr.Event()
+G_IS_WIN = vim.eval('s:is_win') == '1'
+
+class PlugError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+class CmdTimedOut(PlugError):
+ pass
+class CmdFailed(PlugError):
+ pass
+class InvalidURI(PlugError):
+ pass
+class Action(object):
+ INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
+
+class Buffer(object):
+ def __init__(self, lock, num_plugs, is_pull):
+ self.bar = ''
+ self.event = 'Updating' if is_pull else 'Installing'
+ self.lock = lock
+ self.maxy = int(vim.eval('winheight(".")'))
+ self.num_plugs = num_plugs
+
+ def __where(self, name):
+ """ Find first line with name in current buffer. Return line num. """
+ found, lnum = False, 0
+ matcher = re.compile('^[-+x*] {0}:'.format(name))
+ for line in vim.current.buffer:
+ if matcher.search(line) is not None:
+ found = True
+ break
+ lnum += 1
+
+ if not found:
+ lnum = -1
+ return lnum
+
+ def header(self):
+ curbuf = vim.current.buffer
+ curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
+
+ num_spaces = self.num_plugs - len(self.bar)
+ curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
+
+ with self.lock:
+ vim.command('normal! 2G')
+ vim.command('redraw')
+
+ def write(self, action, name, lines):
+ first, rest = lines[0], lines[1:]
+ msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
+ msg.extend([' ' + line for line in rest])
+
+ try:
+ if action == Action.ERROR:
+ self.bar += 'x'
+ vim.command("call add(s:update.errors, '{0}')".format(name))
+ elif action == Action.DONE:
+ self.bar += '='
+
+ curbuf = vim.current.buffer
+ lnum = self.__where(name)
+ if lnum != -1: # Found matching line num
+ del curbuf[lnum]
+ if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
+ lnum = 3
+ else:
+ lnum = 3
+ curbuf.append(msg, lnum)
+
+ self.header()
+ except vim.error:
+ pass
+
+class Command(object):
+ CD = 'cd /d' if G_IS_WIN else 'cd'
+
+ def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
+ self.cmd = cmd
+ if cmd_dir:
+ self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
+ self.timeout = timeout
+ self.callback = cb if cb else (lambda msg: None)
+ self.clean = clean if clean else (lambda: None)
+ self.proc = None
+
+ @property
+ def alive(self):
+ """ Returns true only if command still running. """
+ return self.proc and self.proc.poll() is None
+
+ def execute(self, ntries=3):
+ """ Execute the command with ntries if CmdTimedOut.
+ Returns the output of the command if no Exception.
+ """
+ attempt, finished, limit = 0, False, self.timeout
+
+ while not finished:
+ try:
+ attempt += 1
+ result = self.try_command()
+ finished = True
+ return result
+ except CmdTimedOut:
+ if attempt != ntries:
+ self.notify_retry()
+ self.timeout += limit
+ else:
+ raise
+
+ def notify_retry(self):
+ """ Retry required for command, notify user. """
+ for count in range(3, 0, -1):
+ if G_STOP.is_set():
+ raise KeyboardInterrupt
+ msg = 'Timeout. Will retry in {0} second{1} ...'.format(
+ count, 's' if count != 1 else '')
+ self.callback([msg])
+ time.sleep(1)
+ self.callback(['Retrying ...'])
+
+ def try_command(self):
+ """ Execute a cmd & poll for callback. Returns list of output.
+ Raises CmdFailed -> return code for Popen isn't 0
+ Raises CmdTimedOut -> command exceeded timeout without new output
+ """
+ first_line = True
+
+ try:
+ tfile = tempfile.NamedTemporaryFile(mode='w+b')
+ preexec_fn = not G_IS_WIN and os.setsid or None
+ self.proc = subprocess.Popen(self.cmd, stdout=tfile,
+ stderr=subprocess.STDOUT,
+ stdin=subprocess.PIPE, shell=True,
+ preexec_fn=preexec_fn)
+ thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
+ thrd.start()
+
+ thread_not_started = True
+ while thread_not_started:
+ try:
+ thrd.join(0.1)
+ thread_not_started = False
+ except RuntimeError:
+ pass
+
+ while self.alive:
+ if G_STOP.is_set():
+ raise KeyboardInterrupt
+
+ if first_line or random.random() < G_LOG_PROB:
+ first_line = False
+ line = '' if G_IS_WIN else nonblock_read(tfile.name)
+ if line:
+ self.callback([line])
+
+ time_diff = time.time() - os.path.getmtime(tfile.name)
+ if time_diff > self.timeout:
+ raise CmdTimedOut(['Timeout!'])
+
+ thrd.join(0.5)
+
+ tfile.seek(0)
+ result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
+
+ if self.proc.returncode != 0:
+ raise CmdFailed([''] + result)
+
+ return result
+ except:
+ self.terminate()
+ raise
+
+ def terminate(self):
+ """ Terminate process and cleanup. """
+ if self.alive:
+ if G_IS_WIN:
+ os.kill(self.proc.pid, signal.SIGINT)
+ else:
+ os.killpg(self.proc.pid, signal.SIGTERM)
+ self.clean()
+
+class Plugin(object):
+ def __init__(self, name, args, buf_q, lock):
+ self.name = name
+ self.args = args
+ self.buf_q = buf_q
+ self.lock = lock
+ self.tag = args.get('tag', 0)
+
+ def manage(self):
+ try:
+ if os.path.exists(self.args['dir']):
+ self.update()
+ else:
+ self.install()
+ with self.lock:
+ thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
+ except PlugError as exc:
+ self.write(Action.ERROR, self.name, exc.msg)
+ except KeyboardInterrupt:
+ G_STOP.set()
+ self.write(Action.ERROR, self.name, ['Interrupted!'])
+ except:
+ # Any exception except those above print stack trace
+ msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
+ self.write(Action.ERROR, self.name, msg.split('\n'))
+ raise
+
+ def install(self):
+ target = self.args['dir']
+ if target[-1] == '\\':
+ target = target[0:-1]
+
+ def clean(target):
+ def _clean():
+ try:
+ shutil.rmtree(target)
+ except OSError:
+ pass
+ return _clean
+
+ self.write(Action.INSTALL, self.name, ['Installing ...'])
+ callback = functools.partial(self.write, Action.INSTALL, self.name)
+ cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
+ '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
+ esc(target))
+ com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
+ result = com.execute(G_RETRIES)
+ self.write(Action.DONE, self.name, result[-1:])
+
+ def repo_uri(self):
+ cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
+ command = Command(cmd, self.args['dir'], G_TIMEOUT,)
+ result = command.execute(G_RETRIES)
+ return result[-1]
+
+ def update(self):
+ actual_uri = self.repo_uri()
+ expect_uri = self.args['uri']
+ regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
+ ma = regex.match(actual_uri)
+ mb = regex.match(expect_uri)
+ if ma is None or mb is None or ma.groups() != mb.groups():
+ msg = ['',
+ 'Invalid URI: {0}'.format(actual_uri),
+ 'Expected {0}'.format(expect_uri),
+ 'PlugClean required.']
+ raise InvalidURI(msg)
+
+ if G_PULL:
+ self.write(Action.UPDATE, self.name, ['Updating ...'])
+ callback = functools.partial(self.write, Action.UPDATE, self.name)
+ fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
+ cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
+ com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
+ result = com.execute(G_RETRIES)
+ self.write(Action.DONE, self.name, result[-1:])
+ else:
+ self.write(Action.DONE, self.name, ['Already installed'])
+
+ def write(self, action, name, msg):
+ self.buf_q.put((action, name, msg))
+
+class PlugThread(thr.Thread):
+ def __init__(self, tname, args):
+ super(PlugThread, self).__init__()
+ self.tname = tname
+ self.args = args
+
+ def run(self):
+ thr.current_thread().name = self.tname
+ buf_q, work_q, lock = self.args
+
+ try:
+ while not G_STOP.is_set():
+ name, args = work_q.get_nowait()
+ plug = Plugin(name, args, buf_q, lock)
+ plug.manage()
+ work_q.task_done()
+ except queue.Empty:
+ pass
+
+class RefreshThread(thr.Thread):
+ def __init__(self, lock):
+ super(RefreshThread, self).__init__()
+ self.lock = lock
+ self.running = True
+
+ def run(self):
+ while self.running:
+ with self.lock:
+ thread_vim_command('noautocmd normal! a')
+ time.sleep(0.33)
+
+ def stop(self):
+ self.running = False
+
+if G_NVIM:
+ def thread_vim_command(cmd):
+ vim.session.threadsafe_call(lambda: vim.command(cmd))
+else:
+ def thread_vim_command(cmd):
+ vim.command(cmd)
+
+def esc(name):
+ return '"' + name.replace('"', '\"') + '"'
+
+def nonblock_read(fname):
+ """ Read a file with nonblock flag. Return the last line. """
+ fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
+ buf = os.read(fread, 100000).decode('utf-8', 'replace')
+ os.close(fread)
+
+ line = buf.rstrip('\r\n')
+ left = max(line.rfind('\r'), line.rfind('\n'))
+ if left != -1:
+ left += 1
+ line = line[left:]
+
+ return line
+
+def main():
+ thr.current_thread().name = 'main'
+ nthreads = int(vim.eval('s:update.threads'))
+ plugs = vim.eval('s:update.todo')
+ mac_gui = vim.eval('s:mac_gui') == '1'
+
+ lock = thr.Lock()
+ buf = Buffer(lock, len(plugs), G_PULL)
+ buf_q, work_q = queue.Queue(), queue.Queue()
+ for work in plugs.items():
+ work_q.put(work)
+
+ start_cnt = thr.active_count()
+ for num in range(nthreads):
+ tname = 'PlugT-{0:02}'.format(num)
+ thread = PlugThread(tname, (buf_q, work_q, lock))
+ thread.start()
+ if mac_gui:
+ rthread = RefreshThread(lock)
+ rthread.start()
+
+ while not buf_q.empty() or thr.active_count() != start_cnt:
+ try:
+ action, name, msg = buf_q.get(True, 0.25)
+ buf.write(action, name, ['OK'] if not msg else msg)
+ buf_q.task_done()
+ except queue.Empty:
+ pass
+ except KeyboardInterrupt:
+ G_STOP.set()
+
+ if mac_gui:
+ rthread.stop()
+ rthread.join()
+
+main()
+EOF
+endfunction
+
+function! s:update_ruby()
+ ruby << EOF
+ module PlugStream
+ SEP = ["\r", "\n", nil]
+ def get_line
+ buffer = ''
+ loop do
+ char = readchar rescue return
+ if SEP.include? char.chr
+ buffer << $/
+ break
+ else
+ buffer << char
+ end
+ end
+ buffer
+ end
+ end unless defined?(PlugStream)
+
+ def esc arg
+ %["#{arg.gsub('"', '\"')}"]
+ end
+
+ def killall pid
+ pids = [pid]
+ if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
+ pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
+ else
+ unless `which pgrep 2> /dev/null`.empty?
+ children = pids
+ until children.empty?
+ children = children.map { |pid|
+ `pgrep -P #{pid}`.lines.map { |l| l.chomp }
+ }.flatten
+ pids += children
+ end
+ end
+ pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
+ end
+ end
+
+ def compare_git_uri a, b
+ regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
+ regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
+ end
+
+ require 'thread'
+ require 'fileutils'
+ require 'timeout'
+ running = true
+ iswin = VIM::evaluate('s:is_win').to_i == 1
+ pull = VIM::evaluate('s:update.pull').to_i == 1
+ base = VIM::evaluate('g:plug_home')
+ all = VIM::evaluate('s:update.todo')
+ limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
+ tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
+ nthr = VIM::evaluate('s:update.threads').to_i
+ maxy = VIM::evaluate('winheight(".")').to_i
+ vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
+ cd = iswin ? 'cd /d' : 'cd'
+ tot = VIM::evaluate('len(s:update.todo)') || 0
+ bar = ''
+ skip = 'Already installed'
+ mtx = Mutex.new
+ take1 = proc { mtx.synchronize { running && all.shift } }
+ logh = proc {
+ cnt = bar.length
+ $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
+ $curbuf[2] = '[' + bar.ljust(tot) + ']'
+ VIM::command('normal! 2G')
+ VIM::command('redraw')
+ }
+ where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
+ log = proc { |name, result, type|
+ mtx.synchronize do
+ ing = ![true, false].include?(type)
+ bar += type ? '=' : 'x' unless ing
+ b = case type
+ when :install then '+' when :update then '*'
+ when true, nil then '-' else
+ VIM::command("call add(s:update.errors, '#{name}')")
+ 'x'
+ end
+ result =
+ if type || type.nil?
+ ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
+ elsif result =~ /^Interrupted|^Timeout/
+ ["#{b} #{name}: #{result}"]
+ else
+ ["#{b} #{name}"] + result.lines.map { |l| " " << l }
+ end
+ if lnum = where.call(name)
+ $curbuf.delete lnum
+ lnum = 4 if ing && lnum > maxy
+ end
+ result.each_with_index do |line, offset|
+ $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
+ end
+ logh.call
+ end
+ }
+ bt = proc { |cmd, name, type, cleanup|
+ tried = timeout = 0
+ begin
+ tried += 1
+ timeout += limit
+ fd = nil
+ data = ''
+ if iswin
+ Timeout::timeout(timeout) do
+ tmp = VIM::evaluate('tempname()')
+ system("(#{cmd}) > #{tmp}")
+ data = File.read(tmp).chomp
+ File.unlink tmp rescue nil
+ end
+ else
+ fd = IO.popen(cmd).extend(PlugStream)
+ first_line = true
+ log_prob = 1.0 / nthr
+ while line = Timeout::timeout(timeout) { fd.get_line }
+ data << line
+ log.call name, line.chomp, type if name && (first_line || rand < log_prob)
+ first_line = false
+ end
+ fd.close
+ end
+ [$? == 0, data.chomp]
+ rescue Timeout::Error, Interrupt => e
+ if fd && !fd.closed?
+ killall fd.pid
+ fd.close
+ end
+ cleanup.call if cleanup
+ if e.is_a?(Timeout::Error) && tried < tries
+ 3.downto(1) do |countdown|
+ s = countdown > 1 ? 's' : ''
+ log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
+ sleep 1
+ end
+ log.call name, 'Retrying ...', type
+ retry
+ end
+ [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
+ end
+ }
+ main = Thread.current
+ threads = []
+ watcher = Thread.new {
+ if vim7
+ while VIM::evaluate('getchar(1)')
+ sleep 0.1
+ end
+ else
+ require 'io/console' # >= Ruby 1.9
+ nil until IO.console.getch == 3.chr
+ end
+ mtx.synchronize do
+ running = false
+ threads.each { |t| t.raise Interrupt } unless vim7
+ end
+ threads.each { |t| t.join rescue nil }
+ main.kill
+ }
+ refresh = Thread.new {
+ while true
+ mtx.synchronize do
+ break unless running
+ VIM::command('noautocmd normal! a')
+ end
+ sleep 0.2
+ end
+ } if VIM::evaluate('s:mac_gui') == 1
+
+ clone_opt = VIM::evaluate('s:clone_opt').join(' ')
+ progress = VIM::evaluate('s:progress_opt(1)')
+ nthr.times do
+ mtx.synchronize do
+ threads << Thread.new {
+ while pair = take1.call
+ name = pair.first
+ dir, uri, tag = pair.last.values_at *%w[dir uri tag]
+ exists = File.directory? dir
+ ok, result =
+ if exists
+ chdir = "#{cd} #{iswin ? dir : esc(dir)}"
+ ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
+ current_uri = data.lines.to_a.last
+ if !ret
+ if data =~ /^Interrupted|^Timeout/
+ [false, data]
+ else
+ [false, [data.chomp, "PlugClean required."].join($/)]
+ end
+ elsif !compare_git_uri(current_uri, uri)
+ [false, ["Invalid URI: #{current_uri}",
+ "Expected: #{uri}",
+ "PlugClean required."].join($/)]
+ else
+ if pull
+ log.call name, 'Updating ...', :update
+ fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
+ bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
+ else
+ [true, skip]
+ end
+ end
+ else
+ d = esc dir.sub(%r{[\\/]+$}, '')
+ log.call name, 'Installing ...', :install
+ bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
+ FileUtils.rm_rf dir
+ }
+ end
+ mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
+ log.call name, result, ok
+ end
+ } if running
+ end
+ end
+ threads.each { |t| t.join rescue nil }
+ logh.call
+ refresh.kill if refresh
+ watcher.kill
+EOF
+endfunction
+
+function! s:shellesc_cmd(arg, script)
+ let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
+ return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
+endfunction
+
+function! s:shellesc_ps1(arg)
+ return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
+endfunction
+
+function! s:shellesc_sh(arg)
+ return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
+endfunction
+
+" Escape the shell argument based on the shell.
+" Vim and Neovim's shellescape() are insufficient.
+" 1. shellslash determines whether to use single/double quotes.
+" Double-quote escaping is fragile for cmd.exe.
+" 2. It does not work for powershell.
+" 3. It does not work for *sh shells if the command is executed
+" via cmd.exe (ie. cmd.exe /c sh -c command command_args)
+" 4. It does not support batchfile syntax.
+"
+" Accepts an optional dictionary with the following keys:
+" - shell: same as Vim/Neovim 'shell' option.
+" If unset, fallback to 'cmd.exe' on Windows or 'sh'.
+" - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
+function! plug#shellescape(arg, ...)
+ if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
+ return a:arg
+ endif
+ let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
+ let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
+ let script = get(opts, 'script', 1)
+ if shell =~# 'cmd\(\.exe\)\?$'
+ return s:shellesc_cmd(a:arg, script)
+ elseif shell =~# 'powershell\(\.exe\)\?$' || shell =~# 'pwsh$'
+ return s:shellesc_ps1(a:arg)
+ endif
+ return s:shellesc_sh(a:arg)
+endfunction
+
+function! s:glob_dir(path)
+ return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
+endfunction
+
+function! s:progress_bar(line, bar, total)
+ call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
+endfunction
+
+function! s:compare_git_uri(a, b)
+ " See `git help clone'
+ " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
+ " [git@] github.com[:port] : junegunn/vim-plug [.git]
+ " file:// / junegunn/vim-plug [/]
+ " / junegunn/vim-plug [/]
+ let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
+ let ma = matchlist(a:a, pat)
+ let mb = matchlist(a:b, pat)
+ return ma[1:2] ==# mb[1:2]
+endfunction
+
+function! s:format_message(bullet, name, message)
+ if a:bullet != 'x'
+ return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
+ else
+ let lines = map(s:lines(a:message), '" ".v:val')
+ return extend([printf('x %s:', a:name)], lines)
+ endif
+endfunction
+
+function! s:with_cd(cmd, dir, ...)
+ let script = a:0 > 0 ? a:1 : 1
+ return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
+endfunction
+
+function! s:system(cmd, ...)
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(1)
+ if type(a:cmd) == s:TYPE.list
+ " Neovim's system() supports list argument to bypass the shell
+ " but it cannot set the working directory for the command.
+ " Assume that the command does not rely on the shell.
+ if has('nvim') && a:0 == 0
+ return system(a:cmd)
+ endif
+ let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
+ if &shell =~# 'powershell\(\.exe\)\?$'
+ let cmd = '& ' . cmd
+ endif
+ else
+ let cmd = a:cmd
+ endif
+ if a:0 > 0
+ let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
+ endif
+ if s:is_win && type(a:cmd) != s:TYPE.list
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ return system(cmd)
+ finally
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+endfunction
+
+function! s:system_chomp(...)
+ let ret = call('s:system', a:000)
+ return v:shell_error ? '' : substitute(ret, '\n$', '', '')
+endfunction
+
+function! s:git_validate(spec, check_branch)
+ let err = ''
+ if isdirectory(a:spec.dir)
+ let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
+ let remote = result[-1]
+ if empty(remote)
+ let err = join([remote, 'PlugClean required.'], "\n")
+ elseif !s:compare_git_uri(remote, a:spec.uri)
+ let err = join(['Invalid URI: '.remote,
+ \ 'Expected: '.a:spec.uri,
+ \ 'PlugClean required.'], "\n")
+ elseif a:check_branch && has_key(a:spec, 'commit')
+ let sha = s:git_revision(a:spec.dir)
+ if empty(sha)
+ let err = join(add(result, 'PlugClean required.'), "\n")
+ elseif !s:hash_match(sha, a:spec.commit)
+ let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
+ \ a:spec.commit[:6], sha[:6]),
+ \ 'PlugUpdate required.'], "\n")
+ endif
+ elseif a:check_branch
+ let current_branch = result[0]
+ " Check tag
+ let origin_branch = s:git_origin_branch(a:spec)
+ if has_key(a:spec, 'tag')
+ let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
+ if a:spec.tag !=# tag && a:spec.tag !~ '\*'
+ let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
+ \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
+ endif
+ " Check branch
+ elseif origin_branch !=# current_branch
+ let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
+ \ current_branch, origin_branch)
+ endif
+ if empty(err)
+ let [ahead, behind] = split(s:lastline(s:system([
+ \ 'git', 'rev-list', '--count', '--left-right',
+ \ printf('HEAD...origin/%s', origin_branch)
+ \ ], a:spec.dir)), '\t')
+ if !v:shell_error && ahead
+ if behind
+ " Only mention PlugClean if diverged, otherwise it's likely to be
+ " pushable (and probably not that messed up).
+ let err = printf(
+ \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
+ \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
+ else
+ let err = printf("Ahead of origin/%s by %d commit(s).\n"
+ \ .'Cannot update until local changes are pushed.',
+ \ origin_branch, ahead)
+ endif
+ endif
+ endif
+ endif
+ else
+ let err = 'Not found'
+ endif
+ return [err, err =~# 'PlugClean']
+endfunction
+
+function! s:rm_rf(dir)
+ if isdirectory(a:dir)
+ return s:system(s:is_win
+ \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
+ \ : ['rm', '-rf', a:dir])
+ endif
+endfunction
+
+function! s:clean(force)
+ call s:prepare()
+ call append(0, 'Searching for invalid plugins in '.g:plug_home)
+ call append(1, '')
+
+ " List of valid directories
+ let dirs = []
+ let errs = {}
+ let [cnt, total] = [0, len(g:plugs)]
+ for [name, spec] in items(g:plugs)
+ if !s:is_managed(name)
+ call add(dirs, spec.dir)
+ else
+ let [err, clean] = s:git_validate(spec, 1)
+ if clean
+ let errs[spec.dir] = s:lines(err)[0]
+ else
+ call add(dirs, spec.dir)
+ endif
+ endif
+ let cnt += 1
+ call s:progress_bar(2, repeat('=', cnt), total)
+ normal! 2G
+ redraw
+ endfor
+
+ let allowed = {}
+ for dir in dirs
+ let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
+ let allowed[dir] = 1
+ for child in s:glob_dir(dir)
+ let allowed[child] = 1
+ endfor
+ endfor
+
+ let todo = []
+ let found = sort(s:glob_dir(g:plug_home))
+ while !empty(found)
+ let f = remove(found, 0)
+ if !has_key(allowed, f) && isdirectory(f)
+ call add(todo, f)
+ call append(line('$'), '- ' . f)
+ if has_key(errs, f)
+ call append(line('$'), ' ' . errs[f])
+ endif
+ let found = filter(found, 'stridx(v:val, f) != 0')
+ end
+ endwhile
+
+ 4
+ redraw
+ if empty(todo)
+ call append(line('$'), 'Already clean.')
+ else
+ let s:clean_count = 0
+ call append(3, ['Directories to delete:', ''])
+ redraw!
+ if a:force || s:ask_no_interrupt('Delete all directories?')
+ call s:delete([6, line('$')], 1)
+ else
+ call setline(4, 'Cancelled.')
+ nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
+ nmap <silent> <buffer> dd d_
+ xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
+ echo 'Delete the lines (d{motion}) to delete the corresponding directories'
+ endif
+ endif
+ 4
+ setlocal nomodifiable
+endfunction
+
+function! s:delete_op(type, ...)
+ call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
+endfunction
+
+function! s:delete(range, force)
+ let [l1, l2] = a:range
+ let force = a:force
+ let err_count = 0
+ while l1 <= l2
+ let line = getline(l1)
+ if line =~ '^- ' && isdirectory(line[2:])
+ execute l1
+ redraw!
+ let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
+ let force = force || answer > 1
+ if answer
+ let err = s:rm_rf(line[2:])
+ setlocal modifiable
+ if empty(err)
+ call setline(l1, '~'.line[1:])
+ let s:clean_count += 1
+ else
+ delete _
+ call append(l1 - 1, s:format_message('x', line[1:], err))
+ let l2 += len(s:lines(err))
+ let err_count += 1
+ endif
+ let msg = printf('Removed %d directories.', s:clean_count)
+ if err_count > 0
+ let msg .= printf(' Failed to remove %d directories.', err_count)
+ endif
+ call setline(4, msg)
+ setlocal nomodifiable
+ endif
+ endif
+ let l1 += 1
+ endwhile
+endfunction
+
+function! s:upgrade()
+ echo 'Downloading the latest version of vim-plug'
+ redraw
+ let tmp = s:plug_tempname()
+ let new = tmp . '/plug.vim'
+
+ try
+ let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
+ if v:shell_error
+ return s:err('Error upgrading vim-plug: '. out)
+ endif
+
+ if readfile(s:me) ==# readfile(new)
+ echo 'vim-plug is already up-to-date'
+ return 0
+ else
+ call rename(s:me, s:me . '.old')
+ call rename(new, s:me)
+ unlet g:loaded_plug
+ echo 'vim-plug has been upgraded'
+ return 1
+ endif
+ finally
+ silent! call s:rm_rf(tmp)
+ endtry
+endfunction
+
+function! s:upgrade_specs()
+ for spec in values(g:plugs)
+ let spec.frozen = get(spec, 'frozen', 0)
+ endfor
+endfunction
+
+function! s:status()
+ call s:prepare()
+ call append(0, 'Checking plugins')
+ call append(1, '')
+
+ let ecnt = 0
+ let unloaded = 0
+ let [cnt, total] = [0, len(g:plugs)]
+ for [name, spec] in items(g:plugs)
+ let is_dir = isdirectory(spec.dir)
+ if has_key(spec, 'uri')
+ if is_dir
+ let [err, _] = s:git_validate(spec, 1)
+ let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
+ else
+ let [valid, msg] = [0, 'Not found. Try PlugInstall.']
+ endif
+ else
+ if is_dir
+ let [valid, msg] = [1, 'OK']
+ else
+ let [valid, msg] = [0, 'Not found.']
+ endif
+ endif
+ let cnt += 1
+ let ecnt += !valid
+ " `s:loaded` entry can be missing if PlugUpgraded
+ if is_dir && get(s:loaded, name, -1) == 0
+ let unloaded = 1
+ let msg .= ' (not loaded)'
+ endif
+ call s:progress_bar(2, repeat('=', cnt), total)
+ call append(3, s:format_message(valid ? '-' : 'x', name, msg))
+ normal! 2G
+ redraw
+ endfor
+ call setline(1, 'Finished. '.ecnt.' error(s).')
+ normal! gg
+ setlocal nomodifiable
+ if unloaded
+ echo "Press 'L' on each line to load plugin, or 'U' to update"
+ nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
+ xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
+ end
+endfunction
+
+function! s:extract_name(str, prefix, suffix)
+ return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
+endfunction
+
+function! s:status_load(lnum)
+ let line = getline(a:lnum)
+ let name = s:extract_name(line, '-', '(not loaded)')
+ if !empty(name)
+ call plug#load(name)
+ setlocal modifiable
+ call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
+ setlocal nomodifiable
+ endif
+endfunction
+
+function! s:status_update() range
+ let lines = getline(a:firstline, a:lastline)
+ let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
+ if !empty(names)
+ echo
+ execute 'PlugUpdate' join(names)
+ endif
+endfunction
+
+function! s:is_preview_window_open()
+ silent! wincmd P
+ if &previewwindow
+ wincmd p
+ return 1
+ endif
+endfunction
+
+function! s:find_name(lnum)
+ for lnum in reverse(range(1, a:lnum))
+ let line = getline(lnum)
+ if empty(line)
+ return ''
+ endif
+ let name = s:extract_name(line, '-', '')
+ if !empty(name)
+ return name
+ endif
+ endfor
+ return ''
+endfunction
+
+function! s:preview_commit()
+ if b:plug_preview < 0
+ let b:plug_preview = !s:is_preview_window_open()
+ endif
+
+ let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
+ if empty(sha)
+ return
+ endif
+
+ let name = s:find_name(line('.'))
+ if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
+ return
+ endif
+
+ if exists('g:plug_pwindow') && !s:is_preview_window_open()
+ execute g:plug_pwindow
+ execute 'e' sha
+ else
+ execute 'pedit' sha
+ wincmd P
+ endif
+ setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(1)
+ let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
+ if s:is_win
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ execute 'silent %!' cmd
+ finally
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+ setlocal nomodifiable
+ nnoremap <silent> <buffer> q :q<cr>
+ wincmd p
+endfunction
+
+function! s:section(flags)
+ call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
+endfunction
+
+function! s:format_git_log(line)
+ let indent = ' '
+ let tokens = split(a:line, nr2char(1))
+ if len(tokens) != 5
+ return indent.substitute(a:line, '\s*$', '', '')
+ endif
+ let [graph, sha, refs, subject, date] = tokens
+ let tag = matchstr(refs, 'tag: [^,)]\+')
+ let tag = empty(tag) ? ' ' : ' ('.tag.') '
+ return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
+endfunction
+
+function! s:append_ul(lnum, text)
+ call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
+endfunction
+
+function! s:diff()
+ call s:prepare()
+ call append(0, ['Collecting changes ...', ''])
+ let cnts = [0, 0]
+ let bar = ''
+ let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
+ call s:progress_bar(2, bar, len(total))
+ for origin in [1, 0]
+ let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
+ if empty(plugs)
+ continue
+ endif
+ call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
+ for [k, v] in plugs
+ let branch = s:git_origin_branch(v)
+ if len(branch)
+ let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
+ let cmd = ['git', 'log', '--graph', '--color=never']
+ if s:git_version_requirement(2, 10, 0)
+ call add(cmd, '--no-show-signature')
+ endif
+ call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
+ if has_key(v, 'rtp')
+ call extend(cmd, ['--', v.rtp])
+ endif
+ let diff = s:system_chomp(cmd, v.dir)
+ if !empty(diff)
+ let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
+ call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
+ let cnts[origin] += 1
+ endif
+ endif
+ let bar .= '='
+ call s:progress_bar(2, bar, len(total))
+ normal! 2G
+ redraw
+ endfor
+ if !cnts[origin]
+ call append(5, ['', 'N/A'])
+ endif
+ endfor
+ call setline(1, printf('%d plugin(s) updated.', cnts[0])
+ \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
+
+ if cnts[0] || cnts[1]
+ nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
+ if empty(maparg("\<cr>", 'n'))
+ nmap <buffer> <cr> <plug>(plug-preview)
+ endif
+ if empty(maparg('o', 'n'))
+ nmap <buffer> o <plug>(plug-preview)
+ endif
+ endif
+ if cnts[0]
+ nnoremap <silent> <buffer> X :call <SID>revert()<cr>
+ echo "Press 'X' on each block to revert the update"
+ endif
+ normal! gg
+ setlocal nomodifiable
+endfunction
+
+function! s:revert()
+ if search('^Pending updates', 'bnW')
+ return
+ endif
+
+ let name = s:find_name(line('.'))
+ if empty(name) || !has_key(g:plugs, name) ||
+ \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
+ return
+ endif
+
+ call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
+ setlocal modifiable
+ normal! "_dap
+ setlocal nomodifiable
+ echo 'Reverted'
+endfunction
+
+function! s:snapshot(force, ...) abort
+ call s:prepare()
+ setf vim
+ call append(0, ['" Generated by vim-plug',
+ \ '" '.strftime("%c"),
+ \ '" :source this file in vim to restore the snapshot',
+ \ '" or execute: vim -S snapshot.vim',
+ \ '', '', 'PlugUpdate!'])
+ 1
+ let anchor = line('$') - 3
+ let names = sort(keys(filter(copy(g:plugs),
+ \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
+ for name in reverse(names)
+ let sha = s:git_revision(g:plugs[name].dir)
+ if !empty(sha)
+ call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
+ redraw
+ endif
+ endfor
+
+ if a:0 > 0
+ let fn = s:plug_expand(a:1)
+ if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
+ return
+ endif
+ call writefile(getline(1, '$'), fn)
+ echo 'Saved as '.a:1
+ silent execute 'e' s:esc(fn)
+ setf vim
+ endif
+endfunction
+
+function! s:split_rtp()
+ return split(&rtp, '\\\@<!,')
+endfunction
+
+let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
+let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
+
+if exists('g:plugs')
+ let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
+ call s:upgrade_specs()
+ call s:define_commands()
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/stow/vim/dot-vim/autoload/plug.vim.old b/stow/vim/dot-vim/autoload/plug.vim.old
new file mode 100644
index 0000000..c1657f2
--- /dev/null
+++ b/stow/vim/dot-vim/autoload/plug.vim.old
@@ -0,0 +1,2788 @@
+" vim-plug: Vim plugin manager
+" ============================
+"
+" Download plug.vim and put it in ~/.vim/autoload
+"
+" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
+" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
+"
+" Edit your .vimrc
+"
+" call plug#begin('~/.vim/plugged')
+"
+" " Make sure you use single quotes
+"
+" " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
+" Plug 'junegunn/vim-easy-align'
+"
+" " Any valid git URL is allowed
+" Plug 'https://github.com/junegunn/vim-github-dashboard.git'
+"
+" " Multiple Plug commands can be written in a single line using | separators
+" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
+"
+" " On-demand loading
+" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
+" Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
+"
+" " Using a non-default branch
+" Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
+"
+" " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
+" Plug 'fatih/vim-go', { 'tag': '*' }
+"
+" " Plugin options
+" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
+"
+" " Plugin outside ~/.vim/plugged with post-update hook
+" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
+"
+" " Unmanaged plugin (manually installed and updated)
+" Plug '~/my-prototype-plugin'
+"
+" " Initialize plugin system
+" call plug#end()
+"
+" Then reload .vimrc and :PlugInstall to install plugins.
+"
+" Plug options:
+"
+"| Option | Description |
+"| ----------------------- | ------------------------------------------------ |
+"| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use |
+"| `rtp` | Subdirectory that contains Vim plugin |
+"| `dir` | Custom directory for the plugin |
+"| `as` | Use different name for the plugin |
+"| `do` | Post-update hook (string or funcref) |
+"| `on` | On-demand loading: Commands or `<Plug>`-mappings |
+"| `for` | On-demand loading: File types |
+"| `frozen` | Do not update unless explicitly specified |
+"
+" More information: https://github.com/junegunn/vim-plug
+"
+"
+" Copyright (c) 2017 Junegunn Choi
+"
+" MIT License
+"
+" 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.
+
+if exists('g:loaded_plug')
+ finish
+endif
+let g:loaded_plug = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
+let s:plug_tab = get(s:, 'plug_tab', -1)
+let s:plug_buf = get(s:, 'plug_buf', -1)
+let s:mac_gui = has('gui_macvim') && has('gui_running')
+let s:is_win = has('win32')
+let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
+let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
+if s:is_win && &shellslash
+ set noshellslash
+ let s:me = resolve(expand('<sfile>:p'))
+ set shellslash
+else
+ let s:me = resolve(expand('<sfile>:p'))
+endif
+let s:base_spec = { 'branch': '', 'frozen': 0 }
+let s:TYPE = {
+\ 'string': type(''),
+\ 'list': type([]),
+\ 'dict': type({}),
+\ 'funcref': type(function('call'))
+\ }
+let s:loaded = get(s:, 'loaded', {})
+let s:triggers = get(s:, 'triggers', {})
+
+function! s:isabsolute(dir) abort
+ return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
+endfunction
+
+function! s:git_dir(dir) abort
+ let gitdir = s:trim(a:dir) . '/.git'
+ if isdirectory(gitdir)
+ return gitdir
+ endif
+ if !filereadable(gitdir)
+ return ''
+ endif
+ let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
+ if len(gitdir) && !s:isabsolute(gitdir)
+ let gitdir = a:dir . '/' . gitdir
+ endif
+ return isdirectory(gitdir) ? gitdir : ''
+endfunction
+
+function! s:git_origin_url(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let config = gitdir . '/config'
+ if empty(gitdir) || !filereadable(config)
+ return ''
+ endif
+ return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
+endfunction
+
+function! s:git_revision(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let head = gitdir . '/HEAD'
+ if empty(gitdir) || !filereadable(head)
+ return ''
+ endif
+
+ let line = get(readfile(head), 0, '')
+ let ref = matchstr(line, '^ref: \zs.*')
+ if empty(ref)
+ return line
+ endif
+
+ if filereadable(gitdir . '/' . ref)
+ return get(readfile(gitdir . '/' . ref), 0, '')
+ endif
+
+ if filereadable(gitdir . '/packed-refs')
+ for line in readfile(gitdir . '/packed-refs')
+ if line =~# ' ' . ref
+ return matchstr(line, '^[0-9a-f]*')
+ endif
+ endfor
+ endif
+
+ return ''
+endfunction
+
+function! s:git_local_branch(dir) abort
+ let gitdir = s:git_dir(a:dir)
+ let head = gitdir . '/HEAD'
+ if empty(gitdir) || !filereadable(head)
+ return ''
+ endif
+ let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
+ return len(branch) ? branch : 'HEAD'
+endfunction
+
+function! s:git_origin_branch(spec)
+ if len(a:spec.branch)
+ return a:spec.branch
+ endif
+
+ " The file may not be present if this is a local repository
+ let gitdir = s:git_dir(a:spec.dir)
+ let origin_head = gitdir.'/refs/remotes/origin/HEAD'
+ if len(gitdir) && filereadable(origin_head)
+ return matchstr(get(readfile(origin_head), 0, ''),
+ \ '^ref: refs/remotes/origin/\zs.*')
+ endif
+
+ " The command may not return the name of a branch in detached HEAD state
+ let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
+ return v:shell_error ? '' : result[-1]
+endfunction
+
+if s:is_win
+ function! s:plug_call(fn, ...)
+ let shellslash = &shellslash
+ try
+ set noshellslash
+ return call(a:fn, a:000)
+ finally
+ let &shellslash = shellslash
+ endtry
+ endfunction
+else
+ function! s:plug_call(fn, ...)
+ return call(a:fn, a:000)
+ endfunction
+endif
+
+function! s:plug_getcwd()
+ return s:plug_call('getcwd')
+endfunction
+
+function! s:plug_fnamemodify(fname, mods)
+ return s:plug_call('fnamemodify', a:fname, a:mods)
+endfunction
+
+function! s:plug_expand(fmt)
+ return s:plug_call('expand', a:fmt, 1)
+endfunction
+
+function! s:plug_tempname()
+ return s:plug_call('tempname')
+endfunction
+
+function! plug#begin(...)
+ if a:0 > 0
+ let s:plug_home_org = a:1
+ let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
+ elseif exists('g:plug_home')
+ let home = s:path(g:plug_home)
+ elseif !empty(&rtp)
+ let home = s:path(split(&rtp, ',')[0]) . '/plugged'
+ else
+ return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
+ endif
+ if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
+ return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
+ endif
+
+ let g:plug_home = home
+ let g:plugs = {}
+ let g:plugs_order = []
+ let s:triggers = {}
+
+ call s:define_commands()
+ return 1
+endfunction
+
+function! s:define_commands()
+ command! -nargs=+ -bar Plug call plug#(<args>)
+ if !executable('git')
+ return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
+ endif
+ if has('win32')
+ \ && &shellslash
+ \ && (&shell =~# 'cmd\(\.exe\)\?$' || &shell =~# 'powershell\(\.exe\)\?$')
+ return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
+ endif
+ if !has('nvim')
+ \ && (has('win32') || has('win32unix'))
+ \ && !has('multi_byte')
+ return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
+ endif
+ command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(<bang>0, [<f-args>])
+ command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(<bang>0, [<f-args>])
+ command! -nargs=0 -bar -bang PlugClean call s:clean(<bang>0)
+ command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
+ command! -nargs=0 -bar PlugStatus call s:status()
+ command! -nargs=0 -bar PlugDiff call s:diff()
+ command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(<bang>0, <f-args>)
+endfunction
+
+function! s:to_a(v)
+ return type(a:v) == s:TYPE.list ? a:v : [a:v]
+endfunction
+
+function! s:to_s(v)
+ return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
+endfunction
+
+function! s:glob(from, pattern)
+ return s:lines(globpath(a:from, a:pattern))
+endfunction
+
+function! s:source(from, ...)
+ let found = 0
+ for pattern in a:000
+ for vim in s:glob(a:from, pattern)
+ execute 'source' s:esc(vim)
+ let found = 1
+ endfor
+ endfor
+ return found
+endfunction
+
+function! s:assoc(dict, key, val)
+ let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
+endfunction
+
+function! s:ask(message, ...)
+ call inputsave()
+ echohl WarningMsg
+ let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
+ echohl None
+ call inputrestore()
+ echo "\r"
+ return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
+endfunction
+
+function! s:ask_no_interrupt(...)
+ try
+ return call('s:ask', a:000)
+ catch
+ return 0
+ endtry
+endfunction
+
+function! s:lazy(plug, opt)
+ return has_key(a:plug, a:opt) &&
+ \ (empty(s:to_a(a:plug[a:opt])) ||
+ \ !isdirectory(a:plug.dir) ||
+ \ len(s:glob(s:rtp(a:plug), 'plugin')) ||
+ \ len(s:glob(s:rtp(a:plug), 'after/plugin')))
+endfunction
+
+function! plug#end()
+ if !exists('g:plugs')
+ return s:err('plug#end() called without calling plug#begin() first')
+ endif
+
+ if exists('#PlugLOD')
+ augroup PlugLOD
+ autocmd!
+ augroup END
+ augroup! PlugLOD
+ endif
+ let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
+
+ if exists('g:did_load_filetypes')
+ filetype off
+ endif
+ for name in g:plugs_order
+ if !has_key(g:plugs, name)
+ continue
+ endif
+ let plug = g:plugs[name]
+ if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
+ let s:loaded[name] = 1
+ continue
+ endif
+
+ if has_key(plug, 'on')
+ let s:triggers[name] = { 'map': [], 'cmd': [] }
+ for cmd in s:to_a(plug.on)
+ if cmd =~? '^<Plug>.\+'
+ if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
+ call s:assoc(lod.map, cmd, name)
+ endif
+ call add(s:triggers[name].map, cmd)
+ elseif cmd =~# '^[A-Z]'
+ let cmd = substitute(cmd, '!*$', '', '')
+ if exists(':'.cmd) != 2
+ call s:assoc(lod.cmd, cmd, name)
+ endif
+ call add(s:triggers[name].cmd, cmd)
+ else
+ call s:err('Invalid `on` option: '.cmd.
+ \ '. Should start with an uppercase letter or `<Plug>`.')
+ endif
+ endfor
+ endif
+
+ if has_key(plug, 'for')
+ let types = s:to_a(plug.for)
+ if !empty(types)
+ augroup filetypedetect
+ call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
+ augroup END
+ endif
+ for type in types
+ call s:assoc(lod.ft, type, name)
+ endfor
+ endif
+ endfor
+
+ for [cmd, names] in items(lod.cmd)
+ execute printf(
+ \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
+ \ cmd, string(cmd), string(names))
+ endfor
+
+ for [map, names] in items(lod.map)
+ for [mode, map_prefix, key_prefix] in
+ \ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
+ execute printf(
+ \ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, %s, "%s")<CR>',
+ \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
+ endfor
+ endfor
+
+ for [ft, names] in items(lod.ft)
+ augroup PlugLOD
+ execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
+ \ ft, string(ft), string(names))
+ augroup END
+ endfor
+
+ call s:reorg_rtp()
+ filetype plugin indent on
+ if has('vim_starting')
+ if has('syntax') && !exists('g:syntax_on')
+ syntax enable
+ end
+ else
+ call s:reload_plugins()
+ endif
+endfunction
+
+function! s:loaded_names()
+ return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
+endfunction
+
+function! s:load_plugin(spec)
+ call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
+endfunction
+
+function! s:reload_plugins()
+ for name in s:loaded_names()
+ call s:load_plugin(g:plugs[name])
+ endfor
+endfunction
+
+function! s:trim(str)
+ return substitute(a:str, '[\/]\+$', '', '')
+endfunction
+
+function! s:version_requirement(val, min)
+ for idx in range(0, len(a:min) - 1)
+ let v = get(a:val, idx, 0)
+ if v < a:min[idx] | return 0
+ elseif v > a:min[idx] | return 1
+ endif
+ endfor
+ return 1
+endfunction
+
+function! s:git_version_requirement(...)
+ if !exists('s:git_version')
+ let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
+ endif
+ return s:version_requirement(s:git_version, a:000)
+endfunction
+
+function! s:progress_opt(base)
+ return a:base && !s:is_win &&
+ \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
+endfunction
+
+function! s:rtp(spec)
+ return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
+endfunction
+
+if s:is_win
+ function! s:path(path)
+ return s:trim(substitute(a:path, '/', '\', 'g'))
+ endfunction
+
+ function! s:dirpath(path)
+ return s:path(a:path) . '\'
+ endfunction
+
+ function! s:is_local_plug(repo)
+ return a:repo =~? '^[a-z]:\|^[%~]'
+ endfunction
+
+ " Copied from fzf
+ function! s:wrap_cmds(cmds)
+ let cmds = [
+ \ '@echo off',
+ \ 'setlocal enabledelayedexpansion']
+ \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
+ \ + ['endlocal']
+ if has('iconv')
+ if !exists('s:codepage')
+ let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
+ endif
+ return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
+ endif
+ return map(cmds, 'v:val."\r"')
+ endfunction
+
+ function! s:batchfile(cmd)
+ let batchfile = s:plug_tempname().'.bat'
+ call writefile(s:wrap_cmds(a:cmd), batchfile)
+ let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
+ if &shell =~# 'powershell\(\.exe\)\?$'
+ let cmd = '& ' . cmd
+ endif
+ return [batchfile, cmd]
+ endfunction
+else
+ function! s:path(path)
+ return s:trim(a:path)
+ endfunction
+
+ function! s:dirpath(path)
+ return substitute(a:path, '[/\\]*$', '/', '')
+ endfunction
+
+ function! s:is_local_plug(repo)
+ return a:repo[0] =~ '[/$~]'
+ endfunction
+endif
+
+function! s:err(msg)
+ echohl ErrorMsg
+ echom '[vim-plug] '.a:msg
+ echohl None
+endfunction
+
+function! s:warn(cmd, msg)
+ echohl WarningMsg
+ execute a:cmd 'a:msg'
+ echohl None
+endfunction
+
+function! s:esc(path)
+ return escape(a:path, ' ')
+endfunction
+
+function! s:escrtp(path)
+ return escape(a:path, ' ,')
+endfunction
+
+function! s:remove_rtp()
+ for name in s:loaded_names()
+ let rtp = s:rtp(g:plugs[name])
+ execute 'set rtp-='.s:escrtp(rtp)
+ let after = globpath(rtp, 'after')
+ if isdirectory(after)
+ execute 'set rtp-='.s:escrtp(after)
+ endif
+ endfor
+endfunction
+
+function! s:reorg_rtp()
+ if !empty(s:first_rtp)
+ execute 'set rtp-='.s:first_rtp
+ execute 'set rtp-='.s:last_rtp
+ endif
+
+ " &rtp is modified from outside
+ if exists('s:prtp') && s:prtp !=# &rtp
+ call s:remove_rtp()
+ unlet! s:middle
+ endif
+
+ let s:middle = get(s:, 'middle', &rtp)
+ let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
+ let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
+ let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
+ \ . ','.s:middle.','
+ \ . join(map(afters, 'escape(v:val, ",")'), ',')
+ let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
+ let s:prtp = &rtp
+
+ if !empty(s:first_rtp)
+ execute 'set rtp^='.s:first_rtp
+ execute 'set rtp+='.s:last_rtp
+ endif
+endfunction
+
+function! s:doautocmd(...)
+ if exists('#'.join(a:000, '#'))
+ execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '<nomodeline>' : '') join(a:000)
+ endif
+endfunction
+
+function! s:dobufread(names)
+ for name in a:names
+ let path = s:rtp(g:plugs[name])
+ for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
+ if len(finddir(dir, path))
+ if exists('#BufRead')
+ doautocmd BufRead
+ endif
+ return
+ endif
+ endfor
+ endfor
+endfunction
+
+function! plug#load(...)
+ if a:0 == 0
+ return s:err('Argument missing: plugin name(s) required')
+ endif
+ if !exists('g:plugs')
+ return s:err('plug#begin was not called')
+ endif
+ let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
+ let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
+ if !empty(unknowns)
+ let s = len(unknowns) > 1 ? 's' : ''
+ return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
+ end
+ let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
+ if !empty(unloaded)
+ for name in unloaded
+ call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ endfor
+ call s:dobufread(unloaded)
+ return 1
+ end
+ return 0
+endfunction
+
+function! s:remove_triggers(name)
+ if !has_key(s:triggers, a:name)
+ return
+ endif
+ for cmd in s:triggers[a:name].cmd
+ execute 'silent! delc' cmd
+ endfor
+ for map in s:triggers[a:name].map
+ execute 'silent! unmap' map
+ execute 'silent! iunmap' map
+ endfor
+ call remove(s:triggers, a:name)
+endfunction
+
+function! s:lod(names, types, ...)
+ for name in a:names
+ call s:remove_triggers(name)
+ let s:loaded[name] = 1
+ endfor
+ call s:reorg_rtp()
+
+ for name in a:names
+ let rtp = s:rtp(g:plugs[name])
+ for dir in a:types
+ call s:source(rtp, dir.'/**/*.vim')
+ endfor
+ if a:0
+ if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
+ execute 'runtime' a:1
+ endif
+ call s:source(rtp, a:2)
+ endif
+ call s:doautocmd('User', name)
+ endfor
+endfunction
+
+function! s:lod_ft(pat, names)
+ let syn = 'syntax/'.a:pat.'.vim'
+ call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
+ execute 'autocmd! PlugLOD FileType' a:pat
+ call s:doautocmd('filetypeplugin', 'FileType')
+ call s:doautocmd('filetypeindent', 'FileType')
+endfunction
+
+function! s:lod_cmd(cmd, bang, l1, l2, args, names)
+ call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ call s:dobufread(a:names)
+ execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
+endfunction
+
+function! s:lod_map(map, names, with_prefix, prefix)
+ call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
+ call s:dobufread(a:names)
+ let extra = ''
+ while 1
+ let c = getchar(0)
+ if c == 0
+ break
+ endif
+ let extra .= nr2char(c)
+ endwhile
+
+ if a:with_prefix
+ let prefix = v:count ? v:count : ''
+ let prefix .= '"'.v:register.a:prefix
+ if mode(1) == 'no'
+ if v:operator == 'c'
+ let prefix = "\<esc>" . prefix
+ endif
+ let prefix .= v:operator
+ endif
+ call feedkeys(prefix, 'n')
+ endif
+ call feedkeys(substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
+endfunction
+
+function! plug#(repo, ...)
+ if a:0 > 1
+ return s:err('Invalid number of arguments (1..2)')
+ endif
+
+ try
+ let repo = s:trim(a:repo)
+ let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
+ let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
+ let spec = extend(s:infer_properties(name, repo), opts)
+ if !has_key(g:plugs, name)
+ call add(g:plugs_order, name)
+ endif
+ let g:plugs[name] = spec
+ let s:loaded[name] = get(s:loaded, name, 0)
+ catch
+ return s:err(repo . ' ' . v:exception)
+ endtry
+endfunction
+
+function! s:parse_options(arg)
+ let opts = copy(s:base_spec)
+ let type = type(a:arg)
+ let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
+ if type == s:TYPE.string
+ if empty(a:arg)
+ throw printf(opt_errfmt, 'tag', 'string')
+ endif
+ let opts.tag = a:arg
+ elseif type == s:TYPE.dict
+ for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
+ if has_key(a:arg, opt)
+ \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
+ throw printf(opt_errfmt, opt, 'string')
+ endif
+ endfor
+ for opt in ['on', 'for']
+ if has_key(a:arg, opt)
+ \ && type(a:arg[opt]) != s:TYPE.list
+ \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
+ throw printf(opt_errfmt, opt, 'string or list')
+ endif
+ endfor
+ if has_key(a:arg, 'do')
+ \ && type(a:arg.do) != s:TYPE.funcref
+ \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
+ throw printf(opt_errfmt, 'do', 'string or funcref')
+ endif
+ call extend(opts, a:arg)
+ if has_key(opts, 'dir')
+ let opts.dir = s:dirpath(s:plug_expand(opts.dir))
+ endif
+ else
+ throw 'Invalid argument type (expected: string or dictionary)'
+ endif
+ return opts
+endfunction
+
+function! s:infer_properties(name, repo)
+ let repo = a:repo
+ if s:is_local_plug(repo)
+ return { 'dir': s:dirpath(s:plug_expand(repo)) }
+ else
+ if repo =~ ':'
+ let uri = repo
+ else
+ if repo !~ '/'
+ throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
+ endif
+ let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
+ let uri = printf(fmt, repo)
+ endif
+ return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
+ endif
+endfunction
+
+function! s:install(force, names)
+ call s:update_impl(0, a:force, a:names)
+endfunction
+
+function! s:update(force, names)
+ call s:update_impl(1, a:force, a:names)
+endfunction
+
+function! plug#helptags()
+ if !exists('g:plugs')
+ return s:err('plug#begin was not called')
+ endif
+ for spec in values(g:plugs)
+ let docd = join([s:rtp(spec), 'doc'], '/')
+ if isdirectory(docd)
+ silent! execute 'helptags' s:esc(docd)
+ endif
+ endfor
+ return 1
+endfunction
+
+function! s:syntax()
+ syntax clear
+ syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
+ syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
+ syn match plugNumber /[0-9]\+[0-9.]*/ contained
+ syn match plugBracket /[[\]]/ contained
+ syn match plugX /x/ contained
+ syn match plugDash /^-\{1}\ /
+ syn match plugPlus /^+/
+ syn match plugStar /^*/
+ syn match plugMessage /\(^- \)\@<=.*/
+ syn match plugName /\(^- \)\@<=[^ ]*:/
+ syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
+ syn match plugTag /(tag: [^)]\+)/
+ syn match plugInstall /\(^+ \)\@<=[^:]*/
+ syn match plugUpdate /\(^* \)\@<=[^:]*/
+ syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
+ syn match plugEdge /^ \X\+$/
+ syn match plugEdge /^ \X*/ contained nextgroup=plugSha
+ syn match plugSha /[0-9a-f]\{7,9}/ contained
+ syn match plugRelDate /([^)]*)$/ contained
+ syn match plugNotLoaded /(not loaded)$/
+ syn match plugError /^x.*/
+ syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
+ syn match plugH2 /^.*:\n-\+$/
+ syn match plugH2 /^-\{2,}/
+ syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
+ hi def link plug1 Title
+ hi def link plug2 Repeat
+ hi def link plugH2 Type
+ hi def link plugX Exception
+ hi def link plugBracket Structure
+ hi def link plugNumber Number
+
+ hi def link plugDash Special
+ hi def link plugPlus Constant
+ hi def link plugStar Boolean
+
+ hi def link plugMessage Function
+ hi def link plugName Label
+ hi def link plugInstall Function
+ hi def link plugUpdate Type
+
+ hi def link plugError Error
+ hi def link plugDeleted Ignore
+ hi def link plugRelDate Comment
+ hi def link plugEdge PreProc
+ hi def link plugSha Identifier
+ hi def link plugTag Constant
+
+ hi def link plugNotLoaded Comment
+endfunction
+
+function! s:lpad(str, len)
+ return a:str . repeat(' ', a:len - len(a:str))
+endfunction
+
+function! s:lines(msg)
+ return split(a:msg, "[\r\n]")
+endfunction
+
+function! s:lastline(msg)
+ return get(s:lines(a:msg), -1, '')
+endfunction
+
+function! s:new_window()
+ execute get(g:, 'plug_window', 'vertical topleft new')
+endfunction
+
+function! s:plug_window_exists()
+ let buflist = tabpagebuflist(s:plug_tab)
+ return !empty(buflist) && index(buflist, s:plug_buf) >= 0
+endfunction
+
+function! s:switch_in()
+ if !s:plug_window_exists()
+ return 0
+ endif
+
+ if winbufnr(0) != s:plug_buf
+ let s:pos = [tabpagenr(), winnr(), winsaveview()]
+ execute 'normal!' s:plug_tab.'gt'
+ let winnr = bufwinnr(s:plug_buf)
+ execute winnr.'wincmd w'
+ call add(s:pos, winsaveview())
+ else
+ let s:pos = [winsaveview()]
+ endif
+
+ setlocal modifiable
+ return 1
+endfunction
+
+function! s:switch_out(...)
+ call winrestview(s:pos[-1])
+ setlocal nomodifiable
+ if a:0 > 0
+ execute a:1
+ endif
+
+ if len(s:pos) > 1
+ execute 'normal!' s:pos[0].'gt'
+ execute s:pos[1] 'wincmd w'
+ call winrestview(s:pos[2])
+ endif
+endfunction
+
+function! s:finish_bindings()
+ nnoremap <silent> <buffer> R :call <SID>retry()<cr>
+ nnoremap <silent> <buffer> D :PlugDiff<cr>
+ nnoremap <silent> <buffer> S :PlugStatus<cr>
+ nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
+ xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
+ nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
+ nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
+endfunction
+
+function! s:prepare(...)
+ if empty(s:plug_getcwd())
+ throw 'Invalid current working directory. Cannot proceed.'
+ endif
+
+ for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
+ if exists(evar)
+ throw evar.' detected. Cannot proceed.'
+ endif
+ endfor
+
+ call s:job_abort()
+ if s:switch_in()
+ if b:plug_preview == 1
+ pc
+ endif
+ enew
+ else
+ call s:new_window()
+ endif
+
+ nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>bd<cr>
+ if a:0 == 0
+ call s:finish_bindings()
+ endif
+ let b:plug_preview = -1
+ let s:plug_tab = tabpagenr()
+ let s:plug_buf = winbufnr(0)
+ call s:assign_name()
+
+ for k in ['<cr>', 'L', 'o', 'X', 'd', 'dd']
+ execute 'silent! unmap <buffer>' k
+ endfor
+ setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
+ if exists('+colorcolumn')
+ setlocal colorcolumn=
+ endif
+ setf vim-plug
+ if exists('g:syntax_on')
+ call s:syntax()
+ endif
+endfunction
+
+function! s:assign_name()
+ " Assign buffer name
+ let prefix = '[Plugins]'
+ let name = prefix
+ let idx = 2
+ while bufexists(name)
+ let name = printf('%s (%s)', prefix, idx)
+ let idx = idx + 1
+ endwhile
+ silent! execute 'f' fnameescape(name)
+endfunction
+
+function! s:chsh(swap)
+ let prev = [&shell, &shellcmdflag, &shellredir]
+ if !s:is_win
+ set shell=sh
+ endif
+ if a:swap
+ if &shell =~# 'powershell\(\.exe\)\?$' || &shell =~# 'pwsh$'
+ let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
+ elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
+ set shellredir=>%s\ 2>&1
+ endif
+ endif
+ return prev
+endfunction
+
+function! s:bang(cmd, ...)
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(a:0)
+ " FIXME: Escaping is incomplete. We could use shellescape with eval,
+ " but it won't work on Windows.
+ let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
+ if s:is_win
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
+ execute "normal! :execute g:_plug_bang\<cr>\<cr>"
+ finally
+ unlet g:_plug_bang
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+ return v:shell_error ? 'Exit status: ' . v:shell_error : ''
+endfunction
+
+function! s:regress_bar()
+ let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
+ call s:progress_bar(2, bar, len(bar))
+endfunction
+
+function! s:is_updated(dir)
+ return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
+endfunction
+
+function! s:do(pull, force, todo)
+ for [name, spec] in items(a:todo)
+ if !isdirectory(spec.dir)
+ continue
+ endif
+ let installed = has_key(s:update.new, name)
+ let updated = installed ? 0 :
+ \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
+ if a:force || installed || updated
+ execute 'cd' s:esc(spec.dir)
+ call append(3, '- Post-update hook for '. name .' ... ')
+ let error = ''
+ let type = type(spec.do)
+ if type == s:TYPE.string
+ if spec.do[0] == ':'
+ if !get(s:loaded, name, 0)
+ let s:loaded[name] = 1
+ call s:reorg_rtp()
+ endif
+ call s:load_plugin(spec)
+ try
+ execute spec.do[1:]
+ catch
+ let error = v:exception
+ endtry
+ if !s:plug_window_exists()
+ cd -
+ throw 'Warning: vim-plug was terminated by the post-update hook of '.name
+ endif
+ else
+ let error = s:bang(spec.do)
+ endif
+ elseif type == s:TYPE.funcref
+ try
+ call s:load_plugin(spec)
+ let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
+ call spec.do({ 'name': name, 'status': status, 'force': a:force })
+ catch
+ let error = v:exception
+ endtry
+ else
+ let error = 'Invalid hook type'
+ endif
+ call s:switch_in()
+ call setline(4, empty(error) ? (getline(4) . 'OK')
+ \ : ('x' . getline(4)[1:] . error))
+ if !empty(error)
+ call add(s:update.errors, name)
+ call s:regress_bar()
+ endif
+ cd -
+ endif
+ endfor
+endfunction
+
+function! s:hash_match(a, b)
+ return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
+endfunction
+
+function! s:checkout(spec)
+ let sha = a:spec.commit
+ let output = s:git_revision(a:spec.dir)
+ if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
+ let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
+ let output = s:system(
+ \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
+ endif
+ return output
+endfunction
+
+function! s:finish(pull)
+ let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
+ if new_frozen
+ let s = new_frozen > 1 ? 's' : ''
+ call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
+ endif
+ call append(3, '- Finishing ... ') | 4
+ redraw
+ call plug#helptags()
+ call plug#end()
+ call setline(4, getline(4) . 'Done!')
+ redraw
+ let msgs = []
+ if !empty(s:update.errors)
+ call add(msgs, "Press 'R' to retry.")
+ endif
+ if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
+ \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
+ call add(msgs, "Press 'D' to see the updated changes.")
+ endif
+ echo join(msgs, ' ')
+ call s:finish_bindings()
+endfunction
+
+function! s:retry()
+ if empty(s:update.errors)
+ return
+ endif
+ echo
+ call s:update_impl(s:update.pull, s:update.force,
+ \ extend(copy(s:update.errors), [s:update.threads]))
+endfunction
+
+function! s:is_managed(name)
+ return has_key(g:plugs[a:name], 'uri')
+endfunction
+
+function! s:names(...)
+ return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
+endfunction
+
+function! s:check_ruby()
+ silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
+ if !exists('g:plug_ruby')
+ redraw!
+ return s:warn('echom', 'Warning: Ruby interface is broken')
+ endif
+ let ruby_version = split(g:plug_ruby, '\.')
+ unlet g:plug_ruby
+ return s:version_requirement(ruby_version, [1, 8, 7])
+endfunction
+
+function! s:update_impl(pull, force, args) abort
+ let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
+ let args = filter(copy(a:args), 'v:val != "--sync"')
+ let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
+ \ remove(args, -1) : get(g:, 'plug_threads', 16)
+
+ let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
+ let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
+ \ filter(managed, 'index(args, v:key) >= 0')
+
+ if empty(todo)
+ return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
+ endif
+
+ if !s:is_win && s:git_version_requirement(2, 3)
+ let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
+ let $GIT_TERMINAL_PROMPT = 0
+ for plug in values(todo)
+ let plug.uri = substitute(plug.uri,
+ \ '^https://git::@github\.com', 'https://github.com', '')
+ endfor
+ endif
+
+ if !isdirectory(g:plug_home)
+ try
+ call mkdir(g:plug_home, 'p')
+ catch
+ return s:err(printf('Invalid plug directory: %s. '.
+ \ 'Try to call plug#begin with a valid directory', g:plug_home))
+ endtry
+ endif
+
+ if has('nvim') && !exists('*jobwait') && threads > 1
+ call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
+ endif
+
+ let use_job = s:nvim || s:vim8
+ let python = (has('python') || has('python3')) && !use_job
+ let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
+
+ let s:update = {
+ \ 'start': reltime(),
+ \ 'all': todo,
+ \ 'todo': copy(todo),
+ \ 'errors': [],
+ \ 'pull': a:pull,
+ \ 'force': a:force,
+ \ 'new': {},
+ \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
+ \ 'bar': '',
+ \ 'fin': 0
+ \ }
+
+ call s:prepare(1)
+ call append(0, ['', ''])
+ normal! 2G
+ silent! redraw
+
+ let s:clone_opt = []
+ if get(g:, 'plug_shallow', 1)
+ call extend(s:clone_opt, ['--depth', '1'])
+ if s:git_version_requirement(1, 7, 10)
+ call add(s:clone_opt, '--no-single-branch')
+ endif
+ endif
+
+ if has('win32unix') || has('wsl')
+ call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
+ endif
+
+ let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
+
+ " Python version requirement (>= 2.7)
+ if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
+ redir => pyv
+ silent python import platform; print platform.python_version()
+ redir END
+ let python = s:version_requirement(
+ \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
+ endif
+
+ if (python || ruby) && s:update.threads > 1
+ try
+ let imd = &imd
+ if s:mac_gui
+ set noimd
+ endif
+ if ruby
+ call s:update_ruby()
+ else
+ call s:update_python()
+ endif
+ catch
+ let lines = getline(4, '$')
+ let printed = {}
+ silent! 4,$d _
+ for line in lines
+ let name = s:extract_name(line, '.', '')
+ if empty(name) || !has_key(printed, name)
+ call append('$', line)
+ if !empty(name)
+ let printed[name] = 1
+ if line[0] == 'x' && index(s:update.errors, name) < 0
+ call add(s:update.errors, name)
+ end
+ endif
+ endif
+ endfor
+ finally
+ let &imd = imd
+ call s:update_finish()
+ endtry
+ else
+ call s:update_vim()
+ while use_job && sync
+ sleep 100m
+ if s:update.fin
+ break
+ endif
+ endwhile
+ endif
+endfunction
+
+function! s:log4(name, msg)
+ call setline(4, printf('- %s (%s)', a:msg, a:name))
+ redraw
+endfunction
+
+function! s:update_finish()
+ if exists('s:git_terminal_prompt')
+ let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
+ endif
+ if s:switch_in()
+ call append(3, '- Updating ...') | 4
+ for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
+ let [pos, _] = s:logpos(name)
+ if !pos
+ continue
+ endif
+ if has_key(spec, 'commit')
+ call s:log4(name, 'Checking out '.spec.commit)
+ let out = s:checkout(spec)
+ elseif has_key(spec, 'tag')
+ let tag = spec.tag
+ if tag =~ '\*'
+ let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
+ if !v:shell_error && !empty(tags)
+ let tag = tags[0]
+ call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
+ call append(3, '')
+ endif
+ endif
+ call s:log4(name, 'Checking out '.tag)
+ let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
+ else
+ let branch = s:git_origin_branch(spec)
+ call s:log4(name, 'Merging origin/'.s:esc(branch))
+ let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
+ \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
+ endif
+ if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
+ \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
+ call s:log4(name, 'Updating submodules. This may take a while.')
+ let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
+ endif
+ let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
+ if v:shell_error
+ call add(s:update.errors, name)
+ call s:regress_bar()
+ silent execute pos 'd _'
+ call append(4, msg) | 4
+ elseif !empty(out)
+ call setline(pos, msg[0])
+ endif
+ redraw
+ endfor
+ silent 4 d _
+ try
+ call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
+ catch
+ call s:warn('echom', v:exception)
+ call s:warn('echo', '')
+ return
+ endtry
+ call s:finish(s:update.pull)
+ call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
+ call s:switch_out('normal! gg')
+ endif
+endfunction
+
+function! s:job_abort()
+ if (!s:nvim && !s:vim8) || !exists('s:jobs')
+ return
+ endif
+
+ for [name, j] in items(s:jobs)
+ if s:nvim
+ silent! call jobstop(j.jobid)
+ elseif s:vim8
+ silent! call job_stop(j.jobid)
+ endif
+ if j.new
+ call s:rm_rf(g:plugs[name].dir)
+ endif
+ endfor
+ let s:jobs = {}
+endfunction
+
+function! s:last_non_empty_line(lines)
+ let len = len(a:lines)
+ for idx in range(len)
+ let line = a:lines[len-idx-1]
+ if !empty(line)
+ return line
+ endif
+ endfor
+ return ''
+endfunction
+
+function! s:job_out_cb(self, data) abort
+ let self = a:self
+ let data = remove(self.lines, -1) . a:data
+ let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
+ call extend(self.lines, lines)
+ " To reduce the number of buffer updates
+ let self.tick = get(self, 'tick', -1) + 1
+ if !self.running || self.tick % len(s:jobs) == 0
+ let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
+ let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
+ call s:log(bullet, self.name, result)
+ endif
+endfunction
+
+function! s:job_exit_cb(self, data) abort
+ let a:self.running = 0
+ let a:self.error = a:data != 0
+ call s:reap(a:self.name)
+ call s:tick()
+endfunction
+
+function! s:job_cb(fn, job, ch, data)
+ if !s:plug_window_exists() " plug window closed
+ return s:job_abort()
+ endif
+ call call(a:fn, [a:job, a:data])
+endfunction
+
+function! s:nvim_cb(job_id, data, event) dict abort
+ return (a:event == 'stdout' || a:event == 'stderr') ?
+ \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) :
+ \ s:job_cb('s:job_exit_cb', self, 0, a:data)
+endfunction
+
+function! s:spawn(name, cmd, opts)
+ let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
+ \ 'new': get(a:opts, 'new', 0) }
+ let s:jobs[a:name] = job
+
+ if s:nvim
+ if has_key(a:opts, 'dir')
+ let job.cwd = a:opts.dir
+ endif
+ let argv = a:cmd
+ call extend(job, {
+ \ 'on_stdout': function('s:nvim_cb'),
+ \ 'on_stderr': function('s:nvim_cb'),
+ \ 'on_exit': function('s:nvim_cb'),
+ \ })
+ let jid = s:plug_call('jobstart', argv, job)
+ if jid > 0
+ let job.jobid = jid
+ else
+ let job.running = 0
+ let job.error = 1
+ let job.lines = [jid < 0 ? argv[0].' is not executable' :
+ \ 'Invalid arguments (or job table is full)']
+ endif
+ elseif s:vim8
+ let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
+ if has_key(a:opts, 'dir')
+ let cmd = s:with_cd(cmd, a:opts.dir, 0)
+ endif
+ let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
+ let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
+ \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]),
+ \ 'err_cb': function('s:job_cb', ['s:job_out_cb', job]),
+ \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]),
+ \ 'err_mode': 'raw',
+ \ 'out_mode': 'raw'
+ \})
+ if job_status(jid) == 'run'
+ let job.jobid = jid
+ else
+ let job.running = 0
+ let job.error = 1
+ let job.lines = ['Failed to start job']
+ endif
+ else
+ let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
+ let job.error = v:shell_error != 0
+ let job.running = 0
+ endif
+endfunction
+
+function! s:reap(name)
+ let job = s:jobs[a:name]
+ if job.error
+ call add(s:update.errors, a:name)
+ elseif get(job, 'new', 0)
+ let s:update.new[a:name] = 1
+ endif
+ let s:update.bar .= job.error ? 'x' : '='
+
+ let bullet = job.error ? 'x' : '-'
+ let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
+ call s:log(bullet, a:name, empty(result) ? 'OK' : result)
+ call s:bar()
+
+ call remove(s:jobs, a:name)
+endfunction
+
+function! s:bar()
+ if s:switch_in()
+ let total = len(s:update.all)
+ call setline(1, (s:update.pull ? 'Updating' : 'Installing').
+ \ ' plugins ('.len(s:update.bar).'/'.total.')')
+ call s:progress_bar(2, s:update.bar, total)
+ call s:switch_out()
+ endif
+endfunction
+
+function! s:logpos(name)
+ let max = line('$')
+ for i in range(4, max > 4 ? max : 4)
+ if getline(i) =~# '^[-+x*] '.a:name.':'
+ for j in range(i + 1, max > 5 ? max : 5)
+ if getline(j) !~ '^ '
+ return [i, j - 1]
+ endif
+ endfor
+ return [i, i]
+ endif
+ endfor
+ return [0, 0]
+endfunction
+
+function! s:log(bullet, name, lines)
+ if s:switch_in()
+ let [b, e] = s:logpos(a:name)
+ if b > 0
+ silent execute printf('%d,%d d _', b, e)
+ if b > winheight('.')
+ let b = 4
+ endif
+ else
+ let b = 4
+ endif
+ " FIXME For some reason, nomodifiable is set after :d in vim8
+ setlocal modifiable
+ call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
+ call s:switch_out()
+ endif
+endfunction
+
+function! s:update_vim()
+ let s:jobs = {}
+
+ call s:bar()
+ call s:tick()
+endfunction
+
+function! s:tick()
+ let pull = s:update.pull
+ let prog = s:progress_opt(s:nvim || s:vim8)
+while 1 " Without TCO, Vim stack is bound to explode
+ if empty(s:update.todo)
+ if empty(s:jobs) && !s:update.fin
+ call s:update_finish()
+ let s:update.fin = 1
+ endif
+ return
+ endif
+
+ let name = keys(s:update.todo)[0]
+ let spec = remove(s:update.todo, name)
+ let new = empty(globpath(spec.dir, '.git', 1))
+
+ call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
+ redraw
+
+ let has_tag = has_key(spec, 'tag')
+ if !new
+ let [error, _] = s:git_validate(spec, 0)
+ if empty(error)
+ if pull
+ let cmd = ['git', 'fetch']
+ if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
+ call extend(cmd, ['--depth', '99999999'])
+ endif
+ if !empty(prog)
+ call add(cmd, prog)
+ endif
+ call s:spawn(name, cmd, { 'dir': spec.dir })
+ else
+ let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
+ endif
+ else
+ let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
+ endif
+ else
+ let cmd = ['git', 'clone']
+ if !has_tag
+ call extend(cmd, s:clone_opt)
+ endif
+ if !empty(prog)
+ call add(cmd, prog)
+ endif
+ call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
+ endif
+
+ if !s:jobs[name].running
+ call s:reap(name)
+ endif
+ if len(s:jobs) >= s:update.threads
+ break
+ endif
+endwhile
+endfunction
+
+function! s:update_python()
+let py_exe = has('python') ? 'python' : 'python3'
+execute py_exe "<< EOF"
+import datetime
+import functools
+import os
+try:
+ import queue
+except ImportError:
+ import Queue as queue
+import random
+import re
+import shutil
+import signal
+import subprocess
+import tempfile
+import threading as thr
+import time
+import traceback
+import vim
+
+G_NVIM = vim.eval("has('nvim')") == '1'
+G_PULL = vim.eval('s:update.pull') == '1'
+G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
+G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
+G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
+G_PROGRESS = vim.eval('s:progress_opt(1)')
+G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
+G_STOP = thr.Event()
+G_IS_WIN = vim.eval('s:is_win') == '1'
+
+class PlugError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+class CmdTimedOut(PlugError):
+ pass
+class CmdFailed(PlugError):
+ pass
+class InvalidURI(PlugError):
+ pass
+class Action(object):
+ INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
+
+class Buffer(object):
+ def __init__(self, lock, num_plugs, is_pull):
+ self.bar = ''
+ self.event = 'Updating' if is_pull else 'Installing'
+ self.lock = lock
+ self.maxy = int(vim.eval('winheight(".")'))
+ self.num_plugs = num_plugs
+
+ def __where(self, name):
+ """ Find first line with name in current buffer. Return line num. """
+ found, lnum = False, 0
+ matcher = re.compile('^[-+x*] {0}:'.format(name))
+ for line in vim.current.buffer:
+ if matcher.search(line) is not None:
+ found = True
+ break
+ lnum += 1
+
+ if not found:
+ lnum = -1
+ return lnum
+
+ def header(self):
+ curbuf = vim.current.buffer
+ curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
+
+ num_spaces = self.num_plugs - len(self.bar)
+ curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
+
+ with self.lock:
+ vim.command('normal! 2G')
+ vim.command('redraw')
+
+ def write(self, action, name, lines):
+ first, rest = lines[0], lines[1:]
+ msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
+ msg.extend([' ' + line for line in rest])
+
+ try:
+ if action == Action.ERROR:
+ self.bar += 'x'
+ vim.command("call add(s:update.errors, '{0}')".format(name))
+ elif action == Action.DONE:
+ self.bar += '='
+
+ curbuf = vim.current.buffer
+ lnum = self.__where(name)
+ if lnum != -1: # Found matching line num
+ del curbuf[lnum]
+ if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
+ lnum = 3
+ else:
+ lnum = 3
+ curbuf.append(msg, lnum)
+
+ self.header()
+ except vim.error:
+ pass
+
+class Command(object):
+ CD = 'cd /d' if G_IS_WIN else 'cd'
+
+ def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
+ self.cmd = cmd
+ if cmd_dir:
+ self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
+ self.timeout = timeout
+ self.callback = cb if cb else (lambda msg: None)
+ self.clean = clean if clean else (lambda: None)
+ self.proc = None
+
+ @property
+ def alive(self):
+ """ Returns true only if command still running. """
+ return self.proc and self.proc.poll() is None
+
+ def execute(self, ntries=3):
+ """ Execute the command with ntries if CmdTimedOut.
+ Returns the output of the command if no Exception.
+ """
+ attempt, finished, limit = 0, False, self.timeout
+
+ while not finished:
+ try:
+ attempt += 1
+ result = self.try_command()
+ finished = True
+ return result
+ except CmdTimedOut:
+ if attempt != ntries:
+ self.notify_retry()
+ self.timeout += limit
+ else:
+ raise
+
+ def notify_retry(self):
+ """ Retry required for command, notify user. """
+ for count in range(3, 0, -1):
+ if G_STOP.is_set():
+ raise KeyboardInterrupt
+ msg = 'Timeout. Will retry in {0} second{1} ...'.format(
+ count, 's' if count != 1 else '')
+ self.callback([msg])
+ time.sleep(1)
+ self.callback(['Retrying ...'])
+
+ def try_command(self):
+ """ Execute a cmd & poll for callback. Returns list of output.
+ Raises CmdFailed -> return code for Popen isn't 0
+ Raises CmdTimedOut -> command exceeded timeout without new output
+ """
+ first_line = True
+
+ try:
+ tfile = tempfile.NamedTemporaryFile(mode='w+b')
+ preexec_fn = not G_IS_WIN and os.setsid or None
+ self.proc = subprocess.Popen(self.cmd, stdout=tfile,
+ stderr=subprocess.STDOUT,
+ stdin=subprocess.PIPE, shell=True,
+ preexec_fn=preexec_fn)
+ thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
+ thrd.start()
+
+ thread_not_started = True
+ while thread_not_started:
+ try:
+ thrd.join(0.1)
+ thread_not_started = False
+ except RuntimeError:
+ pass
+
+ while self.alive:
+ if G_STOP.is_set():
+ raise KeyboardInterrupt
+
+ if first_line or random.random() < G_LOG_PROB:
+ first_line = False
+ line = '' if G_IS_WIN else nonblock_read(tfile.name)
+ if line:
+ self.callback([line])
+
+ time_diff = time.time() - os.path.getmtime(tfile.name)
+ if time_diff > self.timeout:
+ raise CmdTimedOut(['Timeout!'])
+
+ thrd.join(0.5)
+
+ tfile.seek(0)
+ result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
+
+ if self.proc.returncode != 0:
+ raise CmdFailed([''] + result)
+
+ return result
+ except:
+ self.terminate()
+ raise
+
+ def terminate(self):
+ """ Terminate process and cleanup. """
+ if self.alive:
+ if G_IS_WIN:
+ os.kill(self.proc.pid, signal.SIGINT)
+ else:
+ os.killpg(self.proc.pid, signal.SIGTERM)
+ self.clean()
+
+class Plugin(object):
+ def __init__(self, name, args, buf_q, lock):
+ self.name = name
+ self.args = args
+ self.buf_q = buf_q
+ self.lock = lock
+ self.tag = args.get('tag', 0)
+
+ def manage(self):
+ try:
+ if os.path.exists(self.args['dir']):
+ self.update()
+ else:
+ self.install()
+ with self.lock:
+ thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
+ except PlugError as exc:
+ self.write(Action.ERROR, self.name, exc.msg)
+ except KeyboardInterrupt:
+ G_STOP.set()
+ self.write(Action.ERROR, self.name, ['Interrupted!'])
+ except:
+ # Any exception except those above print stack trace
+ msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
+ self.write(Action.ERROR, self.name, msg.split('\n'))
+ raise
+
+ def install(self):
+ target = self.args['dir']
+ if target[-1] == '\\':
+ target = target[0:-1]
+
+ def clean(target):
+ def _clean():
+ try:
+ shutil.rmtree(target)
+ except OSError:
+ pass
+ return _clean
+
+ self.write(Action.INSTALL, self.name, ['Installing ...'])
+ callback = functools.partial(self.write, Action.INSTALL, self.name)
+ cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
+ '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
+ esc(target))
+ com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
+ result = com.execute(G_RETRIES)
+ self.write(Action.DONE, self.name, result[-1:])
+
+ def repo_uri(self):
+ cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
+ command = Command(cmd, self.args['dir'], G_TIMEOUT,)
+ result = command.execute(G_RETRIES)
+ return result[-1]
+
+ def update(self):
+ actual_uri = self.repo_uri()
+ expect_uri = self.args['uri']
+ regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
+ ma = regex.match(actual_uri)
+ mb = regex.match(expect_uri)
+ if ma is None or mb is None or ma.groups() != mb.groups():
+ msg = ['',
+ 'Invalid URI: {0}'.format(actual_uri),
+ 'Expected {0}'.format(expect_uri),
+ 'PlugClean required.']
+ raise InvalidURI(msg)
+
+ if G_PULL:
+ self.write(Action.UPDATE, self.name, ['Updating ...'])
+ callback = functools.partial(self.write, Action.UPDATE, self.name)
+ fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
+ cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
+ com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
+ result = com.execute(G_RETRIES)
+ self.write(Action.DONE, self.name, result[-1:])
+ else:
+ self.write(Action.DONE, self.name, ['Already installed'])
+
+ def write(self, action, name, msg):
+ self.buf_q.put((action, name, msg))
+
+class PlugThread(thr.Thread):
+ def __init__(self, tname, args):
+ super(PlugThread, self).__init__()
+ self.tname = tname
+ self.args = args
+
+ def run(self):
+ thr.current_thread().name = self.tname
+ buf_q, work_q, lock = self.args
+
+ try:
+ while not G_STOP.is_set():
+ name, args = work_q.get_nowait()
+ plug = Plugin(name, args, buf_q, lock)
+ plug.manage()
+ work_q.task_done()
+ except queue.Empty:
+ pass
+
+class RefreshThread(thr.Thread):
+ def __init__(self, lock):
+ super(RefreshThread, self).__init__()
+ self.lock = lock
+ self.running = True
+
+ def run(self):
+ while self.running:
+ with self.lock:
+ thread_vim_command('noautocmd normal! a')
+ time.sleep(0.33)
+
+ def stop(self):
+ self.running = False
+
+if G_NVIM:
+ def thread_vim_command(cmd):
+ vim.session.threadsafe_call(lambda: vim.command(cmd))
+else:
+ def thread_vim_command(cmd):
+ vim.command(cmd)
+
+def esc(name):
+ return '"' + name.replace('"', '\"') + '"'
+
+def nonblock_read(fname):
+ """ Read a file with nonblock flag. Return the last line. """
+ fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
+ buf = os.read(fread, 100000).decode('utf-8', 'replace')
+ os.close(fread)
+
+ line = buf.rstrip('\r\n')
+ left = max(line.rfind('\r'), line.rfind('\n'))
+ if left != -1:
+ left += 1
+ line = line[left:]
+
+ return line
+
+def main():
+ thr.current_thread().name = 'main'
+ nthreads = int(vim.eval('s:update.threads'))
+ plugs = vim.eval('s:update.todo')
+ mac_gui = vim.eval('s:mac_gui') == '1'
+
+ lock = thr.Lock()
+ buf = Buffer(lock, len(plugs), G_PULL)
+ buf_q, work_q = queue.Queue(), queue.Queue()
+ for work in plugs.items():
+ work_q.put(work)
+
+ start_cnt = thr.active_count()
+ for num in range(nthreads):
+ tname = 'PlugT-{0:02}'.format(num)
+ thread = PlugThread(tname, (buf_q, work_q, lock))
+ thread.start()
+ if mac_gui:
+ rthread = RefreshThread(lock)
+ rthread.start()
+
+ while not buf_q.empty() or thr.active_count() != start_cnt:
+ try:
+ action, name, msg = buf_q.get(True, 0.25)
+ buf.write(action, name, ['OK'] if not msg else msg)
+ buf_q.task_done()
+ except queue.Empty:
+ pass
+ except KeyboardInterrupt:
+ G_STOP.set()
+
+ if mac_gui:
+ rthread.stop()
+ rthread.join()
+
+main()
+EOF
+endfunction
+
+function! s:update_ruby()
+ ruby << EOF
+ module PlugStream
+ SEP = ["\r", "\n", nil]
+ def get_line
+ buffer = ''
+ loop do
+ char = readchar rescue return
+ if SEP.include? char.chr
+ buffer << $/
+ break
+ else
+ buffer << char
+ end
+ end
+ buffer
+ end
+ end unless defined?(PlugStream)
+
+ def esc arg
+ %["#{arg.gsub('"', '\"')}"]
+ end
+
+ def killall pid
+ pids = [pid]
+ if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
+ pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
+ else
+ unless `which pgrep 2> /dev/null`.empty?
+ children = pids
+ until children.empty?
+ children = children.map { |pid|
+ `pgrep -P #{pid}`.lines.map { |l| l.chomp }
+ }.flatten
+ pids += children
+ end
+ end
+ pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
+ end
+ end
+
+ def compare_git_uri a, b
+ regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
+ regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
+ end
+
+ require 'thread'
+ require 'fileutils'
+ require 'timeout'
+ running = true
+ iswin = VIM::evaluate('s:is_win').to_i == 1
+ pull = VIM::evaluate('s:update.pull').to_i == 1
+ base = VIM::evaluate('g:plug_home')
+ all = VIM::evaluate('s:update.todo')
+ limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
+ tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
+ nthr = VIM::evaluate('s:update.threads').to_i
+ maxy = VIM::evaluate('winheight(".")').to_i
+ vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
+ cd = iswin ? 'cd /d' : 'cd'
+ tot = VIM::evaluate('len(s:update.todo)') || 0
+ bar = ''
+ skip = 'Already installed'
+ mtx = Mutex.new
+ take1 = proc { mtx.synchronize { running && all.shift } }
+ logh = proc {
+ cnt = bar.length
+ $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
+ $curbuf[2] = '[' + bar.ljust(tot) + ']'
+ VIM::command('normal! 2G')
+ VIM::command('redraw')
+ }
+ where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
+ log = proc { |name, result, type|
+ mtx.synchronize do
+ ing = ![true, false].include?(type)
+ bar += type ? '=' : 'x' unless ing
+ b = case type
+ when :install then '+' when :update then '*'
+ when true, nil then '-' else
+ VIM::command("call add(s:update.errors, '#{name}')")
+ 'x'
+ end
+ result =
+ if type || type.nil?
+ ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
+ elsif result =~ /^Interrupted|^Timeout/
+ ["#{b} #{name}: #{result}"]
+ else
+ ["#{b} #{name}"] + result.lines.map { |l| " " << l }
+ end
+ if lnum = where.call(name)
+ $curbuf.delete lnum
+ lnum = 4 if ing && lnum > maxy
+ end
+ result.each_with_index do |line, offset|
+ $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
+ end
+ logh.call
+ end
+ }
+ bt = proc { |cmd, name, type, cleanup|
+ tried = timeout = 0
+ begin
+ tried += 1
+ timeout += limit
+ fd = nil
+ data = ''
+ if iswin
+ Timeout::timeout(timeout) do
+ tmp = VIM::evaluate('tempname()')
+ system("(#{cmd}) > #{tmp}")
+ data = File.read(tmp).chomp
+ File.unlink tmp rescue nil
+ end
+ else
+ fd = IO.popen(cmd).extend(PlugStream)
+ first_line = true
+ log_prob = 1.0 / nthr
+ while line = Timeout::timeout(timeout) { fd.get_line }
+ data << line
+ log.call name, line.chomp, type if name && (first_line || rand < log_prob)
+ first_line = false
+ end
+ fd.close
+ end
+ [$? == 0, data.chomp]
+ rescue Timeout::Error, Interrupt => e
+ if fd && !fd.closed?
+ killall fd.pid
+ fd.close
+ end
+ cleanup.call if cleanup
+ if e.is_a?(Timeout::Error) && tried < tries
+ 3.downto(1) do |countdown|
+ s = countdown > 1 ? 's' : ''
+ log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
+ sleep 1
+ end
+ log.call name, 'Retrying ...', type
+ retry
+ end
+ [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
+ end
+ }
+ main = Thread.current
+ threads = []
+ watcher = Thread.new {
+ if vim7
+ while VIM::evaluate('getchar(1)')
+ sleep 0.1
+ end
+ else
+ require 'io/console' # >= Ruby 1.9
+ nil until IO.console.getch == 3.chr
+ end
+ mtx.synchronize do
+ running = false
+ threads.each { |t| t.raise Interrupt } unless vim7
+ end
+ threads.each { |t| t.join rescue nil }
+ main.kill
+ }
+ refresh = Thread.new {
+ while true
+ mtx.synchronize do
+ break unless running
+ VIM::command('noautocmd normal! a')
+ end
+ sleep 0.2
+ end
+ } if VIM::evaluate('s:mac_gui') == 1
+
+ clone_opt = VIM::evaluate('s:clone_opt').join(' ')
+ progress = VIM::evaluate('s:progress_opt(1)')
+ nthr.times do
+ mtx.synchronize do
+ threads << Thread.new {
+ while pair = take1.call
+ name = pair.first
+ dir, uri, tag = pair.last.values_at *%w[dir uri tag]
+ exists = File.directory? dir
+ ok, result =
+ if exists
+ chdir = "#{cd} #{iswin ? dir : esc(dir)}"
+ ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
+ current_uri = data.lines.to_a.last
+ if !ret
+ if data =~ /^Interrupted|^Timeout/
+ [false, data]
+ else
+ [false, [data.chomp, "PlugClean required."].join($/)]
+ end
+ elsif !compare_git_uri(current_uri, uri)
+ [false, ["Invalid URI: #{current_uri}",
+ "Expected: #{uri}",
+ "PlugClean required."].join($/)]
+ else
+ if pull
+ log.call name, 'Updating ...', :update
+ fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
+ bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
+ else
+ [true, skip]
+ end
+ end
+ else
+ d = esc dir.sub(%r{[\\/]+$}, '')
+ log.call name, 'Installing ...', :install
+ bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
+ FileUtils.rm_rf dir
+ }
+ end
+ mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
+ log.call name, result, ok
+ end
+ } if running
+ end
+ end
+ threads.each { |t| t.join rescue nil }
+ logh.call
+ refresh.kill if refresh
+ watcher.kill
+EOF
+endfunction
+
+function! s:shellesc_cmd(arg, script)
+ let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
+ return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
+endfunction
+
+function! s:shellesc_ps1(arg)
+ return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
+endfunction
+
+function! s:shellesc_sh(arg)
+ return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
+endfunction
+
+" Escape the shell argument based on the shell.
+" Vim and Neovim's shellescape() are insufficient.
+" 1. shellslash determines whether to use single/double quotes.
+" Double-quote escaping is fragile for cmd.exe.
+" 2. It does not work for powershell.
+" 3. It does not work for *sh shells if the command is executed
+" via cmd.exe (ie. cmd.exe /c sh -c command command_args)
+" 4. It does not support batchfile syntax.
+"
+" Accepts an optional dictionary with the following keys:
+" - shell: same as Vim/Neovim 'shell' option.
+" If unset, fallback to 'cmd.exe' on Windows or 'sh'.
+" - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
+function! plug#shellescape(arg, ...)
+ if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
+ return a:arg
+ endif
+ let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
+ let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
+ let script = get(opts, 'script', 1)
+ if shell =~# 'cmd\(\.exe\)\?$'
+ return s:shellesc_cmd(a:arg, script)
+ elseif shell =~# 'powershell\(\.exe\)\?$' || shell =~# 'pwsh$'
+ return s:shellesc_ps1(a:arg)
+ endif
+ return s:shellesc_sh(a:arg)
+endfunction
+
+function! s:glob_dir(path)
+ return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
+endfunction
+
+function! s:progress_bar(line, bar, total)
+ call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
+endfunction
+
+function! s:compare_git_uri(a, b)
+ " See `git help clone'
+ " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
+ " [git@] github.com[:port] : junegunn/vim-plug [.git]
+ " file:// / junegunn/vim-plug [/]
+ " / junegunn/vim-plug [/]
+ let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
+ let ma = matchlist(a:a, pat)
+ let mb = matchlist(a:b, pat)
+ return ma[1:2] ==# mb[1:2]
+endfunction
+
+function! s:format_message(bullet, name, message)
+ if a:bullet != 'x'
+ return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
+ else
+ let lines = map(s:lines(a:message), '" ".v:val')
+ return extend([printf('x %s:', a:name)], lines)
+ endif
+endfunction
+
+function! s:with_cd(cmd, dir, ...)
+ let script = a:0 > 0 ? a:1 : 1
+ return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
+endfunction
+
+function! s:system(cmd, ...)
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(1)
+ if type(a:cmd) == s:TYPE.list
+ " Neovim's system() supports list argument to bypass the shell
+ " but it cannot set the working directory for the command.
+ " Assume that the command does not rely on the shell.
+ if has('nvim') && a:0 == 0
+ return system(a:cmd)
+ endif
+ let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
+ if &shell =~# 'powershell\(\.exe\)\?$'
+ let cmd = '& ' . cmd
+ endif
+ else
+ let cmd = a:cmd
+ endif
+ if a:0 > 0
+ let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
+ endif
+ if s:is_win && type(a:cmd) != s:TYPE.list
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ return system(cmd)
+ finally
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+endfunction
+
+function! s:system_chomp(...)
+ let ret = call('s:system', a:000)
+ return v:shell_error ? '' : substitute(ret, '\n$', '', '')
+endfunction
+
+function! s:git_validate(spec, check_branch)
+ let err = ''
+ if isdirectory(a:spec.dir)
+ let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
+ let remote = result[-1]
+ if empty(remote)
+ let err = join([remote, 'PlugClean required.'], "\n")
+ elseif !s:compare_git_uri(remote, a:spec.uri)
+ let err = join(['Invalid URI: '.remote,
+ \ 'Expected: '.a:spec.uri,
+ \ 'PlugClean required.'], "\n")
+ elseif a:check_branch && has_key(a:spec, 'commit')
+ let sha = s:git_revision(a:spec.dir)
+ if empty(sha)
+ let err = join(add(result, 'PlugClean required.'), "\n")
+ elseif !s:hash_match(sha, a:spec.commit)
+ let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
+ \ a:spec.commit[:6], sha[:6]),
+ \ 'PlugUpdate required.'], "\n")
+ endif
+ elseif a:check_branch
+ let current_branch = result[0]
+ " Check tag
+ let origin_branch = s:git_origin_branch(a:spec)
+ if has_key(a:spec, 'tag')
+ let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
+ if a:spec.tag !=# tag && a:spec.tag !~ '\*'
+ let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
+ \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
+ endif
+ " Check branch
+ elseif origin_branch !=# current_branch
+ let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
+ \ current_branch, origin_branch)
+ endif
+ if empty(err)
+ let [ahead, behind] = split(s:lastline(s:system([
+ \ 'git', 'rev-list', '--count', '--left-right',
+ \ printf('HEAD...origin/%s', origin_branch)
+ \ ], a:spec.dir)), '\t')
+ if !v:shell_error && ahead
+ if behind
+ " Only mention PlugClean if diverged, otherwise it's likely to be
+ " pushable (and probably not that messed up).
+ let err = printf(
+ \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
+ \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
+ else
+ let err = printf("Ahead of origin/%s by %d commit(s).\n"
+ \ .'Cannot update until local changes are pushed.',
+ \ origin_branch, ahead)
+ endif
+ endif
+ endif
+ endif
+ else
+ let err = 'Not found'
+ endif
+ return [err, err =~# 'PlugClean']
+endfunction
+
+function! s:rm_rf(dir)
+ if isdirectory(a:dir)
+ return s:system(s:is_win
+ \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
+ \ : ['rm', '-rf', a:dir])
+ endif
+endfunction
+
+function! s:clean(force)
+ call s:prepare()
+ call append(0, 'Searching for invalid plugins in '.g:plug_home)
+ call append(1, '')
+
+ " List of valid directories
+ let dirs = []
+ let errs = {}
+ let [cnt, total] = [0, len(g:plugs)]
+ for [name, spec] in items(g:plugs)
+ if !s:is_managed(name)
+ call add(dirs, spec.dir)
+ else
+ let [err, clean] = s:git_validate(spec, 1)
+ if clean
+ let errs[spec.dir] = s:lines(err)[0]
+ else
+ call add(dirs, spec.dir)
+ endif
+ endif
+ let cnt += 1
+ call s:progress_bar(2, repeat('=', cnt), total)
+ normal! 2G
+ redraw
+ endfor
+
+ let allowed = {}
+ for dir in dirs
+ let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
+ let allowed[dir] = 1
+ for child in s:glob_dir(dir)
+ let allowed[child] = 1
+ endfor
+ endfor
+
+ let todo = []
+ let found = sort(s:glob_dir(g:plug_home))
+ while !empty(found)
+ let f = remove(found, 0)
+ if !has_key(allowed, f) && isdirectory(f)
+ call add(todo, f)
+ call append(line('$'), '- ' . f)
+ if has_key(errs, f)
+ call append(line('$'), ' ' . errs[f])
+ endif
+ let found = filter(found, 'stridx(v:val, f) != 0')
+ end
+ endwhile
+
+ 4
+ redraw
+ if empty(todo)
+ call append(line('$'), 'Already clean.')
+ else
+ let s:clean_count = 0
+ call append(3, ['Directories to delete:', ''])
+ redraw!
+ if a:force || s:ask_no_interrupt('Delete all directories?')
+ call s:delete([6, line('$')], 1)
+ else
+ call setline(4, 'Cancelled.')
+ nnoremap <silent> <buffer> d :set opfunc=<sid>delete_op<cr>g@
+ nmap <silent> <buffer> dd d_
+ xnoremap <silent> <buffer> d :<c-u>call <sid>delete_op(visualmode(), 1)<cr>
+ echo 'Delete the lines (d{motion}) to delete the corresponding directories'
+ endif
+ endif
+ 4
+ setlocal nomodifiable
+endfunction
+
+function! s:delete_op(type, ...)
+ call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
+endfunction
+
+function! s:delete(range, force)
+ let [l1, l2] = a:range
+ let force = a:force
+ let err_count = 0
+ while l1 <= l2
+ let line = getline(l1)
+ if line =~ '^- ' && isdirectory(line[2:])
+ execute l1
+ redraw!
+ let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
+ let force = force || answer > 1
+ if answer
+ let err = s:rm_rf(line[2:])
+ setlocal modifiable
+ if empty(err)
+ call setline(l1, '~'.line[1:])
+ let s:clean_count += 1
+ else
+ delete _
+ call append(l1 - 1, s:format_message('x', line[1:], err))
+ let l2 += len(s:lines(err))
+ let err_count += 1
+ endif
+ let msg = printf('Removed %d directories.', s:clean_count)
+ if err_count > 0
+ let msg .= printf(' Failed to remove %d directories.', err_count)
+ endif
+ call setline(4, msg)
+ setlocal nomodifiable
+ endif
+ endif
+ let l1 += 1
+ endwhile
+endfunction
+
+function! s:upgrade()
+ echo 'Downloading the latest version of vim-plug'
+ redraw
+ let tmp = s:plug_tempname()
+ let new = tmp . '/plug.vim'
+
+ try
+ let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
+ if v:shell_error
+ return s:err('Error upgrading vim-plug: '. out)
+ endif
+
+ if readfile(s:me) ==# readfile(new)
+ echo 'vim-plug is already up-to-date'
+ return 0
+ else
+ call rename(s:me, s:me . '.old')
+ call rename(new, s:me)
+ unlet g:loaded_plug
+ echo 'vim-plug has been upgraded'
+ return 1
+ endif
+ finally
+ silent! call s:rm_rf(tmp)
+ endtry
+endfunction
+
+function! s:upgrade_specs()
+ for spec in values(g:plugs)
+ let spec.frozen = get(spec, 'frozen', 0)
+ endfor
+endfunction
+
+function! s:status()
+ call s:prepare()
+ call append(0, 'Checking plugins')
+ call append(1, '')
+
+ let ecnt = 0
+ let unloaded = 0
+ let [cnt, total] = [0, len(g:plugs)]
+ for [name, spec] in items(g:plugs)
+ let is_dir = isdirectory(spec.dir)
+ if has_key(spec, 'uri')
+ if is_dir
+ let [err, _] = s:git_validate(spec, 1)
+ let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
+ else
+ let [valid, msg] = [0, 'Not found. Try PlugInstall.']
+ endif
+ else
+ if is_dir
+ let [valid, msg] = [1, 'OK']
+ else
+ let [valid, msg] = [0, 'Not found.']
+ endif
+ endif
+ let cnt += 1
+ let ecnt += !valid
+ " `s:loaded` entry can be missing if PlugUpgraded
+ if is_dir && get(s:loaded, name, -1) == 0
+ let unloaded = 1
+ let msg .= ' (not loaded)'
+ endif
+ call s:progress_bar(2, repeat('=', cnt), total)
+ call append(3, s:format_message(valid ? '-' : 'x', name, msg))
+ normal! 2G
+ redraw
+ endfor
+ call setline(1, 'Finished. '.ecnt.' error(s).')
+ normal! gg
+ setlocal nomodifiable
+ if unloaded
+ echo "Press 'L' on each line to load plugin, or 'U' to update"
+ nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
+ xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
+ end
+endfunction
+
+function! s:extract_name(str, prefix, suffix)
+ return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
+endfunction
+
+function! s:status_load(lnum)
+ let line = getline(a:lnum)
+ let name = s:extract_name(line, '-', '(not loaded)')
+ if !empty(name)
+ call plug#load(name)
+ setlocal modifiable
+ call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
+ setlocal nomodifiable
+ endif
+endfunction
+
+function! s:status_update() range
+ let lines = getline(a:firstline, a:lastline)
+ let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
+ if !empty(names)
+ echo
+ execute 'PlugUpdate' join(names)
+ endif
+endfunction
+
+function! s:is_preview_window_open()
+ silent! wincmd P
+ if &previewwindow
+ wincmd p
+ return 1
+ endif
+endfunction
+
+function! s:find_name(lnum)
+ for lnum in reverse(range(1, a:lnum))
+ let line = getline(lnum)
+ if empty(line)
+ return ''
+ endif
+ let name = s:extract_name(line, '-', '')
+ if !empty(name)
+ return name
+ endif
+ endfor
+ return ''
+endfunction
+
+function! s:preview_commit()
+ if b:plug_preview < 0
+ let b:plug_preview = !s:is_preview_window_open()
+ endif
+
+ let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}')
+ if empty(sha)
+ return
+ endif
+
+ let name = s:find_name(line('.'))
+ if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
+ return
+ endif
+
+ if exists('g:plug_pwindow') && !s:is_preview_window_open()
+ execute g:plug_pwindow
+ execute 'e' sha
+ else
+ execute 'pedit' sha
+ wincmd P
+ endif
+ setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable
+ let batchfile = ''
+ try
+ let [sh, shellcmdflag, shrd] = s:chsh(1)
+ let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha
+ if s:is_win
+ let [batchfile, cmd] = s:batchfile(cmd)
+ endif
+ execute 'silent %!' cmd
+ finally
+ let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
+ if s:is_win && filereadable(batchfile)
+ call delete(batchfile)
+ endif
+ endtry
+ setlocal nomodifiable
+ nnoremap <silent> <buffer> q :q<cr>
+ wincmd p
+endfunction
+
+function! s:section(flags)
+ call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
+endfunction
+
+function! s:format_git_log(line)
+ let indent = ' '
+ let tokens = split(a:line, nr2char(1))
+ if len(tokens) != 5
+ return indent.substitute(a:line, '\s*$', '', '')
+ endif
+ let [graph, sha, refs, subject, date] = tokens
+ let tag = matchstr(refs, 'tag: [^,)]\+')
+ let tag = empty(tag) ? ' ' : ' ('.tag.') '
+ return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
+endfunction
+
+function! s:append_ul(lnum, text)
+ call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
+endfunction
+
+function! s:diff()
+ call s:prepare()
+ call append(0, ['Collecting changes ...', ''])
+ let cnts = [0, 0]
+ let bar = ''
+ let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
+ call s:progress_bar(2, bar, len(total))
+ for origin in [1, 0]
+ let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
+ if empty(plugs)
+ continue
+ endif
+ call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
+ for [k, v] in plugs
+ let branch = s:git_origin_branch(v)
+ if len(branch)
+ let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
+ let cmd = ['git', 'log', '--graph', '--color=never']
+ if s:git_version_requirement(2, 10, 0)
+ call add(cmd, '--no-show-signature')
+ endif
+ call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
+ if has_key(v, 'rtp')
+ call extend(cmd, ['--', v.rtp])
+ endif
+ let diff = s:system_chomp(cmd, v.dir)
+ if !empty(diff)
+ let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
+ call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
+ let cnts[origin] += 1
+ endif
+ endif
+ let bar .= '='
+ call s:progress_bar(2, bar, len(total))
+ normal! 2G
+ redraw
+ endfor
+ if !cnts[origin]
+ call append(5, ['', 'N/A'])
+ endif
+ endfor
+ call setline(1, printf('%d plugin(s) updated.', cnts[0])
+ \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
+
+ if cnts[0] || cnts[1]
+ nnoremap <silent> <buffer> <plug>(plug-preview) :silent! call <SID>preview_commit()<cr>
+ if empty(maparg("\<cr>", 'n'))
+ nmap <buffer> <cr> <plug>(plug-preview)
+ endif
+ if empty(maparg('o', 'n'))
+ nmap <buffer> o <plug>(plug-preview)
+ endif
+ endif
+ if cnts[0]
+ nnoremap <silent> <buffer> X :call <SID>revert()<cr>
+ echo "Press 'X' on each block to revert the update"
+ endif
+ normal! gg
+ setlocal nomodifiable
+endfunction
+
+function! s:revert()
+ if search('^Pending updates', 'bnW')
+ return
+ endif
+
+ let name = s:find_name(line('.'))
+ if empty(name) || !has_key(g:plugs, name) ||
+ \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
+ return
+ endif
+
+ call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
+ setlocal modifiable
+ normal! "_dap
+ setlocal nomodifiable
+ echo 'Reverted'
+endfunction
+
+function! s:snapshot(force, ...) abort
+ call s:prepare()
+ setf vim
+ call append(0, ['" Generated by vim-plug',
+ \ '" '.strftime("%c"),
+ \ '" :source this file in vim to restore the snapshot',
+ \ '" or execute: vim -S snapshot.vim',
+ \ '', '', 'PlugUpdate!'])
+ 1
+ let anchor = line('$') - 3
+ let names = sort(keys(filter(copy(g:plugs),
+ \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')))
+ for name in reverse(names)
+ let sha = s:git_revision(g:plugs[name].dir)
+ if !empty(sha)
+ call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
+ redraw
+ endif
+ endfor
+
+ if a:0 > 0
+ let fn = s:plug_expand(a:1)
+ if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
+ return
+ endif
+ call writefile(getline(1, '$'), fn)
+ echo 'Saved as '.a:1
+ silent execute 'e' s:esc(fn)
+ setf vim
+ endif
+endfunction
+
+function! s:split_rtp()
+ return split(&rtp, '\\\@<!,')
+endfunction
+
+let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
+let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
+
+if exists('g:plugs')
+ let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
+ call s:upgrade_specs()
+ call s:define_commands()
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/stow/vim/dot-vim/colors/burnttoast256.vim b/stow/vim/dot-vim/colors/burnttoast256.vim
new file mode 100644
index 0000000..b9abaac
--- /dev/null
+++ b/stow/vim/dot-vim/colors/burnttoast256.vim
@@ -0,0 +1,116 @@
+" Vim color file
+"
+" Name: burnttoast256.vim
+" Version: 1.4
+" Maintainer: Francois Labelle <quantum.omega@gmail.com>
+" Based on: xoria256.vim 1.1 by Dmitriy Y. Zotikov (xio) <xio@ungrund.org>
+"
+" Should work in recent 256 color terminals. 88-color terms like urxvt are
+" unsupported.
+"
+" Don't forget to install 'ncurses-term' and set TERM to xterm-256color or
+" similar value.
+"
+" Color numbers (0-255) see:
+" http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
+
+
+
+" Bla-bla ----------------------------------------------------------------------
+
+if &t_Co != 256 && ! has("gui_running")
+ echomsg ""
+ echomsg "err: please use GUI or a 256-color terminal (so that t_Co=256 can be set)"
+ echomsg ""
+ finish
+endif
+
+set background=dark
+
+hi clear
+
+if exists("syntax_on")
+ syntax reset
+endif
+
+let colors_name = "burnttoast256"
+
+
+" The real part ----------------------------------------------------------------
+
+"" General colors
+hi Normal ctermfg=252 guifg=#d0d0d0 ctermbg=16 guibg=#000000 cterm=none gui=none
+hi ColorColumn ctermbg=233 guibg=#121212
+hi CursorColumn ctermbg=238 guibg=#444444
+hi Cursor ctermbg=214 guibg=#ffaf00
+hi CursorLine cterm=none gui=none
+hi CursorLineNr ctermfg=7 guifg=#c0c0c0 ctermbg=233 guibg=#121212 cterm=bold gui=bold
+hi FoldColumn ctermfg=248 guifg=#a8a8a8 ctermbg=bg guibg=bg
+hi Folded ctermfg=255 guifg=#eeeeee ctermbg=60 guibg=#5f5f87
+hi IncSearch ctermfg=0 guifg=#000000 ctermbg=223 guibg=#ffd7af cterm=none gui=none
+hi LineNr ctermfg=8 guifg=#808080 ctermbg=233 guibg=#121212
+hi NonText ctermfg=237 guifg=#3a3a3a
+hi Pmenu ctermfg=15 guifg=#ffffff ctermbg=234 guibg=#1c1c1c
+hi PmenuSbar ctermbg=243 guibg=#767676
+hi PmenuSel ctermfg=146 guifg=#afafd7 ctermbg=232 guibg=#080808 cterm=bold gui=bold
+hi PmenuThumb ctermbg=252 guibg=#d0d0d0
+hi Search ctermfg=0 guifg=#000000 ctermbg=149 guibg=#afd75f
+hi SignColumn ctermfg=248 guifg=#a8a8a8
+hi SpecialKey ctermfg=237 guifg=#3a3a3a
+hi StatusLine ctermbg=239 guibg=#4e4e4e cterm=bold gui=bold
+hi StatusLineNC ctermbg=237 guibg=#3a3a3a cterm=none gui=none
+hi TabLine ctermfg=fg guifg=fg ctermbg=242 guibg=#6c6c6c cterm=underline gui=underline
+hi TabLineFill ctermfg=fg guifg=fg ctermbg=242 guibg=#6c6c6c cterm=underline gui=underline
+hi VertSplit ctermfg=237 guifg=#3a3a3a ctermbg=237 guibg=#3a3a3a cterm=none gui=none
+hi Visual ctermfg=24 guifg=#005f87 ctermbg=153 guibg=#afd7ff
+hi VIsualNOS ctermfg=24 guifg=#005f87 ctermbg=153 guibg=#afd7ff cterm=none gui=none
+hi WildMenu ctermfg=0 guifg=#000000 ctermbg=184 guibg=#d7d700 cterm=bold gui=bold
+
+"" Syntax highlighting
+hi Comment ctermfg=36 guifg=#00af87
+hi Constant ctermfg=222 guifg=#ffd787
+hi Error ctermfg=9 guifg=#ff0000 ctermbg=52 guibg=#5f0000 cterm=none gui=none
+hi ErrorMsg ctermfg=9 guifg=#ff0000 ctermbg=52 guibg=#5f0000
+hi Function ctermfg=134 guifg=#af5fd7
+hi Identifier ctermfg=134 guifg=#af5fd7 cterm=none gui=none
+hi Ignore ctermfg=238 guifg=#444444
+hi MatchParen ctermfg=188 guifg=#d7d7d7 ctermbg=68 guibg=#5f87d7 cterm=bold gui=bold
+hi Number ctermfg=180 guifg=#d7af87
+hi PreProc ctermfg=150 guifg=#afd787
+hi Special ctermfg=132 guifg=#af5f87
+hi Statement ctermfg=75 guifg=#5fafff cterm=none gui=none
+hi String ctermfg=217 guifg=#ffafaf
+hi Todo ctermfg=0 guifg=#000000 ctermbg=36 guibg=#00af87
+hi Type ctermfg=146 guifg=#afafd7 cterm=none gui=none
+hi Underlined ctermfg=39 guifg=#00afff cterm=underline gui=underline
+
+"" Python highlighting
+hi pythonFunctionCall ctermfg=210 guifg=#ff8787
+"hi pythonStrFormatting Special
+"hi pythonStrFormat Special
+"hi pythonStrTemplate Special
+
+"" Special
+""" .diff
+hi diffAdded ctermfg=150 guifg=#afd787
+hi diffRemoved ctermfg=174 guifg=#d78787
+""" vimdiff
+hi DiffAdd ctermbg=22 guibg=#005f00
+hi DiffDelete ctermfg=9 guifg=#ff0000 ctermbg=52 guibg=#5f0000 cterm=none gui=none
+hi DiffChange ctermbg=235 guibg=#262626 cterm=none gui=none
+hi DiffText ctermbg=17 guibg=#00005f cterm=none gui=none
+
+"" To highlight lines that are too long
+hi OverLength ctermbg=130 guibg=#af5f00
+
+"" Spelling and compiling errors
+hi SpellBad ctermbg=52 guibg=#5f0000
+
+"" Colors for unit testing.
+hi RedBar ctermbg=52 guibg=#5f0000
+hi GreenBar ctermbg=22 guibg=#005f00
+
+" For indentation
+hi IndentGuidesOdd ctermbg=235 guibg=#262626 ctermfg=237 guifg=#3a3a3a
+hi IndentGuidesEven ctermbg=233 guibg=#121212 ctermfg=237 guifg=#3a3a3a
+
diff --git a/stow/vim/dot-vim/colors/nord.vim b/stow/vim/dot-vim/colors/nord.vim
new file mode 100644
index 0000000..6b00afb
--- /dev/null
+++ b/stow/vim/dot-vim/colors/nord.vim
@@ -0,0 +1,764 @@
+" Copyright (C) 2016-present Arctic Ice Studio <development@arcticicestudio.com>
+" Copyright (C) 2016-present Sven Greb <development@svengreb.de>
+
+" Project: Nord Vim
+" Repository: https://github.com/arcticicestudio/nord-vim
+" License: MIT
+
+if version > 580
+ hi clear
+ if exists("syntax_on")
+ syntax reset
+ endif
+endif
+
+let g:colors_name = "nord"
+let s:nord_vim_version="0.15.0"
+set background=dark
+
+let s:nord0_gui = "#2E3440"
+let s:nord1_gui = "#3B4252"
+let s:nord2_gui = "#434C5E"
+let s:nord3_gui = "#4C566A"
+let s:nord3_gui_bright = "#616E88"
+let s:nord4_gui = "#D8DEE9"
+let s:nord5_gui = "#E5E9F0"
+let s:nord6_gui = "#ECEFF4"
+let s:nord7_gui = "#8FBCBB"
+let s:nord8_gui = "#88C0D0"
+let s:nord9_gui = "#81A1C1"
+let s:nord10_gui = "#5E81AC"
+let s:nord11_gui = "#BF616A"
+let s:nord12_gui = "#D08770"
+let s:nord13_gui = "#EBCB8B"
+let s:nord14_gui = "#A3BE8C"
+let s:nord15_gui = "#B48EAD"
+
+let s:nord1_term = "0"
+let s:nord3_term = "8"
+let s:nord5_term = "7"
+let s:nord6_term = "15"
+let s:nord7_term = "14"
+let s:nord8_term = "6"
+let s:nord9_term = "4"
+let s:nord10_term = "12"
+let s:nord11_term = "1"
+let s:nord12_term = "11"
+let s:nord13_term = "3"
+let s:nord14_term = "2"
+let s:nord15_term = "5"
+
+let s:nord3_gui_brightened = [
+ \ s:nord3_gui,
+ \ "#4e586d",
+ \ "#505b70",
+ \ "#525d73",
+ \ "#556076",
+ \ "#576279",
+ \ "#59647c",
+ \ "#5b677f",
+ \ "#5d6982",
+ \ "#5f6c85",
+ \ "#616e88",
+ \ "#63718b",
+ \ "#66738e",
+ \ "#687591",
+ \ "#6a7894",
+ \ "#6d7a96",
+ \ "#6f7d98",
+ \ "#72809a",
+ \ "#75829c",
+ \ "#78859e",
+ \ "#7b88a1",
+\ ]
+
+if !exists("g:nord_bold")
+ let g:nord_bold = 1
+endif
+
+let s:bold = "bold,"
+if g:nord_bold == 0
+ let s:bold = ""
+endif
+
+if !exists("g:nord_italic")
+ if has("gui_running") || $TERM_ITALICS == "true"
+ let g:nord_italic = 1
+ else
+ let g:nord_italic = 0
+ endif
+endif
+
+let s:italic = "italic,"
+if g:nord_italic == 0
+ let s:italic = ""
+endif
+
+let s:underline = "underline,"
+if ! get(g:, "nord_underline", 1)
+ let s:underline = "NONE,"
+endif
+
+let s:italicize_comments = ""
+if exists("g:nord_italic_comments")
+ if g:nord_italic_comments == 1
+ let s:italicize_comments = s:italic
+ endif
+endif
+
+if !exists('g:nord_uniform_status_lines')
+ let g:nord_uniform_status_lines = 0
+endif
+
+function! s:logWarning(msg)
+ echohl WarningMsg
+ echomsg 'nord: warning: ' . a:msg
+ echohl None
+endfunction
+
+if exists("g:nord_comment_brightness")
+ call s:logWarning('Variable g:nord_comment_brightness has been deprecated and will be removed in version 1.0.0!' .
+ \' The comment color brightness has been increased by 10% by default.' .
+ \' Please see https://github.com/arcticicestudio/nord-vim/issues/145 for more details.')
+ let g:nord_comment_brightness = 10
+endif
+
+if !exists("g:nord_uniform_diff_background")
+ let g:nord_uniform_diff_background = 0
+endif
+
+if !exists("g:nord_cursor_line_number_background")
+ let g:nord_cursor_line_number_background = 0
+endif
+
+if !exists("g:nord_bold_vertical_split_line")
+ let g:nord_bold_vertical_split_line = 0
+endif
+
+function! s:hi(group, guifg, guibg, ctermfg, ctermbg, attr, guisp)
+ if a:guifg != ""
+ exec "hi " . a:group . " guifg=" . a:guifg
+ endif
+ if a:guibg != ""
+ exec "hi " . a:group . " guibg=" . a:guibg
+ endif
+ if a:ctermfg != ""
+ exec "hi " . a:group . " ctermfg=" . a:ctermfg
+ endif
+ if a:ctermbg != ""
+ exec "hi " . a:group . " ctermbg=" . a:ctermbg
+ endif
+ if a:attr != ""
+ exec "hi " . a:group . " gui=" . a:attr . " cterm=" . substitute(a:attr, "undercurl", s:underline, "")
+ endif
+ if a:guisp != ""
+ exec "hi " . a:group . " guisp=" . a:guisp
+ endif
+endfunction
+
+"+---------------+
+"+ UI Components +
+"+---------------+
+"+--- Attributes ---+
+call s:hi("Bold", "", "", "", "", s:bold, "")
+call s:hi("Italic", "", "", "", "", s:italic, "")
+call s:hi("Underline", "", "", "", "", s:underline, "")
+
+"+--- Editor ---+
+call s:hi("ColorColumn", "", s:nord1_gui, "NONE", s:nord1_term, "", "")
+call s:hi("Cursor", s:nord0_gui, s:nord4_gui, "", "NONE", "", "")
+call s:hi("CursorLine", "", s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+call s:hi("Error", s:nord4_gui, s:nord11_gui, "", s:nord11_term, "", "")
+call s:hi("iCursor", s:nord0_gui, s:nord4_gui, "", "NONE", "", "")
+call s:hi("LineNr", s:nord3_gui, "NONE", s:nord3_term, "NONE", "", "")
+call s:hi("MatchParen", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "", "")
+call s:hi("NonText", s:nord2_gui, "", s:nord3_term, "", "", "")
+call s:hi("Normal", s:nord4_gui, s:nord0_gui, "NONE", "NONE", "", "")
+call s:hi("PMenu", s:nord4_gui, s:nord2_gui, "NONE", s:nord1_term, "NONE", "")
+call s:hi("PmenuSbar", s:nord4_gui, s:nord2_gui, "NONE", s:nord1_term, "", "")
+call s:hi("PMenuSel", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "", "")
+call s:hi("PmenuThumb", s:nord8_gui, s:nord3_gui, "NONE", s:nord3_term, "", "")
+call s:hi("SpecialKey", s:nord3_gui, "", s:nord3_term, "", "", "")
+call s:hi("SpellBad", s:nord11_gui, s:nord0_gui, s:nord11_term, "NONE", "undercurl", s:nord11_gui)
+call s:hi("SpellCap", s:nord13_gui, s:nord0_gui, s:nord13_term, "NONE", "undercurl", s:nord13_gui)
+call s:hi("SpellLocal", s:nord5_gui, s:nord0_gui, s:nord5_term, "NONE", "undercurl", s:nord5_gui)
+call s:hi("SpellRare", s:nord6_gui, s:nord0_gui, s:nord6_term, "NONE", "undercurl", s:nord6_gui)
+call s:hi("Visual", "", s:nord2_gui, "", s:nord1_term, "", "")
+call s:hi("VisualNOS", "", s:nord2_gui, "", s:nord1_term, "", "")
+"+- Neovim Support -+
+call s:hi("healthError", s:nord11_gui, s:nord1_gui, s:nord11_term, s:nord1_term, "", "")
+call s:hi("healthSuccess", s:nord14_gui, s:nord1_gui, s:nord14_term, s:nord1_term, "", "")
+call s:hi("healthWarning", s:nord13_gui, s:nord1_gui, s:nord13_term, s:nord1_term, "", "")
+call s:hi("TermCursorNC", "", s:nord1_gui, "", s:nord1_term, "", "")
+
+"+- Vim 8 Terminal Colors -+
+if has('terminal')
+ let g:terminal_ansi_colors = [s:nord1_gui, s:nord11_gui, s:nord14_gui, s:nord13_gui, s:nord9_gui, s:nord15_gui, s:nord8_gui, s:nord5_gui, s:nord3_gui, s:nord11_gui, s:nord14_gui, s:nord13_gui, s:nord9_gui, s:nord15_gui, s:nord7_gui, s:nord6_gui]
+endif
+
+"+- Neovim Terminal Colors -+
+if has('nvim')
+ let g:terminal_color_0 = s:nord1_gui
+ let g:terminal_color_1 = s:nord11_gui
+ let g:terminal_color_2 = s:nord14_gui
+ let g:terminal_color_3 = s:nord13_gui
+ let g:terminal_color_4 = s:nord9_gui
+ let g:terminal_color_5 = s:nord15_gui
+ let g:terminal_color_6 = s:nord8_gui
+ let g:terminal_color_7 = s:nord5_gui
+ let g:terminal_color_8 = s:nord3_gui
+ let g:terminal_color_9 = s:nord11_gui
+ let g:terminal_color_10 = s:nord14_gui
+ let g:terminal_color_11 = s:nord13_gui
+ let g:terminal_color_12 = s:nord9_gui
+ let g:terminal_color_13 = s:nord15_gui
+ let g:terminal_color_14 = s:nord7_gui
+ let g:terminal_color_15 = s:nord6_gui
+endif
+
+"+--- Gutter ---+
+call s:hi("CursorColumn", "", s:nord1_gui, "NONE", s:nord1_term, "", "")
+if g:nord_cursor_line_number_background == 0
+ call s:hi("CursorLineNr", s:nord4_gui, "", "NONE", "", "NONE", "")
+else
+ call s:hi("CursorLineNr", s:nord4_gui, s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+endif
+call s:hi("Folded", s:nord3_gui, s:nord1_gui, s:nord3_term, s:nord1_term, s:bold, "")
+call s:hi("FoldColumn", s:nord3_gui, s:nord0_gui, s:nord3_term, "NONE", "", "")
+call s:hi("SignColumn", s:nord1_gui, s:nord0_gui, s:nord1_term, "NONE", "", "")
+
+"+--- Navigation ---+
+call s:hi("Directory", s:nord8_gui, "", s:nord8_term, "NONE", "", "")
+
+"+--- Prompt/Status ---+
+call s:hi("EndOfBuffer", s:nord1_gui, "", s:nord1_term, "NONE", "", "")
+call s:hi("ErrorMsg", s:nord4_gui, s:nord11_gui, "NONE", s:nord11_term, "", "")
+call s:hi("ModeMsg", s:nord4_gui, "", "", "", "", "")
+call s:hi("MoreMsg", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("Question", s:nord4_gui, "", "NONE", "", "", "")
+if g:nord_uniform_status_lines == 0
+ call s:hi("StatusLine", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "NONE", "")
+ call s:hi("StatusLineNC", s:nord4_gui, s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+ call s:hi("StatusLineTerm", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "NONE", "")
+ call s:hi("StatusLineTermNC", s:nord4_gui, s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+else
+ call s:hi("StatusLine", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "NONE", "")
+ call s:hi("StatusLineNC", s:nord4_gui, s:nord3_gui, "NONE", s:nord3_term, "NONE", "")
+ call s:hi("StatusLineTerm", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "NONE", "")
+ call s:hi("StatusLineTermNC", s:nord4_gui, s:nord3_gui, "NONE", s:nord3_term, "NONE", "")
+endif
+call s:hi("WarningMsg", s:nord0_gui, s:nord13_gui, s:nord1_term, s:nord13_term, "", "")
+call s:hi("WildMenu", s:nord8_gui, s:nord1_gui, s:nord8_term, s:nord1_term, "", "")
+
+"+--- Search ---+
+call s:hi("IncSearch", s:nord6_gui, s:nord10_gui, s:nord6_term, s:nord10_term, s:underline, "")
+call s:hi("Search", s:nord1_gui, s:nord8_gui, s:nord1_term, s:nord8_term, "NONE", "")
+
+"+--- Tabs ---+
+call s:hi("TabLine", s:nord4_gui, s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+call s:hi("TabLineFill", s:nord4_gui, s:nord1_gui, "NONE", s:nord1_term, "NONE", "")
+call s:hi("TabLineSel", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, "NONE", "")
+
+"+--- Window ---+
+call s:hi("Title", s:nord4_gui, "", "NONE", "", "NONE", "")
+
+if g:nord_bold_vertical_split_line == 0
+ call s:hi("VertSplit", s:nord2_gui, s:nord0_gui, s:nord3_term, "NONE", "NONE", "")
+else
+ call s:hi("VertSplit", s:nord2_gui, s:nord1_gui, s:nord3_term, s:nord1_term, "NONE", "")
+endif
+
+"+----------------------+
+"+ Language Base Groups +
+"+----------------------+
+call s:hi("Boolean", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Character", s:nord14_gui, "", s:nord14_term, "", "", "")
+call s:hi("Comment", s:nord3_gui_bright, "", s:nord3_term, "", s:italicize_comments, "")
+call s:hi("Conditional", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Constant", s:nord4_gui, "", "NONE", "", "", "")
+call s:hi("Define", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Delimiter", s:nord6_gui, "", s:nord6_term, "", "", "")
+call s:hi("Exception", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Float", s:nord15_gui, "", s:nord15_term, "", "", "")
+call s:hi("Function", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("Identifier", s:nord4_gui, "", "NONE", "", "NONE", "")
+call s:hi("Include", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Keyword", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Label", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Number", s:nord15_gui, "", s:nord15_term, "", "", "")
+call s:hi("Operator", s:nord9_gui, "", s:nord9_term, "", "NONE", "")
+call s:hi("PreProc", s:nord9_gui, "", s:nord9_term, "", "NONE", "")
+call s:hi("Repeat", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Special", s:nord4_gui, "", "NONE", "", "", "")
+call s:hi("SpecialChar", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("SpecialComment", s:nord8_gui, "", s:nord8_term, "", s:italicize_comments, "")
+call s:hi("Statement", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("StorageClass", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("String", s:nord14_gui, "", s:nord14_term, "", "", "")
+call s:hi("Structure", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("Tag", s:nord4_gui, "", "", "", "", "")
+call s:hi("Todo", s:nord13_gui, "NONE", s:nord13_term, "NONE", "", "")
+call s:hi("Type", s:nord9_gui, "", s:nord9_term, "", "NONE", "")
+call s:hi("Typedef", s:nord9_gui, "", s:nord9_term, "", "", "")
+hi! link Macro Define
+hi! link PreCondit PreProc
+
+"+-----------+
+"+ Languages +
+"+-----------+
+call s:hi("asciidocAttributeEntry", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("asciidocAttributeList", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("asciidocAttributeRef", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("asciidocHLabel", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("asciidocListingBlock", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("asciidocMacroAttributes", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("asciidocOneLineTitle", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("asciidocPassthroughBlock", s:nord9_gui, "", s:nord9_term, "", "", "")
+call s:hi("asciidocQuotedMonospaced", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("asciidocTriplePlusPassthrough", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link asciidocAdmonition Keyword
+hi! link asciidocAttributeRef markdownH1
+hi! link asciidocBackslash Keyword
+hi! link asciidocMacro Keyword
+hi! link asciidocQuotedBold Bold
+hi! link asciidocQuotedEmphasized Italic
+hi! link asciidocQuotedMonospaced2 asciidocQuotedMonospaced
+hi! link asciidocQuotedUnconstrainedBold asciidocQuotedBold
+hi! link asciidocQuotedUnconstrainedEmphasized asciidocQuotedEmphasized
+hi! link asciidocURL markdownLinkText
+
+call s:hi("awkCharClass", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("awkPatterns", s:nord9_gui, "", s:nord9_term, "", s:bold, "")
+hi! link awkArrayElement Identifier
+hi! link awkBoolLogic Keyword
+hi! link awkBrktRegExp SpecialChar
+hi! link awkComma Delimiter
+hi! link awkExpression Keyword
+hi! link awkFieldVars Identifier
+hi! link awkLineSkip Keyword
+hi! link awkOperator Operator
+hi! link awkRegExp SpecialChar
+hi! link awkSearch Keyword
+hi! link awkSemicolon Delimiter
+hi! link awkSpecialCharacter SpecialChar
+hi! link awkSpecialPrintf SpecialChar
+hi! link awkVariables Identifier
+
+call s:hi("cIncluded", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link cOperator Operator
+hi! link cPreCondit PreCondit
+
+call s:hi("cmakeGeneratorExpression", s:nord10_gui, "", s:nord10_term, "", "", "")
+
+hi! link csPreCondit PreCondit
+hi! link csType Type
+hi! link csXmlTag SpecialComment
+
+call s:hi("cssAttributeSelector", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("cssDefinition", s:nord7_gui, "", s:nord7_term, "", "NONE", "")
+call s:hi("cssIdentifier", s:nord7_gui, "", s:nord7_term, "", s:underline, "")
+call s:hi("cssStringQ", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link cssAttr Keyword
+hi! link cssBraces Delimiter
+hi! link cssClassName cssDefinition
+hi! link cssColor Number
+hi! link cssProp cssDefinition
+hi! link cssPseudoClass cssDefinition
+hi! link cssPseudoClassId cssPseudoClass
+hi! link cssVendor Keyword
+
+call s:hi("dosiniHeader", s:nord8_gui, "", s:nord8_term, "", "", "")
+hi! link dosiniLabel Type
+
+call s:hi("dtBooleanKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("dtExecKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("dtLocaleKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("dtNumericKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("dtTypeKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link dtDelim Delimiter
+hi! link dtLocaleValue Keyword
+hi! link dtTypeValue Keyword
+
+if g:nord_uniform_diff_background == 0
+ call s:hi("DiffAdd", s:nord14_gui, s:nord0_gui, s:nord14_term, "NONE", "inverse", "")
+ call s:hi("DiffChange", s:nord13_gui, s:nord0_gui, s:nord13_term, "NONE", "inverse", "")
+ call s:hi("DiffDelete", s:nord11_gui, s:nord0_gui, s:nord11_term, "NONE", "inverse", "")
+ call s:hi("DiffText", s:nord9_gui, s:nord0_gui, s:nord9_term, "NONE", "inverse", "")
+else
+ call s:hi("DiffAdd", s:nord14_gui, s:nord1_gui, s:nord14_term, s:nord1_term, "", "")
+ call s:hi("DiffChange", s:nord13_gui, s:nord1_gui, s:nord13_term, s:nord1_term, "", "")
+ call s:hi("DiffDelete", s:nord11_gui, s:nord1_gui, s:nord11_term, s:nord1_term, "", "")
+ call s:hi("DiffText", s:nord9_gui, s:nord1_gui, s:nord9_term, s:nord1_term, "", "")
+endif
+" Legacy groups for official git.vim and diff.vim syntax
+hi! link diffAdded DiffAdd
+hi! link diffChanged DiffChange
+hi! link diffRemoved DiffDelete
+
+call s:hi("gitconfigVariable", s:nord7_gui, "", s:nord7_term, "", "", "")
+
+call s:hi("goBuiltins", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link goConstants Keyword
+
+call s:hi("helpBar", s:nord3_gui, "", s:nord3_term, "", "", "")
+call s:hi("helpHyperTextJump", s:nord8_gui, "", s:nord8_term, "", s:underline, "")
+
+call s:hi("htmlArg", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("htmlLink", s:nord4_gui, "", "", "", "NONE", "NONE")
+hi! link htmlBold Bold
+hi! link htmlEndTag htmlTag
+hi! link htmlItalic Italic
+hi! link htmlH1 markdownH1
+hi! link htmlH2 markdownH1
+hi! link htmlH3 markdownH1
+hi! link htmlH4 markdownH1
+hi! link htmlH5 markdownH1
+hi! link htmlH6 markdownH1
+hi! link htmlSpecialChar SpecialChar
+hi! link htmlTag Keyword
+hi! link htmlTagN htmlTag
+
+call s:hi("javaDocTags", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link javaCommentTitle Comment
+hi! link javaScriptBraces Delimiter
+hi! link javaScriptIdentifier Keyword
+hi! link javaScriptNumber Number
+
+call s:hi("jsonKeyword", s:nord7_gui, "", s:nord7_term, "", "", "")
+
+call s:hi("lessClass", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link lessAmpersand Keyword
+hi! link lessCssAttribute Delimiter
+hi! link lessFunction Function
+hi! link cssSelectorOp Keyword
+
+hi! link lispAtomBarSymbol SpecialChar
+hi! link lispAtomList SpecialChar
+hi! link lispAtomMark Keyword
+hi! link lispBarSymbol SpecialChar
+hi! link lispFunc Function
+
+hi! link luaFunc Function
+
+call s:hi("markdownBlockquote", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownCode", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownCodeDelimiter", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownFootnote", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownId", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownIdDeclaration", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("markdownH1", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("markdownLinkText", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("markdownUrl", s:nord4_gui, "", "NONE", "", "NONE", "")
+hi! link markdownBold Bold
+hi! link markdownBoldDelimiter Keyword
+hi! link markdownFootnoteDefinition markdownFootnote
+hi! link markdownH2 markdownH1
+hi! link markdownH3 markdownH1
+hi! link markdownH4 markdownH1
+hi! link markdownH5 markdownH1
+hi! link markdownH6 markdownH1
+hi! link markdownIdDelimiter Keyword
+hi! link markdownItalic Italic
+hi! link markdownItalicDelimiter Keyword
+hi! link markdownLinkDelimiter Keyword
+hi! link markdownLinkTextDelimiter Keyword
+hi! link markdownListMarker Keyword
+hi! link markdownRule Keyword
+hi! link markdownHeadingDelimiter Keyword
+
+call s:hi("perlPackageDecl", s:nord7_gui, "", s:nord7_term, "", "", "")
+
+call s:hi("phpClasses", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("phpDocTags", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link phpDocCustomTags phpDocTags
+hi! link phpMemberSelector Keyword
+
+call s:hi("podCmdText", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("podVerbatimLine", s:nord4_gui, "", "NONE", "", "", "")
+hi! link podFormat Keyword
+
+hi! link pythonBuiltin Type
+hi! link pythonEscape SpecialChar
+
+call s:hi("rubyConstant", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("rubySymbol", s:nord6_gui, "", s:nord6_term, "", s:bold, "")
+hi! link rubyAttribute Identifier
+hi! link rubyBlockParameterList Operator
+hi! link rubyInterpolationDelimiter Keyword
+hi! link rubyKeywordAsMethod Function
+hi! link rubyLocalVariableOrMethod Function
+hi! link rubyPseudoVariable Keyword
+hi! link rubyRegexp SpecialChar
+
+call s:hi("rustAttribute", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("rustEnum", s:nord7_gui, "", s:nord7_term, "", s:bold, "")
+call s:hi("rustMacro", s:nord8_gui, "", s:nord8_term, "", s:bold, "")
+call s:hi("rustModPath", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("rustPanic", s:nord9_gui, "", s:nord9_term, "", s:bold, "")
+call s:hi("rustTrait", s:nord7_gui, "", s:nord7_term, "", s:italic, "")
+hi! link rustCommentLineDoc Comment
+hi! link rustDerive rustAttribute
+hi! link rustEnumVariant rustEnum
+hi! link rustEscape SpecialChar
+hi! link rustQuestionMark Keyword
+
+call s:hi("sassClass", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("sassId", s:nord7_gui, "", s:nord7_term, "", s:underline, "")
+hi! link sassAmpersand Keyword
+hi! link sassClassChar Delimiter
+hi! link sassControl Keyword
+hi! link sassControlLine Keyword
+hi! link sassExtend Keyword
+hi! link sassFor Keyword
+hi! link sassFunctionDecl Keyword
+hi! link sassFunctionName Function
+hi! link sassidChar sassId
+hi! link sassInclude SpecialChar
+hi! link sassMixinName Function
+hi! link sassMixing SpecialChar
+hi! link sassReturn Keyword
+
+hi! link shCmdParenRegion Delimiter
+hi! link shCmdSubRegion Delimiter
+hi! link shDerefSimple Identifier
+hi! link shDerefVar Identifier
+
+hi! link sqlKeyword Keyword
+hi! link sqlSpecial Keyword
+
+call s:hi("vimAugroup", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("vimMapRhs", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("vimNotation", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link vimFunc Function
+hi! link vimFunction Function
+hi! link vimUserFunc Function
+
+call s:hi("xmlAttrib", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("xmlCdataStart", s:nord3_gui_bright, "", s:nord3_term, "", s:bold, "")
+call s:hi("xmlNamespace", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link xmlAttribPunct Delimiter
+hi! link xmlCdata Comment
+hi! link xmlCdataCdata xmlCdataStart
+hi! link xmlCdataEnd xmlCdataStart
+hi! link xmlEndTag xmlTagName
+hi! link xmlProcessingDelim Keyword
+hi! link xmlTagName Keyword
+
+call s:hi("yamlBlockMappingKey", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link yamlBool Keyword
+hi! link yamlDocumentStart Keyword
+
+"+----------------+
+"+ Plugin Support +
+"+----------------+
+"+--- UI ---+
+" ALE
+" > w0rp/ale
+call s:hi("ALEWarningSign", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("ALEErrorSign" , s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("ALEWarning" , s:nord13_gui, "", s:nord13_term, "", "undercurl", "")
+call s:hi("ALEError" , s:nord11_gui, "", s:nord11_term, "", "undercurl", "")
+
+" Coc
+" > neoclide/coc
+call s:hi("CocWarningSign", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("CocErrorSign" , s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("CocInfoSign" , s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("CocHintSign" , s:nord10_gui, "", s:nord10_term, "", "", "")
+
+" Nvim LSP
+" > neovim/nvim-lsp
+call s:hi("LSPDiagnosticsWarning", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("LSPDiagnosticsError" , s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("LSPDiagnosticsInformation" , s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("LSPDiagnosticsHint" , s:nord10_gui, "", s:nord10_term, "", "", "")
+
+" GitGutter
+" > airblade/vim-gitgutter
+call s:hi("GitGutterAdd", s:nord14_gui, "", s:nord14_term, "", "", "")
+call s:hi("GitGutterChange", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("GitGutterChangeDelete", s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("GitGutterDelete", s:nord11_gui, "", s:nord11_term, "", "", "")
+
+" Signify
+" > mhinz/vim-signify
+call s:hi("SignifySignAdd", s:nord14_gui, "", s:nord14_term, "", "", "")
+call s:hi("SignifySignChange", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("SignifySignChangeDelete", s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("SignifySignDelete", s:nord11_gui, "", s:nord11_term, "", "", "")
+
+" fugitive.vim
+" > tpope/vim-fugitive
+call s:hi("gitcommitDiscardedFile", s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("gitcommitUntrackedFile", s:nord11_gui, "", s:nord11_term, "", "", "")
+call s:hi("gitcommitSelectedFile", s:nord14_gui, "", s:nord14_term, "", "", "")
+
+" davidhalter/jedi-vim
+call s:hi("jediFunction", s:nord4_gui, s:nord3_gui, "", s:nord3_term, "", "")
+call s:hi("jediFat", s:nord8_gui, s:nord3_gui, s:nord8_term, s:nord3_term, s:underline.s:bold, "")
+
+" NERDTree
+" > scrooloose/nerdtree
+call s:hi("NERDTreeExecFile", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link NERDTreeDirSlash Keyword
+hi! link NERDTreeHelp Comment
+
+" CtrlP
+" > ctrlpvim/ctrlp.vim
+hi! link CtrlPMatch Keyword
+hi! link CtrlPBufferHid Normal
+
+" vim-clap
+" > liuchengxu/vim-clap
+call s:hi("ClapDir", s:nord4_gui, "", "", "", "", "")
+call s:hi("ClapDisplay", s:nord4_gui, s:nord1_gui, "", s:nord1_term, "", "")
+call s:hi("ClapFile", s:nord4_gui, "", "", "NONE", "", "")
+call s:hi("ClapMatches", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("ClapNoMatchesFound", s:nord13_gui, "", s:nord13_term, "", "", "")
+call s:hi("ClapSelected", s:nord7_gui, "", s:nord7_term, "", s:bold, "")
+call s:hi("ClapSelectedSign", s:nord9_gui, "", s:nord9_term, "", "", "")
+
+let s:clap_matches = [
+ \ [s:nord8_gui, s:nord8_term] ,
+ \ [s:nord9_gui, s:nord9_term] ,
+ \ [s:nord10_gui, s:nord10_term] ,
+ \ ]
+for s:nord_clap_match_i in range(1,12)
+ let clap_match_color = s:clap_matches[s:nord_clap_match_i % len(s:clap_matches) - 1]
+ call s:hi("ClapMatches" . s:nord_clap_match_i, clap_match_color[0], "", clap_match_color[1], "", "", "")
+ call s:hi("ClapFuzzyMatches" . s:nord_clap_match_i, clap_match_color[0], "", clap_match_color[1], "", "", "")
+endfor
+unlet s:nord_clap_match_i
+
+hi! link ClapCurrentSelection PmenuSel
+hi! link ClapCurrentSelectionSign ClapSelectedSign
+hi! link ClapInput Pmenu
+hi! link ClapPreview Pmenu
+hi! link ClapProviderAbout ClapDisplay
+hi! link ClapProviderColon Type
+hi! link ClapProviderId Type
+
+" vim-plug
+" > junegunn/vim-plug
+call s:hi("plugDeleted", s:nord11_gui, "", "", s:nord11_term, "", "")
+
+" vim-signature
+" > kshenoy/vim-signature
+call s:hi("SignatureMarkText", s:nord8_gui, "", s:nord8_term, "", "", "")
+
+" vim-startify
+" > mhinz/vim-startify
+call s:hi("StartifyFile", s:nord6_gui, "", s:nord6_term, "", "", "")
+call s:hi("StartifyFooter", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("StartifyHeader", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("StartifyNumber", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("StartifyPath", s:nord8_gui, "", s:nord8_term, "", "", "")
+hi! link StartifyBracket Delimiter
+hi! link StartifySlash Normal
+hi! link StartifySpecial Comment
+
+"+--- Languages ---+
+" Haskell
+" > neovimhaskell/haskell-vim
+call s:hi("haskellPreProc", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("haskellType", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link haskellPragma haskellPreProc
+
+" JavaScript
+" > pangloss/vim-javascript
+call s:hi("jsGlobalNodeObjects", s:nord8_gui, "", s:nord8_term, "", s:italic, "")
+hi! link jsBrackets Delimiter
+hi! link jsFuncCall Function
+hi! link jsFuncParens Delimiter
+hi! link jsThis Keyword
+hi! link jsNoise Delimiter
+hi! link jsPrototype Keyword
+hi! link jsRegexpString SpecialChar
+
+" TypeScript
+" > HerringtonDarkholme/yats.vim
+call s:hi("typescriptBOMWindowMethod", s:nord8_gui, "", s:nord8_term, "", s:italic, "")
+call s:hi("typescriptClassName", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("typescriptDecorator", s:nord12_gui, "", s:nord12_term, "", "", "")
+call s:hi("typescriptInterfaceName", s:nord7_gui, "", s:nord7_term, "", s:bold, "")
+call s:hi("typescriptRegexpString", s:nord13_gui, "", s:nord13_term, "", "", "")
+" TypeScript JSX
+ call s:hi("tsxAttrib", s:nord7_gui, "", s:nord7_term, "", "", "")
+hi! link typescriptOperator Operator
+hi! link typescriptBinaryOp Operator
+hi! link typescriptAssign Operator
+hi! link typescriptMember Identifier
+hi! link typescriptDOMStorageMethod Identifier
+hi! link typescriptArrowFuncArg Identifier
+hi! link typescriptGlobal typescriptClassName
+hi! link typescriptBOMWindowProp Function
+hi! link typescriptArrowFuncDef Function
+hi! link typescriptAliasDeclaration Function
+hi! link typescriptPredefinedType Type
+hi! link typescriptTypeReference typescriptClassName
+hi! link typescriptTypeAnnotation Structure
+hi! link typescriptDocNamedParamType SpecialComment
+hi! link typescriptDocNotation Keyword
+hi! link typescriptDocTags Keyword
+hi! link typescriptImport Keyword
+hi! link typescriptExport Keyword
+hi! link typescriptTry Keyword
+hi! link typescriptVariable Keyword
+hi! link typescriptBraces Normal
+hi! link typescriptObjectLabel Normal
+hi! link typescriptCall Normal
+hi! link typescriptClassHeritage typescriptClassName
+hi! link typescriptFuncTypeArrow Structure
+hi! link typescriptMemberOptionality Structure
+hi! link typescriptNodeGlobal typescriptGlobal
+hi! link typescriptTypeBrackets Structure
+hi! link tsxEqual Operator
+hi! link tsxIntrinsicTagName htmlTag
+hi! link tsxTagName tsxIntrinsicTagName
+
+" Markdown
+" > plasticboy/vim-markdown
+call s:hi("mkdCode", s:nord7_gui, "", s:nord7_term, "", "", "")
+call s:hi("mkdFootnote", s:nord8_gui, "", s:nord8_term, "", "", "")
+call s:hi("mkdRule", s:nord10_gui, "", s:nord10_term, "", "", "")
+call s:hi("mkdLineBreak", s:nord9_gui, "", s:nord9_term, "", "", "")
+hi! link mkdBold Bold
+hi! link mkdItalic Italic
+hi! link mkdString Keyword
+hi! link mkdCodeStart mkdCode
+hi! link mkdCodeEnd mkdCode
+hi! link mkdBlockquote Comment
+hi! link mkdListItem Keyword
+hi! link mkdListItemLine Normal
+hi! link mkdFootnotes mkdFootnote
+hi! link mkdLink markdownLinkText
+hi! link mkdURL markdownUrl
+hi! link mkdInlineURL mkdURL
+hi! link mkdID Identifier
+hi! link mkdLinkDef mkdLink
+hi! link mkdLinkDefTarget mkdURL
+hi! link mkdLinkTitle mkdInlineURL
+hi! link mkdDelimiter Keyword
+
+" Vimwiki
+" > vimwiki/vimwiki
+if !exists("g:vimwiki_hl_headers") || g:vimwiki_hl_headers == 0
+ for s:i in range(1,6)
+ call s:hi("VimwikiHeader".s:i, s:nord8_gui, "", s:nord8_term, "", s:bold, "")
+ endfor
+else
+ let s:vimwiki_hcolor_guifg = [s:nord7_gui, s:nord8_gui, s:nord9_gui, s:nord10_gui, s:nord14_gui, s:nord15_gui]
+ let s:vimwiki_hcolor_ctermfg = [s:nord7_term, s:nord8_term, s:nord9_term, s:nord10_term, s:nord14_term, s:nord15_term]
+ for s:i in range(1,6)
+ call s:hi("VimwikiHeader".s:i, s:vimwiki_hcolor_guifg[s:i-1] , "", s:vimwiki_hcolor_ctermfg[s:i-1], "", s:bold, "")
+ endfor
+endif
+
+call s:hi("VimwikiLink", s:nord8_gui, "", s:nord8_term, "", s:underline, "")
+hi! link VimwikiHeaderChar markdownHeadingDelimiter
+hi! link VimwikiHR Keyword
+hi! link VimwikiList markdownListMarker
+
+" YAML
+" > stephpy/vim-yaml
+call s:hi("yamlKey", s:nord7_gui, "", s:nord7_term, "", "", "")
diff --git a/stow/vim/dot-vim/ftplugin/antlr/antlr3.vim b/stow/vim/dot-vim/ftplugin/antlr/antlr3.vim
new file mode 100644
index 0000000..10e24e5
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/antlr/antlr3.vim
@@ -0,0 +1,91 @@
+" vim: ts=8
+" Vim syntax file
+" Language: ANTLRv3
+" Maintainer: Jörn Horstmann (updated by Davyd Madeley)
+" Last Change: 2008-11-21
+
+" For version 5.x: Clear all syntax items
+" For version 6.x: Quit when a syntax file was already loaded
+if version < 600
+ syntax clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+syn keyword antlrKeyword grammar lexer parser tree header members options fragment returns throws scope init
+
+syn match antlrCharacter '\\\(r\|n\|t\|f\|b\|"\|\'\|\\\|u\x\{4}\)' contained display
+
+syn match antlrToken "\<[A-Z_][a-zA-Z_0-9]\+\>"
+syn match antlrRule "[a-z][a-zA-Z_0-9]\+"
+syn match antlrScopeVariable '$\k\+::\k\+'
+
+syn match antlrOperator "[:;@.]"
+syn match antlrOperator "[()]"
+syn match antlrOperator "[?+*~|!]"
+syn match antlrOperator "[->=^]"
+
+syn match antlrBrace "[{}]"
+syn match antlrBrace "[\[\]]"
+syn region antlrAction matchgroup=antlrBrace start="[{\[]" end="[}\]]" contains=antlrVariable,antlrLiteral,antlrComment
+syn match antlrVariable contained "$[a-zA-Z][a-zA-Z_0-9]*"
+
+syn region antlrLiteral start=+'+ end=+'+ contains=antlrCharacter
+syn region antlrLiteral start=+"+ end=+"+ contains=antlrCharacter
+
+syn region antlrComment start="/\*" end="\*/"
+syn match antlrComment "//.*$"
+
+" Define the default highlighting.
+" For version 5.7 and earlier: only when not done already
+" For version 5.8 and later: only when an item doesn't have highlighting yet
+if version >= 508
+ if version < 508
+ let did_antlr_syntax_inits = 1
+ command -nargs=+ HiLink hi link <args>
+ else
+ command -nargs=+ HiLink hi def link <args>
+ endif
+
+ HiLink antlrLiteral String
+ hi def antlrVariable term=bold cterm=bold gui=bold
+ HiLink antlrBrace Operator
+ HiLink antlrCharacter Special
+ HiLink antlrComment Comment
+ HiLink antlrOperator Operator
+ HiLink antlrKeyword Keyword
+ HiLink antlrToken PreProc
+ HiLink antlrScopeVariable Identifier
+ HiLink antlrRule Type
+
+ delcommand HiLink
+endif
+
+" try to figure out the target language
+let target_languages = []
+" can we figure out from the file name
+let extensions = split(expand('%:t'), '\.')
+if len(extensions) >= 3
+ " great.. the target language was provided via file name
+ call add(target_languages, tolower(extensions[-2]))
+else
+ " no multiple extensions... have to figure out from the content
+ let buffer = join(getline(1, line('$')), "\n")
+ let antlr_options = matchstr(buffer, '\m\(\_s\|;\)options\_s\+{\_.\{-\}}')
+ call substitute(antlr_options, '\mlanguage\_s*=\_s*''\?\(.\{-\}\)''\?\_s*;', '\=add(target_languages, tolower(submatch(1)))', 'g')
+endif
+if exists('target_languages[-1]')
+ " the last defined target language takes precendece
+ let lang=target_languages[-1]
+ let syntax_file = findfile('syntax/' . lang . '.vim', &rtp, 1)
+ if syntax_file != ''
+ " if a valid syntax file was found only
+ let b:current_syntax = ''
+ unlet b:current_syntax
+ let region_name = '@' . lang . 'Language'
+ exe 'syntax include ' . region_name . ' ' . syntax_file
+ exe 'syntax region ' . lang . ' start="{" end="}" keepend contains=' . region_name
+ endif
+endif
+
+let b:current_syntax = "antlr3"
diff --git a/stow/vim/dot-vim/ftplugin/antlr/antlr4.vim b/stow/vim/dot-vim/ftplugin/antlr/antlr4.vim
new file mode 100644
index 0000000..456076c
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/antlr/antlr4.vim
@@ -0,0 +1,76 @@
+" vim: ts=8
+" Vim syntax file
+" Language: ANTLRv4
+" Maintainer: Jörn Horstmann (updated by Dylon Edwards)
+" Last Change: 2015-12-15
+
+" For version 5.x: Clear all syntax items
+" For version 6.x: Quit when a syntax file was already loaded
+if version < 600
+ syntax clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+syn keyword antlrKeyword import fragment lexer parser grammar returns locals throws catch finally mode options tokens header members init after channels mode protected public private
+
+syn keyword antlrReserved rule
+
+syn match antlrCharacter '\\\(r\|n\|t\|f\|b\|"\|\'\|\\\|u\x\{4}\)' contained display
+
+syn match antlrToken "\<[A-Z_][a-zA-Z_0-9]\+\>"
+syn match antlrRule "[a-z][a-zA-Z_0-9]\+"
+syn match antlrScopeVariable '$\k\+::\k\+'
+
+syn match antlrOperator "[:;@.]"
+syn match antlrOperator "[()]"
+syn match antlrOperator "[?+*~|!]"
+syn match antlrOperator "[->=^]"
+
+syn match antlrBlock "[{}]"
+syn region antlrAction matchgroup=antlrBlock start="{" end="}" contains=antlrVariable,antlrAction
+syn match antlrVariable contained "$[a-zA-Z][a-zA-Z_0-9]*"
+
+syn include @JAVA syntax/java.vim
+syn region ANTLR4EmbeddedJavaAction matchgroup=antlrBlock start="{" end="}" contains=@JAVA,antlrVariable containedin=antlrAction
+
+syn keyword antlrOperations pushMode popMode skip channel
+
+syn match antlrBrace "[\[\]]"
+syn region antlrCharClass matchgroup=antlrBrace start="\[" end="\]" contains=antlrCharacter
+
+syn region antlrLiteral start=+'+ end=+'+ contains=antlrCharacter
+syn region antlrLiteral start=+"+ end=+"+ contains=antlrCharacter
+
+syn region antlrComment start="/\*" end="\*/"
+syn match antlrComment "//.*$"
+
+" Define the default highlighting.
+" For version 5.7 and earlier: only when not done already
+" For version 5.8 and later: only when an item doesn't have highlighting yet
+if version >= 508
+ if version < 508
+ let did_antlr_syntax_inits = 1
+ command -nargs=+ HiLink hi link <args>
+ else
+ command -nargs=+ HiLink hi def link <args>
+ endif
+
+ HiLink antlrReserved Error
+ HiLink antlrOperations Identifier
+ HiLink antlrLiteral String
+ hi def antlrVariable term=bold cterm=bold gui=bold
+ HiLink antlrBrace Operator
+ HiLink antlrBlock Operator
+ HiLink antlrCharacter Special
+ HiLink antlrComment Comment
+ HiLink antlrOperator Operator
+ HiLink antlrKeyword Keyword
+ HiLink antlrToken PreProc
+ HiLink antlrScopeVariable Identifier
+ HiLink antlrRule Type
+
+ delcommand HiLink
+endif
+
+let b:current_syntax = "antlr4"
diff --git a/stow/vim/dot-vim/ftplugin/asciidoc/asciidoc.vim b/stow/vim/dot-vim/ftplugin/asciidoc/asciidoc.vim
new file mode 100644
index 0000000..b94b875
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/asciidoc/asciidoc.vim
@@ -0,0 +1,19 @@
+" ~/.vim/ftplugin/asciidoc/asciidoc.vim
+" asciidoc vim configuration
+"
+" Syntastic linter: proselint
+
+" Tabs hate: tabs are expanded to 2 spaces
+setlocal tabstop=2 shiftwidth=2 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 80 characters
+" call matchadd('ColorColumn', '\%81v', 100)
+setlocal textwidth=80
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/asciidoc/asciidoc.vim<CR>
+
+" Proselint is slow to act
+nnoremap <buffer> <leader>sp :SyntasticCheck proselint<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
diff --git a/stow/vim/dot-vim/ftplugin/cpp/cpp.vim b/stow/vim/dot-vim/ftplugin/cpp/cpp.vim
new file mode 100644
index 0000000..e2072c1
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/cpp/cpp.vim
@@ -0,0 +1,28 @@
+" ~/.vim/ftplugin/cpp/cpp.vim
+" C++-specific vim configuration
+
+let mapleader = " "
+
+" Column marker: 80 characters
+match ColorColumn /\%81v/
+setlocal textwidth=80
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/cpp/cpp.vim<CR>
+nnoremap <buffer> <leader>E :Errors<CR>
+
+nnoremap <buffer> <leader>c :w<CR>:!g++ "%"; ./a.out<Space>
+
+nnoremap <buffer> gd :YcmCompleter GoTo<CR>
+nnoremap <buffer> gr :YcmCompleter GoToReferences<CR>
+nnoremap <buffer> gh :YcmCompleter GetType<CR>
+nnoremap <buffer> gD :YcmCompleter GetDoc<CR>
+nnoremap <buffer> gR :YcmCompleter RefactorRename<Space>
+nnoremap <buffer> <leader>fi :YcmCompleter FixIt<CR>
+nnoremap <buffer> <leader>e :YcmDiags<CR>
+
+nnoremap <buffer> <leader>temp :-1r ~/.vim/ftplugin/cpp/snips/template.txt<CR>Gdd?<+++><CR>cf>
+
+let mapleader = ","
+
+inoremap <buffer> <leader>if if (<+++>) {<CR><++><CR>}?<+++><CR>cf>
+inoremap <buffer> <leader>for for (<+++>; <++>; <++>) {<CR><++><CR>}?<+++><CR>cf>
diff --git a/stow/vim/dot-vim/ftplugin/cpp/snips/template.txt b/stow/vim/dot-vim/ftplugin/cpp/snips/template.txt
new file mode 100644
index 0000000..90ef863
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/cpp/snips/template.txt
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main() {
+ <+++>
+ return 0;
+}
diff --git a/stow/vim/dot-vim/ftplugin/css/css.vim b/stow/vim/dot-vim/ftplugin/css/css.vim
new file mode 100644
index 0000000..695243c
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/css/css.vim
@@ -0,0 +1,20 @@
+" ~/.vim/ftplugin/css/css.vim
+" CSS-specific vim configuration
+"
+" Syntastic linters: csslint, prettycss, sstylelint
+
+"let g:syntastic_css_csslint_args = "--ignore=order-alphabetical,ids"
+
+setlocal textwidth=80
+call matchadd('ColorColumn', '\%81v', 100)
+
+" Tabs are expanded to 2 spaces
+setlocal tabstop=2 shiftwidth=2 expandtab
+" Don't show existing tabs
+setlocal nolist
+
+" Mappings
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/css/css.vim<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
+nnoremap <buffer> <leader>s vi{!sort<CR>
+
diff --git a/stow/vim/dot-vim/ftplugin/html/html.vim b/stow/vim/dot-vim/ftplugin/html/html.vim
new file mode 100644
index 0000000..41890b4
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/html/html.vim
@@ -0,0 +1,78 @@
+" ~/.vim/ftplugin/html/html.vim
+" HTML-specific vim configuration
+"
+" Syntastic linter: tidy
+
+" Tabs are expanded to 2 spaces
+setlocal tabstop=2 shiftwidth=2 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 100 characters
+call matchadd('ColorColumn', '\%101v', 100)
+setlocal textwidth=100
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>o :!$BROWSER % &<CR><CR>
+nnoremap <buffer> <leader>f :!firefox --new-window % &<CR><CR>
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/html/html.vim<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
+
+nnoremap <buffer> <leader>temp :-1r ~/.vim/ftplugin/html/snips/template.txt<CR>Gdd?<+++><CR>cf>
+
+let mapleader = ","
+
+" Layout elements
+inoremap <buffer> <leader>hheader <header><Enter><Enter></header><Enter><++><Esc>kki
+inoremap <buffer> <leader>hnav <nav><Enter><Enter></nav><Enter><++><Esc>kki
+inoremap <buffer> <leader>hsection <section><Enter><Enter></section><Enter><++><Esc>kki
+inoremap <buffer> <leader>harticle <article><Enter><Enter></article><Enter><++><Esc>kki
+inoremap <buffer> <leader>hfooter <footer><Enter><Enter></footer><Enter><++><Esc>kki
+inoremap <buffer> <leader>haside <aside><Enter><Enter></aside><Enter><++><Esc>kki
+inoremap <buffer> <leader>hdiv <div class=""><Enter><++><Enter></div><Enter><++><Esc>?""<Enter>a
+
+" Headers and paragraphs
+inoremap <buffer> <leader>hh1 <h1></h1><Enter><++><Esc>?</h1><Enter>i
+inoremap <buffer> <leader>hh2 <h2></h2><Enter><++><Esc>?</h2><Enter>i
+inoremap <buffer> <leader>hh3 <h3></h3><Enter><++><Esc>?</h3><Enter>i
+inoremap <buffer> <leader>hh4 <h4></h4><Enter><++><Esc>?</h4><Enter>i
+inoremap <buffer> <leader>hh5 <h5></h5><Enter><++><Esc>?</h5><Enter>i
+inoremap <buffer> <leader>hh6 <h6></h6><Enter><++><Esc>?</h6><Enter>i
+inoremap <buffer> <leader>hp <p></p><Enter><++><Esc>?</p><Enter>i
+
+" Text formatting
+inoremap <buffer> <leader>hb <b></b><++><Esc>?</b><Enter>i
+inoremap <buffer> <leader>hi <i></i><++><Esc>?</i><Enter>i
+inoremap <buffer> <leader>hem <em></em><++><Esc>?</em><Enter>i
+inoremap <buffer> <leader>hcode <code></code><++><Esc>?</code><Enter>i
+inoremap <buffer> <leader>hsub <sub></sub><++><Esc>?</sub><Enter>i
+inoremap <buffer> <leader>hsup <sup></sup><++><Esc>?</sup><Enter>i
+inoremap <buffer> <leader>hcite <cite></cite><++><Esc>?</cite><Enter>i
+
+" Tables
+inoremap <buffer> <leader>htable <table border=""><Enter><caption><++></caption><Enter><thead><Enter><tr><Enter><++><Enter></tr><Enter></thead><Enter><tbody><Enter><++><Enter></tbody><Enter></table><Enter><++><Esc>?""<Enter>a
+inoremap <buffer> <leader>htr <tr><Enter><Enter></tr><Enter><++><Esc>kki
+inoremap <buffer> <leader>hth <th></th><Enter><++><Esc>?</<Enter>i
+inoremap <buffer> <leader>htd <td></td><Enter><++><Esc>?</<Enter>i
+
+" Lists
+inoremap <buffer> <leader>hul <ul><Enter><li></li><Enter><++><Enter></ul><Enter><++><Esc>?</l<Enter>i
+inoremap <buffer> <leader>hol <ol><Enter><li></li><Enter><++><Enter></ol><Enter><++><Esc>?</l<Enter>i
+inoremap <buffer> <leader>hli <li></li><Enter><++><Esc>?</<Enter>i
+
+" Forms
+inoremap <buffer> <leader>hform <form><Enter><fieldset><Enter><Enter></fieldset><Enter></form><Enter><++><Esc>3ki
+inoremap <buffer> <leader>hfs <fieldset><Enter><Enter></fieldset><Enter><++><Esc>kki
+inoremap <buffer> <leader>hlegend <legend></legend><Enter><++><Esc>?</<Enter>i
+inoremap <buffer> <leader>hlabel <label><input type="<++>"></label><Enter><++><Esc>?<i<Enter>i
+
+" Media
+inoremap <buffer> <leader>ha <a href=""><++></a><Enter><++><Esc>?""<Enter>a
+inoremap <buffer> <leader>himg <img src="" alt="<++>"><Enter><++><Esc>?""<Enter>a
+
+" Misc
+" Comment a line
+nnoremap <buffer> <leader>hc I<!-- <Esc>A --><Esc>0
+" Uncomment a line
+nnoremap <buffer> <leader>hC 05x/--><Enter>3x0
diff --git a/stow/vim/dot-vim/ftplugin/html/snips/template.txt b/stow/vim/dot-vim/ftplugin/html/snips/template.txt
new file mode 100644
index 0000000..38d9f26
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/html/snips/template.txt
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><+++></title>
+</head>
+
+<body>
+
+ <++>
+
+</body>
+</html>
diff --git a/stow/vim/dot-vim/ftplugin/java/java.vim b/stow/vim/dot-vim/ftplugin/java/java.vim
new file mode 100644
index 0000000..2595b32
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/java/java.vim
@@ -0,0 +1,14 @@
+" ~/.vim/ftplugin/java.vim
+" Java-specific vim configuration
+
+call matchadd('ColorColumn', '\%131v', 100)
+
+let g:syntastic_java_checkers = []
+
+nnoremap <buffer> <leader>gt :YcmCompleter GoTo<CR>
+nnoremap <buffer> <leader>gr :YcmCompleter GoToReferences<CR>
+nnoremap <buffer> <leader>fi :YcmCompleter FixIt<CR>
+nnoremap <buffer> <leader>rr :YcmCompleter RefactorRename<Space>
+nnoremap <buffer> <leader>e :YcmDiags<CR>
+nnoremap <buffer> <leader>R :!./run.sh<CR>
+
diff --git a/stow/vim/dot-vim/ftplugin/javascript/javascript.vim b/stow/vim/dot-vim/ftplugin/javascript/javascript.vim
new file mode 100644
index 0000000..4935410
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/javascript/javascript.vim
@@ -0,0 +1,38 @@
+" ~/.vim/ftplugin/javascript/javascript.vim
+" JavaScript-specific vim configuration
+"
+" Syntastic linters: jslint
+
+" JSLint arguments
+" let g:syntastic_javascript_jslint_args = "--browser --indent"
+
+" ALE
+let b:ale_linters = ['eslint']
+
+" Tabs are expanded to 4 spaces
+setlocal tabstop=4 shiftwidth=4 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 110 characters
+call matchadd('ColorColumn', '\%111v', 100)
+setlocal textwidth=110
+
+" Mappings
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/javascript/javascript.vim<CR>
+
+nnoremap <buffer> gd :YcmCompleter GoTo<CR>
+nnoremap <buffer> gr :YcmCompleter GoToReferences<CR>
+nnoremap <buffer> gh :YcmCompleter GetType<CR>
+nnoremap <buffer> gD :YcmCompleter GetDoc<CR>
+nnoremap <buffer> gR :YcmCompleter RefactorRename<Space>
+nnoremap <buffer> <leader>fi :YcmCompleter FixIt<CR>
+nnoremap <buffer> <leader>e :YcmDiags<CR>
+
+let mapleader = ","
+
+inoremap <buffer> <leader>if if (<+++>) {<CR><++><CR>} <++><Esc>?<+++><CR>cf>
+inoremap <buffer> <leader>else else {<CR><+++><CR>}<CR><++><Esc>?<+++><CR>cf>
diff --git a/stow/vim/dot-vim/ftplugin/plantuml/plantuml.vim b/stow/vim/dot-vim/ftplugin/plantuml/plantuml.vim
new file mode 100644
index 0000000..5bab6a2
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/plantuml/plantuml.vim
@@ -0,0 +1,20 @@
+" ~/.vim/ftplugin/plantuml/plantuml.vim
+" PlantUML-specific vim configuration
+
+" Tabs hate: tabs are expanded to 4 spaces
+setlocal tabstop=4 shiftwidth=4 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 80 characters
+call matchadd('ColorColumn', '\%81v', 100)
+setlocal textwidth=80
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>temp :-1r ~/.vim/ftplugin/plantuml/snips/template.txt<CR>Gdd?<+++><CR>cf>
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/plantuml/plantuml.vim<CR>
+
+nnoremap <buffer> <leader>c :w<CR>:!compilePlantUML.sh "%"<CR><CR>
+nnoremap <buffer> <leader>o :w<CR>:!compilePlantUML.sh -o "%"<CR><CR>
diff --git a/stow/vim/dot-vim/ftplugin/plantuml/snips/template.txt b/stow/vim/dot-vim/ftplugin/plantuml/snips/template.txt
new file mode 100644
index 0000000..f649539
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/plantuml/snips/template.txt
@@ -0,0 +1,9 @@
+@startuml
+
+class <+++> {
+ <++>
+}
+
+<++>
+
+@enduml
diff --git a/stow/vim/dot-vim/ftplugin/python/python.vim b/stow/vim/dot-vim/ftplugin/python/python.vim
new file mode 100644
index 0000000..358f902
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/python/python.vim
@@ -0,0 +1,34 @@
+" ~/.vim/ftplugin/python/python.vim
+" Python-specific vim configuration
+
+" Tabs are expanded to 4 spaces
+setlocal tabstop=4 shiftwidth=4 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 90 characters
+call matchadd('ColorColumn', '\%91v', 100)
+setlocal textwidth=90
+
+let b:ale_python_pylint_options = '-d invalid-name'
+
+" Mappings
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/python/python.vim<CR>
+nnoremap <buffer> <leader>x :! ./%
+nnoremap <buffer> <leader>X :! ./%<CR>
+
+nnoremap <buffer> gd :YcmCompleter GoTo<CR>
+nnoremap <buffer> gr :YcmCompleter GoToReferences<CR>
+nnoremap <buffer> gR :YcmCompleter RefactorRename<Space>
+nnoremap <buffer> <leader>gd :YcmCompleter GetDoc<CR>
+nnoremap <buffer> <leader>gT :YcmCompleter GetType<CR>
+
+nnoremap <buffer> <leader>db :!tmux split-window -l '40\%' 'python -m̀ pdb %'
+
+let mapleader = ","
+
+inoremap <buffer> <leader>temp <Esc>:-1r ~/.vim/ftplugin/python/snips/template<CR>Gdd?<+++><CR>cf>
+inoremap <buffer> <leader>class <Esc>:-1r ~/.vim/ftplugin/python/snips/class<CR>Gdd?<+++><CR>cf>
diff --git a/stow/vim/dot-vim/ftplugin/python/snips/class b/stow/vim/dot-vim/ftplugin/python/snips/class
new file mode 100644
index 0000000..0febf0c
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/python/snips/class
@@ -0,0 +1,3 @@
+class <+++>:
+ def __init__(<++>):
+ self.<++> = <++>
diff --git a/stow/vim/dot-vim/ftplugin/python/snips/template b/stow/vim/dot-vim/ftplugin/python/snips/template
new file mode 100644
index 0000000..d10fa4a
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/python/snips/template
@@ -0,0 +1,5 @@
+#!/usr/bin/python
+
+"""<+++>"""
+
+<++>
diff --git a/stow/vim/dot-vim/ftplugin/sent/sent.vim b/stow/vim/dot-vim/ftplugin/sent/sent.vim
new file mode 100644
index 0000000..c9cc974
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/sent/sent.vim
@@ -0,0 +1,19 @@
+" ~/.vim/ftplugin/sent/sent.vim
+" Vim configuration for writing sent presentations
+
+" Tabs hate: tabs are expanded to 4 spaces
+setlocal tabstop=4 shiftwidth=4 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+call matchadd('ColorColumn', '\%41v', 100)
+setlocal textwidth=40
+
+setlocal spell
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/sent/sent.vim<CR>
+
+nnoremap <buffer> <leader>c :w<CR>:!killall sent <CR>:!sent % & <CR><CR>
+nnoremap <buffer> <leader>o :w<CR>:!sent % & <CR><CR>
diff --git a/stow/vim/dot-vim/ftplugin/sh/sh.vim b/stow/vim/dot-vim/ftplugin/sh/sh.vim
new file mode 100644
index 0000000..3eb4da5
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/sh/sh.vim
@@ -0,0 +1,16 @@
+" ~/.vim/ftplugin/sh.vim
+" Bourne shell scripts specific vim configuration
+"
+" Linters: sh, shellcheck
+
+call matchadd('ColorColumn', '\%111v', 100)
+setlocal textwidth=110
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/sh/sh.vim<CR>
+nnoremap <buffer> <leader>+x :!chmod 744 %<CR><CR>
+nnoremap <buffer> <leader>x :w<CR>:!./%<Space>
+nnoremap <buffer> <leader>e :Errors<CR>
+
+inoremap <buffer> <leader>sh #!/bin/sh
+inoremap <buffer> <leader>if if <+++>; then<CR><++><CR>fi<++><Esc>?<+++><CR>cf>
+"inoremap <buffer> <leader>if <Esc>:read ~/.vim/ftplugin/sh/snippets/if.txt<CR>kdd/<+++><CR>cf>
diff --git a/stow/vim/dot-vim/ftplugin/sh/snippets/if.txt b/stow/vim/dot-vim/ftplugin/sh/snippets/if.txt
new file mode 100644
index 0000000..9f6bb88
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/sh/snippets/if.txt
@@ -0,0 +1,3 @@
+if <+++>; then
+ <++>
+fi<++>
diff --git a/stow/vim/dot-vim/ftplugin/tex/snips/template.txt b/stow/vim/dot-vim/ftplugin/tex/snips/template.txt
new file mode 100644
index 0000000..1f4bd46
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/tex/snips/template.txt
@@ -0,0 +1,30 @@
+\documentclass{article}
+
+\usepackage{geometry}
+\usepackage{graphicx}
+\usepackage{booktabs}
+
+\geometry{left=4.5cm,top=2cm,bottom=2cm,right=4.5cm}
+
+\renewcommand{\contentsname}{Contenidos}
+\renewcommand{\figurename}{Figura}
+
+\begin{document}
+
+\frenchspacing
+
+\title{<+++>}
+
+\author{<++>}
+
+\date{}
+
+\maketitle
+
+\tableofcontents
+
+\section{<++>}
+
+<++>
+
+\end{document}
diff --git a/stow/vim/dot-vim/ftplugin/tex/tex.vim b/stow/vim/dot-vim/ftplugin/tex/tex.vim
new file mode 100644
index 0000000..87f3c88
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/tex/tex.vim
@@ -0,0 +1,45 @@
+" ~/.vim/ftplugin/tex.vim
+" LaTeX-specific vim configuration
+
+" Syntastic linters: chktex, lacheck, proselint
+
+call matchadd('ColorColumn', '\%91v', 100)
+setlocal textwidth=80
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/tex/tex.vim<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
+" proselint not enabled by default (it's slow)
+"nnoremap <buffer> <leader>sp :SyntasticCheck proselint<CR>
+
+nnoremap <buffer> <leader>c :w<Enter>:!toPDF.sh %<CR><CR>
+nnoremap <buffer> <leader>o :w<Enter>:!toPDF.sh -o %<CR><CR>
+nnoremap <buffer> <leader>O :w<Enter>:!toPDF.sh -o %<CR>
+nnoremap <buffer> <leader>m :w<Enter>:!make<CR><CR>
+nnoremap <buffer> <leader>M :w<Enter>:!make<CR>
+
+nnoremap <buffer> <leader>temp :-1r ~/.vim/ftplugin/tex/snips/template.txt<CR>/<+++><CR>cf>
+
+let mapleader = ","
+
+inoremap <buffer> <leader>ldocclass \documentclass{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lpckg \usepackage{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lbdoc \begin{document}<Esc>o<Enter><Enter><Enter>\end{document}<Esc>kkI
+inoremap <buffer> <leader>lauthor \author{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>ltitle \title{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lsection \section{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lssection \subsection{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lsssection \subsubsection{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lssssection \paragraph{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lsssssection \subparagraph{}<Esc>o<++><Esc>k$i
+inoremap <buffer> <leader>lb \textbf{}<++><Esc>F}i
+inoremap <buffer> <leader>li \textit{}<++><Esc>F}i
+inoremap <buffer> <leader>lemph \emph{}<++><Esc>F}i
+inoremap <buffer> <leader>llabel \label{}<++><Esc>F}i
+inoremap <buffer> <leader>lref ~\ref{}<++><Esc>F}i
+inoremap <buffer> <leader>lenumerate \begin{enumerate}<Esc>o<Enter><Enter><Enter>\end{enumerate}<Esc>kkI\item<Enter><Esc>I<Tab><++><Esc>k<<i<Tab><Esc>$a<Space>
+inoremap <buffer> <leader>litemize \begin{itemize}<Esc>o<Enter><Enter><Enter>\end{itemize}<Esc>kkI\item<Enter><Esc>I<Tab><++><Esc>k<<i<Tab><Esc>$a<Space>
+inoremap <buffer> <leader>litem \item<Enter><++><Esc>k$a<Space>
+inoremap <buffer> <leader>limage \begin{figure}[h]<Enter>\begin{center}<Enter>\includegraphics[width=\textwidth]{}<Enter>\caption{<++>}<Enter>\end{center}<Enter>\end{figure}<Enter><++><Esc>4k$i
+inoremap <buffer> <leader>ltable \begin{table}[h]<Enter>\makebox[\linewidth]{\centering<Enter>\centering<Enter>\begin{tabular}{c<Space>cxxx}<Enter>\toprule<Enter><++><Space>&<Space><++><Space>\\<Enter>\midrule<Enter><++><Space>&<Space><++><Space>\\<Enter>\bottomrule<Enter>\end{tabular}<Enter>}<Enter>\end{table}<Enter><++><Esc>?xxx<Enter>cw
diff --git a/stow/vim/dot-vim/ftplugin/text/text.vim b/stow/vim/dot-vim/ftplugin/text/text.vim
new file mode 100644
index 0000000..c370a74
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/text/text.vim
@@ -0,0 +1,20 @@
+" ~/.vim/ftplugin/text/text.vim
+" Plain text files vim configuration
+"
+" Syntastic linter: proselint
+
+" Tabs hate: tabs are expanded to 2 spaces
+setlocal tabstop=2 shiftwidth=2 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+let mapleader = " "
+
+call matchadd('ColorColumn', '\%101v', 100)
+setlocal textwidth=100
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/text/text.vim<CR>
+
+" Proselint is slow to act
+nnoremap <buffer> <leader>sp :SyntasticCheck proselint<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
diff --git a/stow/vim/dot-vim/ftplugin/typescript/typescript.vim b/stow/vim/dot-vim/ftplugin/typescript/typescript.vim
new file mode 100644
index 0000000..7c91032
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/typescript/typescript.vim
@@ -0,0 +1,33 @@
+" ~/.vim/ftplugin/typescript/typescript.vim
+" TypeScript-specific vim configuration
+
+" ALE
+let b:ale_linters = ['eslint']
+
+" Tabs are expanded to 4 spaces
+setlocal tabstop=4 shiftwidth=4 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Column marker: 110 characters
+call matchadd('ColorColumn', '\%111v', 100)
+setlocal textwidth=110
+
+" Mappings
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/typescript/typescript.vim<CR>
+
+nnoremap <buffer> gd :YcmCompleter GoTo<CR>
+nnoremap <buffer> gr :YcmCompleter GoToReferences<CR>
+nnoremap <buffer> gh :YcmCompleter GetType<CR>
+nnoremap <buffer> gD :YcmCompleter GetDoc<CR>
+nnoremap <buffer> gR :YcmCompleter RefactorRename<Space>
+nnoremap <buffer> <leader>fi :YcmCompleter FixIt<CR>
+nnoremap <buffer> <leader>e :YcmDiags<CR>
+
+let mapleader = ","
+
+inoremap <buffer> <leader>if if (<+++>) {<CR><++><CR>} <++><Esc>?<+++><CR>cf>
+inoremap <buffer> <leader>else else {<CR><+++><CR>}<CR><++><Esc>?<+++><CR>cf>
diff --git a/stow/vim/dot-vim/ftplugin/vimwiki/snips/plantUml.txt b/stow/vim/dot-vim/ftplugin/vimwiki/snips/plantUml.txt
new file mode 100644
index 0000000..4a016eb
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/vimwiki/snips/plantUml.txt
@@ -0,0 +1,5 @@
+```{.plantuml caption="<+++>"}
+@startuml
+<++>
+@enduml
+```
diff --git a/stow/vim/dot-vim/ftplugin/vimwiki/snips/template.txt b/stow/vim/dot-vim/ftplugin/vimwiki/snips/template.txt
new file mode 100644
index 0000000..f6b16a8
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/vimwiki/snips/template.txt
@@ -0,0 +1,15 @@
+---
+header-includes:
+ - \usepackage[margin=1.5in]{geometry}
+
+title: "Title"
+author:
+- Ãñigo Gutiérrez Fernández
+
+ \
+
+ UO238186
+date: "10/04/2020"
+---
+
+# Introducción
diff --git a/stow/vim/dot-vim/ftplugin/vimwiki/vimwiki.vim b/stow/vim/dot-vim/ftplugin/vimwiki/vimwiki.vim
new file mode 100644
index 0000000..c361600
--- /dev/null
+++ b/stow/vim/dot-vim/ftplugin/vimwiki/vimwiki.vim
@@ -0,0 +1,39 @@
+" ~/.vim/ftplugin/vimwiki.vim
+" vimwiki-specific vim configuration
+"
+" Syntastic linter: mdl, proselint, textlint
+
+"let g:syntastic_vimwiki_checkers = ['markdown/mdl']
+
+" Tabs hate: tabs are expanded to 2 spaces
+setlocal tabstop=2 shiftwidth=2 expandtab
+" Show existing tabs (they can be deleted with :retab)
+setlocal list
+
+" Text width
+match ColorColumn /\%81v/
+setlocal textwidth=80
+setlocal linebreak
+
+" Syntax highlighting colors
+highlight Title ctermfg=blue
+highlight TitleHash ctermfg=cyan
+match TitleHash /^##*/
+
+setlocal conceallevel=1
+
+let mapleader = " "
+
+nnoremap <buffer> <leader>C :sp ~/.vim/ftplugin/vimwiki/vimwiki.vim<CR>
+nnoremap <buffer> <leader>e :Errors<CR>
+
+nnoremap <buffer> <leader>tt :r ~/.vim/ftplugin/vimwiki/snips/template.txt<CR>
+nnoremap <buffer> <leader>tp :r ~/.vim/ftplugin/vimwiki/snips/plantUml.txt<CR>/<+++><CR>cf>
+
+nnoremap <buffer> <leader>c :w<CR>:!toPDF.sh "%"<CR>
+nnoremap <buffer> <leader>o :w<CR>:!toPDF.sh -o "%"<CR><CR>
+
+let mapleader = ","
+
+inoremap <buffer> <leader>h <Esc><<A
+inoremap <buffer> <leader>l <Esc>>>A
diff --git a/stow/vim/dot-vim/plugin/SWTC.vim b/stow/vim/dot-vim/plugin/SWTC.vim
new file mode 100644
index 0000000..9459e92
--- /dev/null
+++ b/stow/vim/dot-vim/plugin/SWTC.vim
@@ -0,0 +1,339 @@
+" Vim global plugin for Star Wars crawls
+" Maintainer: Damian Conway
+" License: This file is placed in the public domain.
+
+"######################################################################
+"## ##
+"## To use: ##
+"## ##
+"## :SWTC <filename> ##
+"## ##
+"## See file 'intro.swtc' for the crawl-specification syntax ##
+"## ##
+"######################################################################
+
+
+" If already loaded, we're done...
+if exists("loaded_SWcrawl")
+ finish
+endif
+let loaded_SWcrawl = 1
+
+" Preserve external compatibility options, then enable full vim compatibility...
+let s:save_cpo = &cpo
+set cpo&vim
+
+" Set up the actual colon command...
+command! -nargs=1 -complete=file SWTC call SWcrawl(<f-args>)
+
+
+" Implementation....
+
+let s:CRAWL_SPEED = 1 "(lines per second)
+let s:STAR_DENSITY = 50 "(pixels per star, i.e. 1 star per STAR_DENSITY pixels)
+let s:STARFIELD_HEIGHT = 2 "(screens deep)
+
+let s:LOGO_LINE1 = '^\s*\[\zs.*\ze\]\s*$'
+let s:LOGO_LINE2 = '^\s*\[\[\zs.*\ze\]\]\s*$'
+let s:LOGO_LINE3 = '^\s*\[\[\[\zs.*\ze\]\]\]\s*$'
+let s:LOGO_LINE4 = '^\s*\[\[\[\[\zs.*\ze\]\]\]\]\s*$'
+let s:CENTRED_CRAWL_LINE = '^\s*[>]\s*\zs.\{-}\ze\s*[<]\s*$'
+let s:CRAWL_LINE = '^\s*[|]\s*\zs.\{-}\ze\s*[|]\s*$'
+let s:PREFACE_LINE = '^\s*\zs.\{-}\ze\s*$'
+
+highlight SWC_PREFACE ctermfg=cyan
+highlight SWC_FADE_LIGHT ctermfg=cyan
+highlight SWC_FADE_DARK ctermfg=blue
+highlight SWC_LOGO ctermfg=yellow cterm=bold
+highlight SWC_CRAWL ctermfg=yellow
+highlight SWC_STAR ctermfg=white
+highlight SWC_BLACK ctermfg=black ctermbg=black
+
+let s:PREFACE_POS = { 'x': 10, 'y': 5 }
+
+function! SWcrawl (textsource)
+ " Load preface, logo, and text to be crawled...
+ let preface = []
+ let logo1 = []
+ let logo2 = []
+ let logo3 = []
+ let logo4 = []
+ let crawl = []
+ let centred = []
+ let max_crawl_width = 0
+ for nextline in readfile(a:textsource)
+ " Ignore blank lines...
+ if nextline =~ '^\s*$'
+ continue
+
+ " Lines in [...] are logo components...
+ elseif nextline =~ s:LOGO_LINE4
+ let logo4 += [ matchstr(nextline, s:LOGO_LINE4) ]
+ elseif nextline =~ s:LOGO_LINE3
+ let logo3 += [ matchstr(nextline, s:LOGO_LINE3) ]
+ elseif nextline =~ s:LOGO_LINE2
+ let logo2 += [ matchstr(nextline, s:LOGO_LINE2) ]
+ elseif nextline =~ s:LOGO_LINE1
+ let logo1 += [ matchstr(nextline, s:LOGO_LINE1) ]
+
+ " Lines in |...| are crawl components...
+ elseif nextline =~ s:CRAWL_LINE
+ let next_crawl = matchstr(nextline, s:CRAWL_LINE)
+ if strlen(next_crawl) > max_crawl_width
+ let max_crawl_width = strlen(substitute(next_crawl,'\s\+',' ','g'))
+ endif
+ let crawl += [ next_crawl ]
+ let centred += [ 0 ]
+
+ " Lines in >...< are centred crawl components...
+ elseif nextline =~ s:CENTRED_CRAWL_LINE
+ let next_crawl = matchstr(nextline, s:CENTRED_CRAWL_LINE)
+ if strlen(next_crawl) > max_crawl_width
+ let max_crawl_width = strlen(substitute(next_crawl,'\s\+',' ','g'))
+ endif
+ let crawl += [ next_crawl ]
+ let centred += [ 1 ]
+
+ " Anything else is preface...
+ else
+ let preface += [ substitute(matchstr(nextline, s:PREFACE_LINE), "^\s*", repeat(" ",s:PREFACE_POS.x), '') ]
+
+ endif
+ endfor
+
+ " Ensure all logos available...
+ let logo1 = len(logo1) ? logo1 : ["YOUR", "LOGO", "HERE"]
+ let logo2 = len(logo2) ? logo2 : copy(logo1)
+ let logo3 = len(logo3) ? logo3 : copy(logo2)
+ let logo4 = len(logo4) ? logo4 : copy(logo3)
+
+ " Save current buffer for final transition effect...
+ let original_buffer = getline(1,'$')
+
+ " Switch to a new buffer...
+ let prev_matches = getmatches()
+ enew!
+ let b:WIN = { 'x' : winwidth(0), 'y' : winheight(0) }
+ call setline(1, repeat([""], b:WIN.y + 1))
+
+ " And hide annoyances...
+ set lcs=
+ let old_rulerformat = &rulerformat
+ let &rulerformat="%#SWC_BLACK#%l"
+ echo ""
+
+ " Generate starfield...
+ let stars = SWC_gen_stars()
+
+ " Clear screen...
+ call setline(1, repeat([""], s:STARFIELD_HEIGHT * b:WIN.y) + original_buffer)
+ redraw
+ sleep 2
+
+ " Start with preface...
+ call matchadd('SWC_PREFACE', '.', 100)
+ call setline(s:PREFACE_POS.y, preface)
+ echo ""
+ redraw
+ sleep 5
+
+ " Clean up...
+ call clearmatches()
+ call setline(s:PREFACE_POS.y, repeat([""], len(preface)))
+ echo ""
+ redraw
+ sleep 1
+
+ " Then show logo receding at centre of screen...
+ call clearmatches()
+ call matchadd('SWC_BLACK', '*', 102)
+ call matchadd('SWC_STAR', '\s\zs[.]\ze\s', 101)
+ call matchadd('SWC_LOGO', '.', 100)
+ call SWC_draw_logo(logo1)
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ sleep 3
+
+ " Push it away...
+ call setline(1, repeat([""], b:WIN.y))
+ call SWC_draw_logo(logo2)
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ sleep 500m
+
+ call setline(1, repeat([""], b:WIN.y))
+ call SWC_draw_logo(logo3)
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ sleep 500m
+
+ call setline(1, repeat([""], b:WIN.y))
+ call SWC_draw_logo(logo4)
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ sleep 500m
+
+
+ " Clean up...
+ call clearmatches()
+ call matchadd('SWC_STAR', '\s\zs[.]\ze\s', 101)
+ call setline(1, repeat([""], b:WIN.y))
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ sleep 2
+
+ " Run crawl...
+ call clearmatches()
+ call matchadd('SWC_CRAWL', '.', 100)
+ call matchadd('SWC_STAR', '\s\zs[.]\ze\s', 101)
+ for offset_from_bottom in range(1, len(crawl) + b:WIN.y)
+ let crawl_line = offset_from_bottom < b:WIN.y ? 0 : offset_from_bottom - b:WIN.y + 1
+ for screen_line in range(1, b:WIN.y)
+ if screen_line >= b:WIN.y - offset_from_bottom && crawl_line < len(crawl)
+ let padded_line = SWC_pad(crawl[crawl_line], screen_line, centred[crawl_line], max_crawl_width)
+ call setline(screen_line, padded_line)
+ let crawl_line += 1
+ else
+ call setline(screen_line, "")
+ endif
+ endfor
+ call SWC_paint_stars(stars)
+ echo ""
+ redraw
+ exec 'sleep ' . s:trunc(1000/s:CRAWL_SPEED) . 'm'
+ if getchar(0) || offset_from_bottom > len(crawl) && padded_line !~ '\S'
+ break
+ endif
+ endfor
+
+ " Pan starfield down...
+ call matchadd('SWC_FADE_DARK', '[^.]', 200)
+ sleep 200m
+ for offset_from_top in range(1, s:STARFIELD_HEIGHT * b:WIN.y)
+ 1delete
+ redraw
+ exec 'sleep ' . (200 - 2 * offset_from_top) . 'm'
+ endfor
+ sleep 200m
+
+
+ " Switch back to previous buffer and restore normal highlighting...
+ edit! #
+ call setmatches(prev_matches)
+ let &rulerformat = old_rulerformat
+ redraw
+
+endfunction
+
+function s:trunc (n)
+ return str2nr(string( a:n ))
+endfunction
+
+function! SWC_draw_logo (logo)
+ let logo = copy(a:logo)
+
+ " Find centre for logo...
+ let logo_width = 0
+ for line in logo
+ if strlen(line) > logo_width
+ let logo_width = strlen(line)
+ endif
+ endfor
+ let logo_pos_x = (b:WIN.x - logo_width) / 2
+ let logo_pos_y = (b:WIN.y - len(logo)) / 2
+
+ " Move logo to centre...
+ call map(logo, "repeat(' ', logo_pos_x) . v:val")
+
+ " Draw logo
+ call setline(logo_pos_y, logo)
+
+endfunction
+
+function! SWC_pad (text, y_pos, centred, max_text_width)
+
+ " Does this need padding???
+ let words = split(a:text, '\s\+')
+ if len(words) < 1
+ return a:text
+ endif
+
+ " How many unpadded characters are there???
+ let unpadded_width = 0
+ for word in words
+ let unpadded_width += strlen(word)
+ endfor
+
+ " How much padding is needed???
+ let rel_y = (2.0 * a:y_pos / b:WIN.y) - 1.0
+ let stretched_width = s:trunc( a:max_text_width + rel_y * (b:WIN.x - a:max_text_width) )
+ let required_padding = max([ 0, stretched_width - unpadded_width ])
+ let indent = (b:WIN.x - stretched_width) / 2
+ let gap_count = len(words) - 1
+
+ " Is this a last line???
+ let tight = a:centred || strlen(a:text) < 0.9 * a:max_text_width
+
+ " Insert padding...
+ if a:y_pos >= b:WIN.y/2
+ let min_padding_needed_for = gap_count
+ if tight
+ let min_pad_per_gap = max([ 1, s:trunc(rel_y * 6.0) ])
+ else
+ let min_pad_per_gap = max([ 1, required_padding / gap_count ])
+ let leftover_padding = required_padding - gap_count * min_pad_per_gap
+ let min_padding_needed_for = min([ gap_count, gap_count - leftover_padding ])
+ endif
+ let padded_text = join(words[0 : min_padding_needed_for], repeat(" ", min_pad_per_gap))
+ \ . repeat(" ", min_pad_per_gap+1)
+ \ . join(words[min_padding_needed_for+1 : -1], repeat(" ", min_pad_per_gap+1))
+ let padded_text = substitute(padded_text, '\s*$', '', '')
+
+ " Or remove chars (in the distance)...
+ elseif a:text =~ '\S'
+" let delta = s:trunc( 8.0 * (b:WIN.y/2 - a:y_pos) )
+" let greeked_len = max([ 0, strlen(substitute(a:text, '^\s*\|\s*$', '', 'g')) - delta ])
+ let greeked_len = tight ? stretched_width * (unpadded_width + gap_count) / a:max_text_width : stretched_width
+ let padded_text = repeat('~', greeked_len)
+
+ " Or ignore it...
+ else
+ let padded_text = ""
+
+ endif
+
+ " Indent to centre...
+ let padded_text = substitute(padded_text, '\s*$', '', '')
+ let max_ever_padding = b:WIN.x - a:max_text_width
+ let indent = a:centred ? (b:WIN.x - strlen(padded_text))/2
+ \ : indent
+ return repeat(" ", indent) . padded_text
+endfunction
+
+function! SWC_gen_stars ()
+ let star_count = b:WIN.x * s:STARFIELD_HEIGHT * b:WIN.y / s:STAR_DENSITY
+ let stars = []
+ for n in range(star_count)
+ let x = RandomNumber(b:WIN.x) + 1
+ let y = RandomNumber(s:STARFIELD_HEIGHT * b:WIN.y) + 1
+ let stars += [{'y':y,'x':x}]
+ endfor
+ return stars
+endfunction
+
+function! SWC_paint_stars (stars)
+ let max_x = b:WIN.x
+ for star in a:stars
+ let line = strpart(getline(star.y) . repeat(" ", max_x), 0, max_x)
+ let line = substitute(line, '\s\zs\%'.(star.x-1).'c\s\ze\s', '.', '')
+ call setline(star.y, line)
+ endfor
+endfunction
+
+" Restore previous external compatibility options
+let &cpo = s:save_cpo
diff --git a/stow/vim/dot-vim/plugin/dragvisuals.vim b/stow/vim/dot-vim/plugin/dragvisuals.vim
new file mode 100644
index 0000000..12c4f5d
--- /dev/null
+++ b/stow/vim/dot-vim/plugin/dragvisuals.vim
@@ -0,0 +1,345 @@
+" Vim global plugin for dragging virtual blocks
+" Last change: Tue Jul 24 07:19:35 EST 2012
+" Maintainer: Damian Conway
+" License: This file is placed in the public domain.
+
+"#########################################################################
+"## ##
+"## Add the following (uncommented) to your .vimrc... ##
+"## ##
+"## runtime plugin/dragvisuals.vim ##
+"## ##
+"## vmap <expr> <LEFT> DVB_Drag('left') ##
+"## vmap <expr> <RIGHT> DVB_Drag('right') ##
+"## vmap <expr> <DOWN> DVB_Drag('down') ##
+"## vmap <expr> <UP> DVB_Drag('up') ##
+"## vmap <expr> D DVB_Duplicate() ##
+"## ##
+"## " Remove any introduced trailing whitespace after moving... ##
+"## let g:DVB_TrimWS = 1 ##
+"## ##
+"## Or, if you use the arrow keys for normal motions, choose ##
+"## four other keys for block dragging. For example: ##
+"## ##
+"## vmap <expr> h DVB_Drag('left') ##
+"## vmap <expr> l DVB_Drag('right') ##
+"## vmap <expr> j DVB_Drag('down') ##
+"## vmap <expr> k DVB_Drag('up') ##
+"## ##
+"## Or: ##
+"## ##
+"## vmap <expr> <S-LEFT> DVB_Drag('left') ##
+"## vmap <expr> <S-RIGHT> DVB_Drag('right') ##
+"## vmap <expr> <S-DOWN> DVB_Drag('down') ##
+"## vmap <expr> <S-UP> DVB_Drag('up') ##
+"## ##
+"## Or even: ##
+"## ##
+"## vmap <expr> <LEFT><LEFT> DVB_Drag('left') ##
+"## vmap <expr> <RIGHT><RIGHT> DVB_Drag('right') ##
+"## vmap <expr> <DOWN><DOWN> DVB_Drag('down') ##
+"## vmap <expr> <UP><UP> DVB_Drag('up') ##
+"## ##
+"#########################################################################
+
+
+" If already loaded, we're done...
+if exists("loaded_dragvirtualblocks")
+ finish
+endif
+let loaded_dragvirtualblocks = 1
+
+" Preserve external compatibility options, then enable full vim compatibility...
+let s:save_cpo = &cpo
+set cpo&vim
+
+"====[ Implementation ]====================================
+
+" Toggle this to stop trimming on drags...
+if !exists('g:DVB_TrimWS')
+ let g:DVB_TrimWS = 1
+endif
+
+function! DVB_Drag (dir)
+ " No-op in Visual mode...
+ if mode() ==# 'v'
+ return "\<ESC>gv"
+
+ " Do Visual Line drag indirectly via temporary nmap
+ " (to ensure we have access to block position data)...
+ elseif mode() ==# 'V'
+ " Set up a temporary convenience...
+ exec "nnoremap <silent><expr><buffer> M \<SID>Drag_Lines('".a:dir."')"
+
+ " Return instructions to implement the move and reset selection...
+ return '"vyM'
+
+ " Otherwise do Visual Block drag indirectly via temporary nmap
+ " (to ensure we have access to block position data)...
+ else
+ " Set up a temporary convenience...
+ exec "nnoremap <silent><expr><buffer> M \<SID>Drag_Block('".a:dir."')"
+
+ " Return instructions to implement the move and reset selection...
+ return '"vyM'
+ endif
+endfunction
+
+" Duplicate selected block and place to the right...
+function! DVB_Duplicate ()
+ exec "nnoremap <silent><expr><buffer> M \<SID>DuplicateBlock()"
+ return '"vyM'
+endfunction
+
+function! s:DuplicateBlock ()
+ nunmap <buffer> M
+ " Locate block boundaries...
+ let [buf_left, line_left, col_left, offset_left ] = getpos("'<")
+ let [buf_right, line_right, col_right, offset_right] = getpos("'>")
+
+ " Identify special '$' blocks...
+ let dollar_block = 0
+ let start_col = min([col_left+offset_left, col_right+offset_right])
+ let end_col = max([col_left+offset_left, col_right+offset_right])
+ let visual_width = end_col - start_col + 1
+ for visual_line in split(getreg("v"),"\n")
+ if strlen(visual_line) > visual_width
+ let dollar_block = 1
+ let visual_width = strlen(visual_line)
+ endif
+ endfor
+ let square_up = (dollar_block ? (start_col+visual_width-2).'|' : '')
+
+ set virtualedit=all
+ return 'gv'.square_up.'yPgv'
+ \. (visual_width-dollar_block) . 'lo' . (visual_width-dollar_block) . 'l'
+ \. "y:set virtualedit=block\<CR>gv"
+ \. (dollar_block ? 'o$' : '')
+endfunction
+
+
+" Kludge to hide change reporting inside implementation...
+let s:NO_REPORT = ":let b:DVB_report=&report\<CR>:let &report=1000000000\<CR>"
+let s:PREV_REPORT = ":let &report = b:DVB_report\<CR>"
+
+
+" Drag in specified direction in Visual Line mode...
+function! s:Drag_Lines (dir)
+ " Clean up the temporary convenience...
+ nunmap <buffer> M
+
+ " Locate block being shifted...
+ let [buf_left, line_left, col_left, offset_left ] = getpos("'<")
+ let [buf_right, line_right, col_right, offset_right] = getpos("'>")
+
+ " Drag entire lines left if possible...
+ if a:dir == 'left'
+ " Are all lines indented at least one space???
+ let lines = getline(line_left, line_right)
+ let all_indented = match(lines, '^[^ ]') == -1
+ nohlsearch
+
+ " If can't trim one space from start of each line, be a no-op...
+ if !all_indented
+ return 'gv'
+
+ " Otherwise drag left by removing one space from start of each line...
+ else
+ return s:NO_REPORT
+ \ . "gv:s/^ //\<CR>"
+ \ . s:PREV_REPORT
+ \ . "gv"
+ endif
+
+ " To drag entire lines right, add a space in column 1...
+ elseif a:dir == 'right'
+ return s:NO_REPORT
+ \ . "gv:s/^/ /\<CR>:nohlsearch\<CR>"
+ \ . s:PREV_REPORT
+ \ . "gv"
+
+ " To drag entire lines upwards...
+ elseif a:dir == 'up'
+ let EOF = line('$')
+
+ " Can't drag up if at first line...
+ if line_left == 1 || line_right == 1
+ return 'gv'
+
+ " Needs special handling at EOF (because cursor moves up on delete)...
+ elseif line_left == EOF || line_right == EOF
+ let height = line_right - line_left
+ let select_extra = height ? height . 'j' : ""
+ return s:NO_REPORT
+ \ . 'gvxP'
+ \ . s:PREV_REPORT
+ \ . 'V' . select_extra
+
+ " Otherwise just cut-move-paste-reselect...
+ else
+ let height = line_right - line_left
+ let select_extra = height ? height . 'j' : ""
+ return s:NO_REPORT
+ \ . 'gvxkP'
+ \ . s:PREV_REPORT
+ \ . 'V' . select_extra
+ endif
+
+ " To drag entire lines downwards...
+ elseif a:dir == 'down'
+ let EOF = line('$')
+
+ " This is how much extra we're going to have to reselect...
+ let height = line_right - line_left
+ let select_extra = height ? height . 'j' : ""
+
+ " Needs special handling at EOF (to push selection down into new space)...
+ if line_left == EOF || line_right == EOF
+ return "O\<ESC>gv"
+
+ " Otherwise, just cut-move-paste-reselect...
+ else
+ return s:NO_REPORT
+ \ . 'gvxp'
+ \ . s:PREV_REPORT
+ \ . 'V' . select_extra
+ endif
+
+ endif
+endfunction
+
+" Drag in specified direction in Visual Block mode...
+function! s:Drag_Block (dir)
+ " Clean up the temporary convenience...
+ nunmap <buffer> M
+
+ " Locate block being shifted...
+ let [buf_left, line_left, col_left, offset_left ] = getpos("'<")
+ let [buf_right, line_right, col_right, offset_right] = getpos("'>")
+
+ " Identify special '$' blocks...
+ let dollar_block = 0
+ let start_col = min([col_left+offset_left, col_right+offset_right])
+ let end_col = max([col_left+offset_left, col_right+offset_right])
+ let visual_width = end_col - start_col + 1
+ for visual_line in split(getreg("v"),"\n")
+ if strlen(visual_line) > visual_width
+ let dollar_block = 1
+ let visual_width = strlen(visual_line)
+ endif
+ endfor
+ let square_up = (dollar_block ? (start_col+visual_width-2).'|' : '')
+
+ " Drag left...
+ if a:dir == 'left'
+ "Can't drag left at left margin...
+ if col_left == 1 || col_right == 1
+ return 'gv'
+
+ " Otherwise reposition one column left (and optionally trim any whitespace)...
+ elseif g:DVB_TrimWS
+ " May need to be able to temporarily step past EOL...
+ let prev_ve = &virtualedit
+ set virtualedit=all
+
+ " Are we moving past other text???
+ let square_up_final = ""
+ if dollar_block
+ let lines = getline(line_left, line_right)
+ if match(lines, '^.\{'.(start_col-2).'}\S') >= 0
+ let dollar_block = 0
+ let square_up_final = (start_col+visual_width-3).'|'
+ endif
+ endif
+
+ let vcol = start_col - 2
+ return 'gv'.square_up.'xhP'
+ \ . s:NO_REPORT
+ \ . "gvhoho:s/\\s*$//\<CR>gv\<ESC>"
+ \ . ':set virtualedit=' . prev_ve . "\<CR>"
+ \ . s:PREV_REPORT
+ \ . ":nohlsearch\<CR>gv"
+ \ . (dollar_block ? '$' : square_up_final )
+ else
+ return 'gv'.square_up.'xhPgvhoho'
+ endif
+
+ " Drag right...
+ elseif a:dir == 'right'
+ " May need to be able to temporarily step past EOL...
+ let prev_ve = &virtualedit
+ set virtualedit=all
+
+ " Reposition block one column to the right...
+ if g:DVB_TrimWS
+ let vcol = start_col
+ return 'gv'.square_up.'xp'
+ \ . s:NO_REPORT
+ \ . "gvlolo"
+ \ . ":s/\\s*$//\<CR>gv\<ESC>"
+ \ . ':set virtualedit=' . prev_ve . "\<CR>"
+ \ . s:PREV_REPORT
+ \ . (dollar_block ? 'gv$' : 'gv')
+ else
+ return 'gv'.square_up.'xp:set virtualedit=' . prev_ve . "\<CR>gvlolo"
+ endif
+
+ " Drag upwards...
+ elseif a:dir == 'up'
+ " Can't drag upwards at top margin...
+ if line_left == 1 || line_right == 1
+ return 'gv'
+ endif
+
+ " May need to be able to temporarily step past EOL...
+ let prev_ve = &virtualedit
+ set virtualedit=all
+
+ " If trimming whitespace, jump to just below block to do it...
+ if g:DVB_TrimWS
+ let height = line_right - line_left + 1
+ return 'gv'.square_up.'xkPgvkoko"vy'
+ \ . height
+ \ . 'j:s/\s*$//'
+ \ . "\<CR>:nohlsearch\<CR>:set virtualedit="
+ \ . prev_ve
+ \ . "\<CR>gv"
+ \ . (dollar_block ? '$' : '')
+
+ " Otherwise just move and reselect...
+ else
+ return 'gv'.square_up.'xkPgvkoko"vy:set virtualedit='
+ \ . prev_ve
+ \ . "\<CR>gv"
+ \ . (dollar_block ? '$' : '')
+ endif
+
+ " Drag downwards...
+ elseif a:dir == 'down'
+ " May need to be able to temporarily step past EOL...
+ let prev_ve = &virtualedit
+ set virtualedit=all
+
+ " If trimming whitespace, move to just above block to do it...
+ if g:DVB_TrimWS
+ return 'gv'.square_up.'xjPgvjojo"vyk:s/\s*$//'
+ \ . "\<CR>:nohlsearch\<CR>:set virtualedit="
+ \ . prev_ve
+ \ . "\<CR>gv"
+ \ . (dollar_block ? '$' : '')
+
+ " Otherwise just move and reselect...
+ else
+ return 'gv'.square_up.'xjPgvjojo"vy'
+ \ . "\<CR>:set virtualedit="
+ \ . prev_ve
+ \ . "\<CR>gv"
+ \ . (dollar_block ? '$' : '')
+ endif
+ endif
+endfunction
+
+
+" Restore previous external compatibility options
+let &cpo = s:save_cpo
+
diff --git a/stow/vim/dot-vim/plugin/rng.vim b/stow/vim/dot-vim/plugin/rng.vim
new file mode 100644
index 0000000..51c9991
--- /dev/null
+++ b/stow/vim/dot-vim/plugin/rng.vim
@@ -0,0 +1,83 @@
+" George Marsaglia's Multiply-with-carry Random Number Generator {{{
+" Modified to work within Vim's semantics
+let s:m_w = 1 + getpid()
+let s:m_z = localtime()
+
+" not sure of the wisdom of generating a full 32-bit RN here
+" and then using abs() on the sucker. Feedback welcome.
+function! RandomNumber(...)
+ if a:0 == 0
+ let s:m_z = (36969 * and(s:m_z, 0xffff)) + (s:m_z / 65536)
+ let s:m_w = (18000 * and(s:m_w, 0xffff)) + (s:m_w / 65536)
+ return (s:m_z * 65536) + s:m_w " 32-bit result
+ elseif a:0 == 1 " We return a number in [0, a:1] or [a:1, 0]
+ return a:1 < 0 ? RandomNumber(a:1,0) : RandomNumber(0,a:1)
+ else " if a:2 >= 2
+ return abs(RandomNumber()) % (abs(a:2 - a:1) + 1) + a:1
+ endif
+endfunction
+" end RNG }}}
+
+" RandomChar(base, cap)
+" base : the lowest char number desired
+" cap : the highest char number desired
+" Defaults to ASCII characters in the range
+" 33-126 (!-~)
+" But it's capable of much wider character tables
+function! RandomChar(...)
+ let base = 33
+ let cap = 126
+ if a:0 > 0
+ let base = a:1
+ endif
+ if a:0 > 1
+ let cap = a:2
+ endif
+ return nr2char(RandomNumber(base, cap))
+endfunction
+
+function! RandomCharsInSet(length, set)
+ let from = join(map(range(len(a:set)), 'nr2char(char2nr("a")+v:val)'), '')
+ let to = join(a:set, '')
+ return map(RandomChars(a:length, 97, 96+len(a:set)), 'tr(v:val, from, to)')
+endfunction
+
+function! RandomChars(length, ...)
+ let args = []
+ if a:0 > 0
+ if type(a:1) == type([])
+ let args = a:1
+ else
+ let args = a:000
+ endif
+ endif
+ return map(repeat([0], a:length), 'call("RandomChar", args)')
+endfunction
+
+function! RandomString(length, ...)
+ let args = []
+ if a:0 > 0
+ if type(a:1) == type([])
+ let args = a:1
+ else
+ let args = a:000
+ endif
+ endif
+ return join(call('RandomChars', [a:length, args]), '')
+endfunction
+
+let s:chars = '! " # $ % & '' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~'
+let s:charlist = split(s:chars, ' ')
+
+function! RandomCharFromRegex(regex, ...)
+ let charlist = a:0 ? split(a:1, '\zs') : copy(s:charlist)
+ call filter(charlist, 'v:val =~ a:regex')
+ return charlist[RandomNumber(0, len(charlist) - 1)]
+endfunction
+
+function! RandomStringFromRegex(regex, lenght, ...)
+ let charlist = a:0 ? split(a:1, '\zs') : copy(s:charlist)
+ call filter(charlist, 'v:val =~ a:regex')
+ let len = len(charlist) - 1
+ return join(map(range(a:lenght), 'charlist[RandomNumber(0, len)]'), '')
+endfunction
diff --git a/stow/vim/dot-vimrc b/stow/vim/dot-vimrc
new file mode 100644
index 0000000..9520648
--- /dev/null
+++ b/stow/vim/dot-vimrc
@@ -0,0 +1,280 @@
+" _
+" __ _(_)_ __ ___ _ __ ___
+" \ \ / / | '_ ` _ \| '__/ __|
+" \ V /| | | | | | | | | (__
+" \_/ |_|_| |_| |_|_| \___|
+"
+
+let mapleader = ","
+
+call plug#begin('~/.vim/plugged') "Update with PlugInstall
+Plug 'PotatoesMaster/i3-vim-syntax'
+Plug 'junegunn/goyo.vim'
+Plug 'junegunn/fzf.vim'
+Plug 'junegunn/limelight.vim'
+Plug 'arcticicestudio/nord-vim'
+Plug 'tpope/vim-surround'
+Plug 'tpope/vim-repeat'
+Plug 'vimwiki/vimwiki'
+Plug 'vim-airline/vim-airline'
+Plug 'psliwka/vim-smoothie'
+"Plug 'vim-syntastic/syntastic'
+Plug 'dense-analysis/ale'
+"Plug 'Shougo/deoplete.nvim', {'do': ':UpdateRemotePlugins' }
+"Plug 'Shougo/deoplete-clangx'
+"Plug 'neoclide/coc.nvim', {'branch': 'release'}
+Plug 'Valloric/YouCompleteMe'
+"Plug 'OmniSharp/omnisharp-vim'
+Plug 'dylanaraps/wal.vim'
+"Plug 'ThePrimeagen/vim-be-good'
+call plug#end()
+
+" Basic settings
+set nocompatible
+set encoding=utf-8
+set number relativenumber
+set tabstop=4
+set shiftwidth=4
+set scrolloff=2
+set wildmode=longest,list,full
+set splitbelow
+set splitright
+set vb " No bell
+" Searching
+set ignorecase
+set wrapscan
+set incsearch
+set nohlsearch
+
+" Filetypes
+filetype indent plugin on
+au BufRead,BufNewFile *.pu,*.puml,*.pumlc set filetype=plantuml
+au BufRead,BufNewFile *.g set filetype=antlr3
+au BufRead,BufNewFile *.g4 set filetype=antlr4
+au BufRead,BufNewFile *.sent set filetype=sent
+let g:tex_flavor = "latex"
+
+" hlnext: Highlighting searches
+nnoremap <silent> n n:call HLNext(0.1)<CR>
+nnoremap <silent> N N:call HLNext(0.1)<CR>
+function! HLNext(blinktime)
+ set invcursorline
+ redraw
+ exec 'sleep'.float2nr(a:blinktime*1000).'m'
+ set invcursorline
+ redraw
+endfunction
+
+" dragvisuals: dragging visual blocks
+vmap <expr> <LEFT> DVB_Drag('left')
+vmap <expr> <RIGHT> DVB_Drag('right')
+vmap <expr> <DOWN> DVB_Drag('down')
+vmap <expr> <UP> DVB_Drag('up')
+vmap <expr> D DVB_Duplicate()
+
+" Showing tabs
+"exec "set listchars=tab:\uBB\uBB,trail:\uB7,nbsp:~"
+exec "set listchars=tab:\uBB·,trail:\uB7,nbsp:~"
+nnoremap <leader>l :set list!<CR>
+
+" For deoplete
+
+"let g:deoplete#enable_at_startup = 1
+"
+"call deoplete#custom#option({
+"\ 'auto_complete': v:true,
+"\ 'auto_complete_delay': 0,
+"\ 'smart_case': v:true,
+"\ })
+"
+"call deoplete#custom#option('sources', {
+"\ '_': ['ale'],
+"\})
+
+" For CoC
+"inoremap <silent><expr> <c-space> coc#refresh()
+
+" For YouCompleteMe
+let g:ycm_auto_trigger = 1
+let g:ycm_autoclose_preview_window_after_insertion = 1
+
+" For syntastic
+"set statusline+=%#warningmsg#
+"set statusline+=%{SyntasticStatuslineFlag()}
+"set statusline+=%*
+"let g:syntastic_cs_checkers = ['code_checker']
+"let g:syntastic_enable_signs = 1
+"let g:syntastic_aggregate_errors = 1
+"let g:syntastic_always_populate_loc_list = 1
+"let g:syntastic_auto_loc_list = 2
+"let g:syntastic_check_on_open = 1
+"let g:syntastic_check_on_wq = 0
+
+" For omnisharp
+"let g:OmniSharp_server_stdio = 1
+"let g:OmniSharp_server_path = '/home/taamas/repos/omnisharp-roslyn/artifacts/scripts/OmniSharp.Stdio'
+"let g:OmniSharp_selector_ui = 'fzf'
+
+" For cursor shape with tmux
+if exists('$TMUX')
+ let &t_SI = "\<Esc>Ptmux;\<Esc>\e[5 q\<Esc>\\"
+ let &t_EI = "\<Esc>Ptmux;\<Esc>\e[2 q\<Esc>\\"
+else
+ let &t_SI = "\e[5 q"
+ let &t_EI = "\e[2 q"
+endif
+
+"if empty($TMUX)
+" let &t_SI = "\<Esc>]50;CursorShape=1\x7" " Vertical bar in insert mode
+" let &t_EI = "\<Esc>]50;CursorShape=0\x7" " Block in normal mode
+"else
+" let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\"
+" let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\"
+"endif
+
+"if &term =~ '^screen'
+" " tmux will send xterm-style keys when its xterm-keys option is on
+" execute "set <xUp>=\e[1;*A"
+" execute "set <xDown>=\e[1;*B"
+" execute "set <xRight>=\e[1;*C"
+" execute "set <xLeft>=\e[1;*D"
+"endif
+
+" For vim-airline
+if !exists('g:airline_symbols')
+ let g:airline_symbols = {}
+endif
+let g:airline_left_sep=''
+let g:airline_right_sep=''
+let g:airline_symbols.linenr = '🔃'
+let g:airline_symbols.maxlinenr = '↩'
+
+" For vimwiki
+" filetype plugin on
+let g:vimwiki_ext2syntax = {'.md': 'markdown', '.markdown': 'markdown', '.mdown': 'markdown'}
+let g:vimwiki_list = [{'path': '~/docs/vimwiki/', 'syntax': 'markdown', 'ext': '.md'},
+ \ {'path': '~/docs/notas/', 'syntax': 'markdown', 'ext': '.md'},
+ \ {'path': '~/games/dungeonesYDragones/tarkba/vimwiki/', 'syntax': 'markdown', 'ext': '.md'},
+ \ {'path': '~/games/dungeonesYDragones/creaciones/vimwiki/', 'syntax': 'markdown', 'ext': '.md'}]
+
+" Vertical split character
+set fillchars+=vert:â–ˆ
+
+" Shortcuts for split navigation
+map <C-h> <C-w>h
+map <C-j> <C-w>j
+map <C-k> <C-w>k
+map <C-l> <C-w>l
+map <A-h> <C-w><
+map <A-j> <C-w>+
+map <A-k> <C-w>-
+map <A-l> <C-w>>
+map <A-H> <C-w>10<
+map <A-J> <C-w>5+
+map <A-K> <C-w>5-
+map <A-L> <C-w>10>
+
+" Shortcuts for opening netrw
+nnoremap <leader>f :40Vexplore<CR>
+nnoremap <leader>F :40Lexplore<CR>
+
+" FZF
+nnoremap <C-p> :Files<CR>
+
+" Moving -here-
+nnoremap <leader>h :cd %:h<CR>
+
+nnoremap <C-n> :tabnew<CR>
+"nnoremap <Tab> gt "Can't be set without also remapping <C-i>
+"nnoremap <S-Tab> gT
+
+nnoremap <leader>vrc :80vsp ~/.vimrc<CR>
+nnoremap <leader>S :!tmux split-window -l '40\%'<CR><CR>
+
+nnoremap <leader>/ /grtfjx<CR>
+
+""" Color
+colorscheme nord
+
+" Syntax
+syntax enable
+syntax on
+
+" Line number color
+highlight LineNr ctermfg=12
+highlight LineNr cterm=NONE
+highlight CursorLineNr ctermfg=5
+highlight CursorLineNr cterm=underline
+
+" Completion color
+highlight Pmenu ctermbg=blue ctermfg=black
+highlight PmenuSel ctermbg=darkblue ctermfg=white
+
+" Panel border color
+"highlight VertSplit ctermbg=black ctermfg=darkblue
+
+" Symbol pairs match color
+highlight MatchParen ctermbg=cyan ctermfg=black
+
+" Errors colors
+highlight Error ctermbg=red ctermfg=black
+highlight SpellBad ctermbg=red ctermfg=black
+
+" Comments colors
+highlight Comment ctermfg=green
+
+" Line size marking
+highlight ColorColumn ctermbg=darkblue ctermfg=black
+"Redjail Bomb (thx Damian Conway!)
+"highlight ColorColumn ctermbg=red ctermfg=blue
+"exec 'set colorcolumn='.join(range(2,80,3), ',')
+
+"Clean trailing whitespaces on save
+autocmd BufWritePre * %s/\s\+$//e
+
+" Autoupdate ~/.Xresources
+autocmd BufWritePost ~/.Xresources !xrdb %
+
+" Generate ~/.mainpage/urls on ~/.config/qutebrowser save
+autocmd BufWritePost ~/.config/qutebrowser/config.py !grep -e \'.*\':\ \'.*{}.*\' "$HOME/.config/qutebrowser/config.py" | grep -v DEFAULT | sed 's/,//; s/^\ *//' > ~/.mainpage/urls
+
+""" Remaps """
+
+nnoremap Y y$
+nnoremap <leader>ss :set spell!<CR>
+nnoremap <leader>sl :set spelllang=
+nnoremap <leader>sL :setlocal spelllang=
+nnoremap <leader>e :Errors<Enter>
+nnoremap <leader>x :w<CR>:! ./%
+inoremap <leader>w <Esc>:w<Enter>
+inoremap <leader><leader> <Esc>/<++><Enter>cf>
+nnoremap <leader>p "+p
+nnoremap <leader>y "+yy
+vnoremap <leader>y "+y
+nnoremap <leader>d "+dd
+vnoremap <leader>d "+d
+
+" For Goyo
+function! s:goyo_enter()
+ silent !tmux set status off
+endfunction
+
+function! s:goyo_leave()
+ silent !tmux set status on
+endfunction
+
+autocmd! User GoyoEnter nested call <SID>goyo_enter()
+autocmd! User GoyoLeave nested call <SID>goyo_leave()
+
+let g:goyo_linenr = 1
+nnoremap <leader>G :Goyo \| set linebreak<CR>:e<CR>
+
+" For LimeLight
+let g:limelight_conceal_ctermfg = 7
+
+"" git
+nnoremap <leader>gs :!git status<CR>
+"nnoremap <leader>gpull :!git pull<CR>
+nnoremap <leader>ga :!git add %<CR>
+nnoremap <leader>gp :!git push<CR>
+nnoremap <leader>gc :!git commit -m "
diff --git a/stow/vis/dot-config/vis/colors/basic_colors b/stow/vis/dot-config/vis/colors/basic_colors
new file mode 100644
index 0000000..4be5fa4
--- /dev/null
+++ b/stow/vis/dot-config/vis/colors/basic_colors
@@ -0,0 +1,15 @@
+4
+12
+6
+14
+2
+10
+11
+3
+5
+1
+13
+9
+7
+15
+0
diff --git a/stow/vis/dot-config/vis/colors/rainbow b/stow/vis/dot-config/vis/colors/rainbow
new file mode 100644
index 0000000..13714c5
--- /dev/null
+++ b/stow/vis/dot-config/vis/colors/rainbow
@@ -0,0 +1,30 @@
+#1F80F3
+#08ADD8
+#06B3D4
+#01D7AF
+#01DBA9
+#0DF47E
+#26FE53
+#2CFF4B
+#4FF829
+#58F522
+#83DE0B
+#8FD506
+#ADBB01
+#BEAA01
+#D78C07
+#E27C0D
+#F65525
+#F94C2B
+#FE2A4D
+#FE2456
+#F20B81
+#D901AC
+#D501B2
+#B007D6
+#AA09DA
+#7C21F4
+#6D2DF9
+#5541FE
+#3E58FE
+#2A70F9
diff --git a/stow/vis/dot-config/vis/config b/stow/vis/dot-config/vis/config
new file mode 100644
index 0000000..a68497a
--- /dev/null
+++ b/stow/vis/dot-config/vis/config
@@ -0,0 +1,91 @@
+##Refresh rate of the visualizers. A really high refresh rate may cause screen tearing. Default is 20.
+#visualizer.fps=20
+
+##Sets the audio sources to use. Currently available ones are "mpd" and "alsa"Sets the audio sources to use.
+##Currently available ones are "mpd", "pulse" and "alsa". Defaults to "mpd".
+#audio.sources=pulse
+
+##vis tries to find the correct pulseaudio sink, however this will not work on all systems.
+##If pulse audio is not working with vis try switching the audio source. A list can be found by running the
+##command pacmd list-sinks | grep -e 'name:' -e 'index'
+#audio.pulse.source=0
+
+##Defaults to "/tmp/mpd.fifo"
+#mpd.fifo.path=/tmp/mpd.fifo
+
+##If set to false the visualizers will use mono mode instead of stereo. Some visualizers will
+##behave differently when mono is enabled. For example, spectrum show two sets of bars.
+#audio.stereo.enabled=false
+
+##Specifies how often the visualizer will change in seconds. 0 means do not rotate. Default is 0.
+#visualizer.rotation.secs=10
+
+##Configures the samples rate and the cutoff frequencies.
+#audio.sampling.frequency=44100
+#audio.low.cutoff.frequency=30
+#audio.high.cutoff.frequency=22050
+
+##Applies scaling factor to both lorenz and ellipse visualizers. This is useful when the system audio is set
+#to a low volume.
+#visualizer.scaling.multiplier=1.0
+
+##Configures the visualizers and the order they are in. Available visualizers are spectrum,lorenz,ellipse.
+##Defaults to spectrum,ellipse,lorenz
+#visualizers=spectrum,ellipse,lorenz
+
+
+##Configures what character the spectrum visualizer will use. Specifying a space (e.g " ") means the
+##background will be colored instead of the character. Defaults to " ".
+#visualizer.spectrum.character=#
+
+##Spectrum bar width. Defaults to 2.
+#visualizer.spectrum.bar.width=2
+
+##The amount of space between each bar in the spectrum visualizer. Defaults to 1. It's possible to set this to
+##zero to have no space between bars
+#visualizer.spectrum.bar.spacing=1
+
+##Available smoothing options are monstercat, sgs, none.
+#visualizer.spectrum.smoothing.mode=sgs
+
+##This configures the falloff effect on the spectrum visualizer. Available falloff options are fill,top,none.
+##Defaults to "fill"
+#visualizer.spectrum.falloff.mode=fill
+
+##Configures how fast the falloff character falls. This is an exponential falloff so values usually look
+##best 0.9+ and small changes in this value can have a large effect. Defaults to 0.95
+#visualizer.spectrum.falloff.weight=0.95
+
+##Margins in percent of total screen for spectrum visualizer. All margins default to 0
+#visualizer.spectrum.top.margin=0.30
+#visualizer.spectrum.bottom.margin=0.10
+#visualizer.spectrum.right.margin=0.10
+#visualizer.spectrum.left.margin=0.10
+
+##Reverses the direction of the spectrum so that high freqs are first and low freqs last. Defaults to false.
+#visualizer.spectrum.reversed=false
+
+##This configures the sgs smoothing effect on the spectrum visualizer. More points spreads out the smoothing
+##effect and increasing passes runs the smoother multiple times on reach run. Defaults are points=3 and passes=1
+#visualizer.sgs.smoothing.points=3
+#visualizer.sgs.smoothing.passes=1
+
+
+##Configures what character the ellipse visualizer will use. Specifying a space (e.g " ") means the
+##background will be colored instead of the character. Defaults to "â–ˆ".
+#visualizer.ellipse.character=#
+
+##The radius of each color ring in the ellipse visualizer. Defaults to 2.
+#visualizer.ellipse.radius=2
+
+
+##Configures what character the lorenz visualizer will use. Specifying a space (e.g " ") means the
+##background will be colored instead of the character. Defaults to "â–ˆ".
+#visualizer.lorenz.character=#
+
+
+##Turns off overriding the user's terminal colors. This is true by default.
+#colors.override.terminal=false
+
+##Specifies the color scheme. The color scheme must be in ~/.config/vis/colors/ directory. The default scheme is "colors".
+#colors.scheme=rainbow
diff --git a/stow/zathura/dot-config/zathura/zathurarc b/stow/zathura/dot-config/zathura/zathurarc
new file mode 100644
index 0000000..78e873b
--- /dev/null
+++ b/stow/zathura/dot-config/zathura/zathurarc
@@ -0,0 +1,58 @@
+set page-padding 1
+set selection-clipboard clipboard
+set statusbar-home-tilde true
+set guioptions shv
+
+# Colors
+
+# Outside the document
+set default-bg "#000000"
+
+# Status bar
+set statusbar-bg "#1d2021"
+set statusbar-fg "#fbf1c7"
+set font "mononoki 10"
+
+# Document light and dark colors
+set recolor false # recolor at start
+set recolor-keephue false
+
+#set recolor-lightcolor "#232f40"
+set recolor-lightcolor "#1d2021"
+
+# Solarized
+# Dark
+#set recolor-darkcolor "#fdf6e3" #base3 (lighest)
+set recolor-darkcolor "#eee8d5" #base2
+#set recolor-darkcolor "#93a1a1" #base1
+#set recolor-darkcolor "#2aa198" #cyan
+#set recolor-darkcolor "#268bd2" #blue
+#set recolor-lightcolor "#073642" #base02
+#set recolor-lightcolor "#002b36" #base03 (darkest)
+# Light
+#set recolor-darkcolor "#002b36" #base03 (darkest)
+#set recolor-lightcolor "#fdf6e3" #base3 (lighest)
+
+# based on custom dmenu
+#set recolor-darkcolor "#ffffff"
+#set recolor-lightcolor "#093145"
+
+# Keep images color
+set recolor-reverse-video false
+# Use same hue when recoloring
+set recolor-keephue false
+
+map u scroll half-up
+map d scroll half-down
+map D toggle_page_mode
+map b toggle_statusbar
+map r reload
+map R rotate
+map p print
+map Y exec 'echo "%" | xsel -bi; dunstify "%"'
+map I exec '/home/taamas/scripts/pdfs/pdfSxiv.sh "%"'
+#map y feedkeys "<Mod4>d"
+#map P change_mode presentation
+
+#map x exec 'scp "%" taamas@93.156.110.0:~/pdfs'
+#map X exec 'scp "%" taamas@93.156.110.0:/srv/http/files'