diff options
Diffstat (limited to 'stow')
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' |