dotfiles

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 80ef8313b5f7275443e956982ecd31f3a0e9cd3b
parent 9ac9a34e2fd9e3eac0ab7078cb27ce889e3e7678
Author: Torpus <lain1941@disroot.org>
Date:   Sun, 11 May 2025 10:46:06 +0100

Add config and local

Diffstat:
A.config/htop/htoprc | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/lf/cleaner | 4++++
A.config/lf/icons | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/lf/lfrc | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/lf/scope | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/lf/shortcutrc | 29+++++++++++++++++++++++++++++
A.config/nvim/autoload/plug.vim | 2863+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/init.vim | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/nvim/plugged/goyo.vim | 1+
A.config/nvim/plugged/nerdtree | 1+
A.config/nvim/plugged/vim-airline | 1+
A.config/nvim/plugged/vim-commentary | 1+
A.config/nvim/plugged/vim-css-color | 1+
A.config/nvim/plugged/vim-surround | 1+
A.config/nvim/plugged/vimagit | 1+
A.config/nvim/plugged/vimwiki | 1+
A.config/nvim/shortcuts.vim | 29+++++++++++++++++++++++++++++
A.config/shell/aliasrc | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/shell/bm-dirs | 14++++++++++++++
A.config/shell/bm-files | 23+++++++++++++++++++++++
A.config/shell/inputrc | 19+++++++++++++++++++
A.config/shell/profile | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/shell/shortcutenvrc | 30++++++++++++++++++++++++++++++
A.config/shell/shortcutrc | 30++++++++++++++++++++++++++++++
A.config/shell/zshnameddirrc | 29+++++++++++++++++++++++++++++
A.local/bin/arkenfox-auto-update | 23+++++++++++++++++++++++
A.local/bin/booksplit | 43+++++++++++++++++++++++++++++++++++++++++++
A.local/bin/compiler | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/cron/README.md | 11+++++++++++
A.local/bin/cron/checkup | 17+++++++++++++++++
A.local/bin/cron/crontog | 6++++++
A.local/bin/cron/newsup | 15+++++++++++++++
A.local/bin/displayselect | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/dmenuhandler | 21+++++++++++++++++++++
A.local/bin/dmenumountcifs | 19+++++++++++++++++++
A.local/bin/dmenupass | 6++++++
A.local/bin/dmenurecord | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/dmenuunicode | 18++++++++++++++++++
A.local/bin/getbib | 14++++++++++++++
A.local/bin/getcomproot | 9+++++++++
A.local/bin/getkeys | 5+++++
A.local/bin/ifinstalled | 12++++++++++++
A.local/bin/lfub | 24++++++++++++++++++++++++
A.local/bin/linkhandler | 26++++++++++++++++++++++++++
A.local/bin/maimpick | 20++++++++++++++++++++
A.local/bin/mounter | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/noisereduce | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/opout | 13+++++++++++++
A.local/bin/otp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/pauseallmpv | 10++++++++++
A.local/bin/peertubetorrent | 9+++++++++
A.local/bin/podentr | 7+++++++
A.local/bin/qndl | 12++++++++++++
A.local/bin/queueandnotify | 14++++++++++++++
A.local/bin/remapd | 8++++++++
A.local/bin/remaps | 11+++++++++++
A.local/bin/rotdir | 12++++++++++++
A.local/bin/rssadd | 18++++++++++++++++++
A.local/bin/sd | 22++++++++++++++++++++++
A.local/bin/setbg | 41+++++++++++++++++++++++++++++++++++++++++
A.local/bin/shortcuts | 44++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/slider | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/start-redshift.sh | 3+++
A.local/bin/statusbar/sb-battery | 37+++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-brightness | 23+++++++++++++++++++++++
A.local/bin/statusbar/sb-clock | 29+++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-cpu | 12++++++++++++
A.local/bin/statusbar/sb-cpubars | 44++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-disk | 23+++++++++++++++++++++++
A.local/bin/statusbar/sb-doppler | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-forecast | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-help-icon | 17+++++++++++++++++
A.local/bin/statusbar/sb-internet | 33+++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-iplocate | 15+++++++++++++++
A.local/bin/statusbar/sb-kbselect | 17+++++++++++++++++
A.local/bin/statusbar/sb-mailbox | 20++++++++++++++++++++
A.local/bin/statusbar/sb-memory | 12++++++++++++
A.local/bin/statusbar/sb-moonphase | 37+++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-mpdup | 8++++++++
A.local/bin/statusbar/sb-music | 19+++++++++++++++++++
A.local/bin/statusbar/sb-nettraf | 29+++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-news | 17+++++++++++++++++
A.local/bin/statusbar/sb-pacpackages | 29+++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-popupgrade | 9+++++++++
A.local/bin/statusbar/sb-price | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/statusbar/sb-tasks | 20++++++++++++++++++++
A.local/bin/statusbar/sb-torrent | 27+++++++++++++++++++++++++++
A.local/bin/statusbar/sb-volume | 39+++++++++++++++++++++++++++++++++++++++
A.local/bin/sysact | 26++++++++++++++++++++++++++
A.local/bin/tag | 49+++++++++++++++++++++++++++++++++++++++++++++++++
A.local/bin/td-toggle | 12++++++++++++
A.local/bin/texclear | 9+++++++++
A.local/bin/torwrap | 7+++++++
A.local/bin/transadd | 9+++++++++
A.local/bin/tutorialvids | 26++++++++++++++++++++++++++
A.local/bin/unix | 26++++++++++++++++++++++++++
A.local/bin/unmounter | 28++++++++++++++++++++++++++++
A.local/bin/weath | 25+++++++++++++++++++++++++
A.local/bin/xdg-terminal-exec | 3+++
A.local/share/asuka.jpeg | 0
A.local/share/bg | 2++
A.local/share/lain.jpeg | 0
A.local/share/lain.jpg | 0
A.local/share/rena.jpg | 0
A.local/src/dmenu | 1+
A.local/src/dwm | 1+
A.local/src/dwmblocks | 1+
A.local/src/st | 1+
A.local/src/yay | 1+
109 files changed, 6054 insertions(+), 0 deletions(-)

diff --git a/.config/htop/htoprc b/.config/htop/htoprc @@ -0,0 +1,63 @@ +# Beware! This file is rewritten by htop when settings are changed in the interface. +# The parser is also very primitive, and not human-friendly. +htop_version=3.3.0 +config_reader_min_version=3 +fields=0 48 17 18 38 39 40 2 46 47 49 1 +hide_kernel_threads=1 +hide_userland_threads=1 +hide_running_in_container=0 +shadow_other_users=1 +show_thread_names=0 +show_program_path=1 +highlight_base_name=0 +highlight_deleted_exe=1 +shadow_distribution_path_prefix=0 +highlight_megabytes=1 +highlight_threads=1 +highlight_changes=0 +highlight_changes_delay_secs=5 +find_comm_in_cmdline=1 +strip_exe_from_cmdline=1 +show_merged_command=0 +header_margin=1 +screen_tabs=1 +detailed_cpu_time=0 +cpu_count_from_one=1 +show_cpu_usage=1 +show_cpu_frequency=1 +show_cpu_temperature=1 +degree_fahrenheit=0 +update_process_names=0 +account_guest_in_cpu_meter=0 +color_scheme=0 +enable_mouse=1 +delay=12 +hide_function_bar=0 +header_layout=two_50_50 +column_meters_0=AllCPUs Memory Swap +column_meter_modes_0=1 1 1 +column_meters_1=Tasks LoadAverage Uptime +column_meter_modes_1=2 2 2 +tree_view=1 +sort_key=47 +tree_sort_key=47 +sort_direction=-1 +tree_sort_direction=1 +tree_view_always_by_pid=0 +all_branches_collapsed=0 +screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command +.sort_key=PERCENT_MEM +.tree_sort_key=PERCENT_MEM +.tree_view_always_by_pid=0 +.tree_view=1 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 +screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE Command +.sort_key=IO_RATE +.tree_sort_key=PID +.tree_view_always_by_pid=0 +.tree_view=0 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 diff --git a/.config/lf/cleaner b/.config/lf/cleaner @@ -0,0 +1,4 @@ +#!/bin/sh +if [ -n "$FIFO_UEBERZUG" ]; then + printf '{"action": "remove", "identifier": "PREVIEW"}\n' > "$FIFO_UEBERZUG" +fi diff --git a/.config/lf/icons b/.config/lf/icons @@ -0,0 +1,77 @@ +di ๐Ÿ“ +fi ๐Ÿ“ƒ +tw ๐Ÿค +ow ๐Ÿ“‚ +ln โ›“ +or โŒ +ex ๐ŸŽฏ +*.txt โœ +*.mom โœ +*.me โœ +*.ms โœ +*.avif ๐Ÿ–ผ +*.png ๐Ÿ–ผ +*.webp ๐Ÿ–ผ +*.ico ๐Ÿ–ผ +*.jpg ๐Ÿ“ธ +*.jpe ๐Ÿ“ธ +*.jpeg ๐Ÿ“ธ +*.gif ๐Ÿ–ผ +*.svg ๐Ÿ—บ +*.tif ๐Ÿ–ผ +*.tiff ๐Ÿ–ผ +*.xcf ๐Ÿ–Œ +*.htm ๐ŸŒŽ +*.xml ๐Ÿ“ฐ +*.gpg ๐Ÿ”’ +*.css ๐ŸŽจ +*.pdf ๐Ÿ“š +*.djvu ๐Ÿ“š +*.epub ๐Ÿ“š +*.csv ๐Ÿ““ +*.xlsx ๐Ÿ““ +*.tex ๐Ÿ“œ +*.md ๐Ÿ“˜ +*.r ๐Ÿ“Š +*.R ๐Ÿ“Š +*.rmd ๐Ÿ“Š +*.Rmd ๐Ÿ“Š +*.m ๐Ÿ“Š +*.mp3 ๐ŸŽต +*.opus ๐ŸŽต +*.ogg ๐ŸŽต +*.m4a ๐ŸŽต +*.flac ๐ŸŽผ +*.wav ๐ŸŽผ +*.mkv ๐ŸŽฅ +*.mp4 ๐ŸŽฅ +*.webm ๐ŸŽฅ +*.mpeg ๐ŸŽฅ +*.avi ๐ŸŽฅ +*.mov ๐ŸŽฅ +*.mpg ๐ŸŽฅ +*.wmv ๐ŸŽฅ +*.m4b ๐ŸŽฅ +*.flv ๐ŸŽฅ +*.zip ๐Ÿ“ฆ +*.rar ๐Ÿ“ฆ +*.7z ๐Ÿ“ฆ +*.tar ๐Ÿ“ฆ +*.z64 ๐ŸŽฎ +*.v64 ๐ŸŽฎ +*.n64 ๐ŸŽฎ +*.gba ๐ŸŽฎ +*.nes ๐ŸŽฎ +*.gdi ๐ŸŽฎ +*.1 โ„น +*.nfo โ„น +*.info โ„น +*.log ๐Ÿ“™ +*.iso ๐Ÿ“€ +*.img ๐Ÿ“€ +*.bib ๐ŸŽ“ +*.ged ๐Ÿ‘ช +*.part ๐Ÿ’” +*.torrent ๐Ÿ”ฝ +*.jar โ™จ +*.java โ™จ diff --git a/.config/lf/lfrc b/.config/lf/lfrc @@ -0,0 +1,193 @@ +# Luke's lf settings + + +# Note on Image Previews +# For those wanting image previews, like this system, there are four steps to +# set it up. These are done automatically for LARBS users, but I will state +# them here for others doing it manually. +# +# 1. ueberzug must be installed. +# 2. The scope file (~/.config/lf/scope for me), must have a command similar to +# mine to generate ueberzug images. +# 3. A `set cleaner` line as below is a cleaner script. +# 4. lf should be started through a wrapper script (~/.local/bin/lfub for me) +# that creates the environment for ueberzug. This command can be be aliased +# in your shellrc (`alias lf="lfub") or if set to a binding, should be +# called directly instead of normal lf. + +# Basic vars +set shellopts '-eu' +set ifs "\n" +set scrolloff 10 +set icons +set period 1 +set hiddenfiles ".*:*.aux:*.log:*.bbl:*.bcf:*.blg:*.run.xml" +set cleaner '~/.config/lf/cleaner' +set previewer '~/.config/lf/scope' +set autoquit true + +# cmds/functions +cmd open ${{ + case $(file --mime-type "$(readlink -f $f)" -b) in + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) localc $fx ;; + image/vnd.djvu|application/pdf|application/postscript) setsid -f zathura $fx >/dev/null 2>&1 ;; + text/*|application/json|inode/x-empty|application/x-subrip) $EDITOR $fx;; + image/x-xcf) setsid -f gimp $f >/dev/null 2>&1 ;; + image/svg+xml) display -- $f ;; + image/*) rotdir $f | grep -i "\.\(png\|jpg\|jpeg\|gif\|webp\|avif\|tif\|ico\)\(_large\)*$" | + setsid -f nsxiv -aio 2>/dev/null | while read -r file; do + [ -z "$file" ] && continue + lf -remote "send select \"$file\"" + lf -remote "send toggle" + done & + ;; + audio/*|video/x-ms-asf) mpv --audio-display=no $f ;; + video/*) setsid -f mpv $f -quiet >/dev/null 2>&1 ;; + application/pdf|application/vnd.djvu|application/epub*) setsid -f zathura $fx >/dev/null 2>&1 ;; + application/pgp-encrypted) $EDITOR $fx ;; + application/vnd.openxmlformats-officedocument.wordprocessingml.document|application/vnd.oasis.opendocument.text|application/vnd.openxmlformats-officedocument.spreadsheetml.sheet|application/vnd.oasis.opendocument.spreadsheet|application/vnd.oasis.opendocument.spreadsheet-template|application/vnd.openxmlformats-officedocument.presentationml.presentation|application/vnd.oasis.opendocument.presentation-template|application/vnd.oasis.opendocument.presentation|application/vnd.ms-powerpoint|application/vnd.oasis.opendocument.graphics|application/vnd.oasis.opendocument.graphics-template|application/vnd.oasis.opendocument.formula|application/vnd.oasis.opendocument.database) setsid -f libreoffice $fx >/dev/null 2>&1 ;; + application/octet-stream) case ${f##*.} in + doc|docx|xls|xlsx|odt|ppt|pptx) setsid -f libreoffice $fx >/dev/null 2>&1 ;; + ghw) setsid -f gtkwave $f >/dev/null 2>&1 ;; + ts) setsid -f mpv $f -quiet >/dev/null 2>&1 ;; + *) setsid -f zathura $fx >/dev/null 2>&1 ;; + esac ;; + *) for f in $fx; do setsid -f $OPENER $f >/dev/null 2>&1; done;; + esac +}} + +cmd mkdir $mkdir -p "$@" + +cmd extract ${{ + clear; tput cup $(($(tput lines)/3)); tput bold + set -f + printf "%s\n\t" "$fx" + printf "extract?[y/N]" + read ans + [ $ans = "y" ] && { + case $fx in + *.tar.bz2) tar xjf $fx ;; + *.tar.gz) tar xzf $fx ;; + *.bz2) bunzip2 $fx ;; + *.rar) unrar e $fx ;; + *.gz) gunzip $fx ;; + *.tar) tar xf $fx ;; + *.tbz2) tar xjf $fx ;; + *.tgz) tar xzf $fx ;; + *.zip) unzip $fx ;; + *.Z) uncompress $fx ;; + *.7z) 7z x $fx ;; + *.tar.xz) tar xf $fx ;; + esac + } +}} + +cmd delete ${{ + clear; tput cup $(($(tput lines)/3)); tput bold + set -f + printf "%s\n\t" "$fx" + printf "delete?[y/N]" + read ans + [ $ans = "y" ] && rm -rf -- $fx +}} + +cmd moveto ${{ + set -f + dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Move to where? ' | sed 's|~|$HOME|') + [ -z "$dest" ] && exit + destpath=$(eval printf '%s' \"$dest\") + clear; tput cup $(($(tput lines)/3)); tput bold + echo "From:" + echo "$fx" | sed 's/^/ /' + printf "To:\n %s\n\n\tmove?[y/N]" "$destpath" + read -r ans + [ "$ans" != "y" ] && exit + for x in $fx; do + mv -iv "$x" "$destpath" + done && + notify-send "๐Ÿšš File(s) moved." "File(s) moved to $destpath." +}} + +cmd copyto ${{ + set -f + dest=$(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' "${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" | fzf --prompt 'Copy to where? ' | sed 's|~|$HOME|') + [ -z "$dest" ] && exit + destpath=$(eval printf '%s' \"$dest\") + clear; tput cup $(($(tput lines)/3)); tput bold + echo "From:" + echo "$fx" | sed 's/^/ /' + printf "To:\n %s\n\n\tcopy?[y/N]" "$destpath" + read -r ans + [ "$ans" != "y" ] && exit + for x in $fx; do + cp -ivr "$x" "$destpath" + done && + notify-send "๐Ÿ“‹ File(s) copied." "File(s) copied to $destpath." +}} + +cmd setbg "$1" + +cmd bulkrename ${{ + tmpfile_old="$(mktemp)" + tmpfile_new="$(mktemp)" + + [ -n "$fs" ] && fs=$(basename -a $fs) || fs=$(ls) + + echo "$fs" > "$tmpfile_old" + echo "$fs" > "$tmpfile_new" + $EDITOR "$tmpfile_new" + + [ "$(wc -l < "$tmpfile_old")" -eq "$(wc -l < "$tmpfile_new")" ] || { rm -f "$tmpfile_old" "$tmpfile_new"; exit 1; } + + paste "$tmpfile_old" "$tmpfile_new" | while IFS="$(printf '\t')" read -r src dst + do + [ "$src" = "$dst" ] || [ -e "$dst" ] || mv -- "$src" "$dst" + done + + rm -f "$tmpfile_old" "$tmpfile_new" + lf -remote "send $id unselect" +}} + +# Bindings +map <c-f> $lf -remote "send $id select \"$(fzf)\"" +map J $lf -remote "send $id cd $(sed -e 's/\s*#.*//' -e '/^$/d' -e 's/^\S*\s*//' ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs | fzf)" +map H cd ~ +map g top +map D delete +map E extract +map C copyto +map M moveto +map <c-n> push :mkdir<space>""<left> +map <c-r> reload +map <c-s> set hidden! +map <enter> shell +map x $$f +map X !$f +map o &mimeopen "$f" +map O $mimeopen --ask "$f" + +map A :rename; cmd-end # at the very end +map c push A<c-u> # new rename +map I :rename; cmd-home # at the very beginning +map i :rename # before extension +map a :rename; cmd-right # after extension +map B bulkrename +map b $setbg $f + +map <c-e> down +map <c-y> up +map V push :!nvim<space> + +map W $setsid -f $TERMINAL >/dev/null 2>&1 + +map U $printf "%s" "$fx" | xclip -selection clipboard +map u $printf "%s" "$fx" | sed 's/.*\///' | xclip -selection clipboard +map . $printf "%s" "$fx" | sed -E 's/^.+\[/https:\/\/www.youtube.com\/watch?v=/' | sed -E 's/\]\..+//' | xclip -selection clipboard +map <gt> $printf "%s" "$fx" | sed -E 's/^.+\[/https:\/\/piped.video\/watch?v=/' | sed -E 's/\]\..+//' | xclip -selection clipboard +map T $nsxiv -t "$(pwd)" # opens thumbnail mode +map <c-l> unselect + + + +# Source Bookmarks +source "~/.config/lf/shortcutrc" diff --git a/.config/lf/scope b/.config/lf/scope @@ -0,0 +1,62 @@ +#!/bin/sh + +# File preview handler for lf. + +set -C -f +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" + +image() { + if [ -f "$1" ] && [ -n "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && command -V ueberzug >/dev/null 2>&1; then + printf '{"action": "add", "identifier": "PREVIEW", "x": "%s", "y": "%s", "width": "%s", "height": "%s", "scaler": "contain", "path": "%s"}\n' "$4" "$5" "$(($2-1))" "$(($3-1))" "$1" > "$FIFO_UEBERZUG" + else + mediainfo "$6" + fi +} + +# Note that the cache file name is a function of file information, meaning if +# an image appears in multiple places across the machine, it will not have to +# be regenerated once seen. + +case "$(file --dereference --brief --mime-type -- "$1")" in + image/avif) CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE" ] && magick "$1" "$CACHE.jpg" + image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1" ;; + image/vnd.djvu) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE" ] && djvused "$1" -e 'select 1; save-page-with /dev/stdout' | magick -density 200 - "$CACHE.jpg" > /dev/null 2>&1 + image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1" ;; +image/svg+xml) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE" ] && inkscape --convert-dpi-method=none -o "$CACHE.png" --export-overwrite -D --export-png-color-mode=RGBA_16 "$1" + image "$CACHE.png" "$2" "$3" "$4" "$5" "$1" + ;; + image/x-xcf) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | awk '{print $1}')" + [ ! -f "$CACHE.jpg" ] && magick "$1[0]" "$CACHE.jpg" + image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1" + ;; + image/*) image "$1" "$2" "$3" "$4" "$5" "$1" ;; + text/html) lynx -width="$4" -display_charset=utf-8 -dump "$1" ;; + text/troff) man ./ "$1" | col -b ;; + text/* | */xml | application/json | application/x-ndjson) bat -p --theme ansi --terminal-width "$(($4-2))" -f "$1" ;; + audio/* | application/octet-stream) mediainfo "$1" || exit 1 ;; + video/* ) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE" ] && ffmpegthumbnailer -i "$1" -o "$CACHE" -s 0 + image "$CACHE" "$2" "$3" "$4" "$5" "$1" + ;; + */pdf) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE.jpg" ] && pdftoppm -jpeg -f 1 -singlefile "$1" "$CACHE" + image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1" + ;; + */epub+zip|*/mobi*) + CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/lf/thumb.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | cut -d' ' -f1)" + [ ! -f "$CACHE.jpg" ] && gnome-epub-thumbnailer "$1" "$CACHE.jpg" + image "$CACHE.jpg" "$2" "$3" "$4" "$5" "$1" + ;; + application/*zip) atool --list -- "$1" ;; + *opendocument*) odt2txt "$1" ;; + application/pgp-encrypted) gpg -d -- "$1" ;; +esac +exit 1 diff --git a/.config/lf/shortcutrc b/.config/lf/shortcutrc @@ -0,0 +1,29 @@ +map Ccac cd "/home/joseph/.cache" +map Ccf cd "/home/joseph/.config" +map CD cd "/home/joseph/Downloads" +map Cd cd "/home/joseph/Documents" +map Cdt cd "/home/joseph/.local/share" +map Crr cd "/home/joseph/.local/src" +map Ch cd "/home/joseph" +map Cm cd "/home/joseph/Music" +map Cmn cd "/mnt" +map Cpp cd "/home/joseph/Pictures" +map Csc cd "/home/joseph/.local/bin" +map Csrc cd "/home/joseph/.local/src" +map Cvv cd "/home/joseph/Videos" +map Ebf $$EDITOR "/home/joseph/.config/shell/bm-files" +map Ebd $$EDITOR "/home/joseph/.config/shell/bm-dirs" +map Ecfx $$EDITOR "/home/joseph/.config/x11/xresources" +map Ecfb $$EDITOR "~/.local/src/dwmblocks/config.h" +map Ecfv $$EDITOR "/home/joseph/.config/nvim/init.vim" +map Ecfz $$EDITOR "/home/joseph/.config/zsh/.zshrc" +map Ecfa $$EDITOR "/home/joseph/.config/shell/aliasrc" +map Ecfp $$EDITOR "/home/joseph/.config/shell/profile" +map Ecfm $$EDITOR "/home/joseph/.config/mutt/muttrc" +map Ecfn $$EDITOR "/home/joseph/.config/newsboat/config" +map Ecfu $$EDITOR "/home/joseph/.config/newsboat/urls" +map Ecfmb $$EDITOR "/home/joseph/.config/ncmpcpp/bindings" +map Ecfmc $$EDITOR "/home/joseph/.config/ncmpcpp/config" +map Ecfl $$EDITOR "/home/joseph/.config/lf/lfrc" +map EcfL $$EDITOR "/home/joseph/.config/lf/scope" +map EcfX $$EDITOR "/home/joseph/.config/nsxiv/exec/key-handler" diff --git a/.config/nvim/autoload/plug.vim b/.config/nvim/autoload/plug.vim @@ -0,0 +1,2863 @@ +" vim-plug: Vim plugin manager +" ============================ +" +" 1. Download plug.vim and put it in 'autoload' directory +" +" # Vim +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim +" +" # Neovim +" sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \ +" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim' +" +" 2. Add a vim-plug section to your ~/.vimrc (or ~/.config/nvim/init.vim for Neovim) +" +" call plug#begin() +" +" " List your plugins here +" Plug 'tpope/vim-sensible' +" +" call plug#end() +" +" 3. Reload the file or restart Vim, then you can, +" +" :PlugInstall to install plugins +" :PlugUpdate to update plugins +" :PlugDiff to review the changes from the last update +" :PlugClean to remove plugins no longer in the list +" +" For more information, see https://github.com/junegunn/vim-plug +" +" +" Copyright (c) 2024 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:is_powershell(shell) + return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$' +endfunction + +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 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 has('nvim') + let home = stdpath('data') . '/plugged' + 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\)\?$' || s:is_powershell(&shell)) + 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 get(g:, 'did_load_filetypes', 0) + 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') + if has('nvim-0.5.0') + call s:source(s:rtp(plug), 'ftdetect/**/*.lua', 'after/ftdetect/**/*.lua') + endif + 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-\><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') + if has('nvim-0.5.0') + call s:source(s:rtp(a:spec), 'plugin/**/*.lua', 'after/plugin/**/*.lua') + endif +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 s:is_powershell(&shell) + 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') + if has('nvim-0.5.0') " see neovim#14686 + call s:source(rtp, dir.'/**/*.lua') + endif + 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,plugAbort + syn match plugNumber /[0-9]\+[0-9.]*/ contained + syn match plugBracket /[[\]]/ contained + syn match plugX /x/ contained + syn match plugAbort /\~/ 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 plugAbort Ignore + 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', '-tabnew') +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(0) + if s:switch_in() + if b:plug_preview == 1 + pc + endif + enew + else + call s:new_window() + endif + + nnoremap <silent> <buffer> q :call <SID>close_pane()<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:close_pane() + if b:plug_preview == 1 + pc + let b:plug_preview = -1 + elseif exists('s:jobs') && !empty(s:jobs) + call s:job_abort(1) + else + bd + 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 s:is_powershell(&shell) + 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) + if has('nvim') + " Reset &rtp to invalidate Neovim cache of loaded Lua modules + " See https://github.com/junegunn/vim-plug/pull/1157#issuecomment-1809226110 + let &rtp = &rtp + endif + 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) + let error = 0 + 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) + let error = v:shell_error + endif + return [output, error] +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(deepcopy(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 + + " Set remote name, overriding a possible user git config's clone.defaultRemoteName + let s:clone_opt = ['--origin', 'origin'] + 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 + let out = '' + let error = 0 + if has_key(spec, 'commit') + call s:log4(name, 'Checking out '.spec.commit) + let [out, error] = 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) + let error = v:shell_error + endif + if !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) + let error = v:shell_error + endif + let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + if 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:mark_aborted(name, message) + let attrs = { 'running': 0, 'error': 1, 'abort': 1, 'lines': [a:message] } + let s:jobs[a:name] = extend(get(s:jobs, a:name, {}), attrs) +endfunction + +function! s:job_abort(cancel) + 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 + if a:cancel + call s:mark_aborted(name, 'Aborted') + endif + endfor + + if a:cancel + for todo in values(s:update.todo) + let todo.abort = 1 + endfor + else + let s:jobs = {} + endif +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:bullet_for(job, ...) + if a:job.running + return a:job.new ? '+' : '*' + endif + if get(a:job, 'abort', 0) + return '~' + endif + return a:job.error ? 'x' : get(a:000, 0, '-') +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 result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) + if len(result) + call s:log(s:bullet_for(self), self.name, result) + endif + 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(0) + 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, spec, queue, opts) + let job = { 'name': a:name, 'spec': a:spec, 'running': 1, 'error': 0, 'lines': [''], + \ 'new': get(a:opts, 'new', 0), 'queue': copy(a:queue) } + let Item = remove(job.queue, 0) + let argv = type(Item) == s:TYPE.funcref ? call(Item, [a:spec]) : Item + let s:jobs[a:name] = job + + if s:nvim + if has_key(a:opts, 'dir') + let job.cwd = a:opts.dir + endif + 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(argv), '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') ? [argv, a:opts.dir] : [argv])) + let job.error = v:shell_error != 0 + let job.running = 0 + endif +endfunction + +function! s:reap(name) + let job = remove(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 more = len(get(job, 'queue', [])) + let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) + if len(result) + call s:log(s:bullet_for(job), a:name, result) + endif + + if !job.error && more + let job.spec.queue = job.queue + let s:update.todo[a:name] = job.spec + else + let s:update.bar .= s:bullet_for(job, '=') + call s:bar() + endif +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:checkout_command(spec) + let a:spec.branch = s:git_origin_branch(a:spec) + return ['git', 'checkout', '-q', a:spec.branch, '--'] +endfunction + +function! s:merge_command(spec) + let a:spec.branch = s:git_origin_branch(a:spec) + return ['git', 'merge', '--ff-only', 'origin/'.a:spec.branch] +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) + if get(spec, 'abort', 0) + call s:mark_aborted(name, 'Skipped') + call s:reap(name) + continue + endif + + let queue = get(spec, 'queue', []) + let new = empty(globpath(spec.dir, '.git', 1)) + + if empty(queue) + call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') + redraw + endif + + let has_tag = has_key(spec, 'tag') + if len(queue) + call s:spawn(name, spec, queue, { 'dir': spec.dir }) + elseif !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 + let queue = [cmd, split('git remote set-head origin -a')] + if !has_tag && !has_key(spec, 'commit') + call extend(queue, [function('s:checkout_command'), function('s:merge_command')]) + endif + call s:spawn(name, spec, queue, { '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, spec, [extend(cmd, [spec.uri, s:trim(spec.dir)]), function('s:checkout_command'), function('s:merge_command')], { '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 s:is_powershell(shell) + 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 + let pwsh = s:is_powershell(&shell) + let cd = s:is_win && !pwsh ? 'cd /d' : 'cd' + let sep = pwsh ? ';' : '&&' + return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, 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 s:is_powershell(&shell) + 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 || len(ahead_behind) != 2 + let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required." + else + let [ahead, behind] = ahead_behind + if ahead && 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) + elseif ahead + 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) || get(spec, 'frozen', 0) + 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) + let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$') + if empty(name) + return + endif + let title = 'HEAD@{1}..' + let command = 'git diff --no-color HEAD@{1}' + else + let title = sha + let command = 'git show --no-color --pretty=medium '.sha + let name = s:find_name(line('.')) + endif + + if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) + return + endif + + if !s:is_preview_window_open() + execute get(g:, 'plug_pwindow', 'vertical rightbelow new') + execute 'e' title + else + execute 'pedit' title + wincmd P + endif + setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable + let batchfile = '' + try + let [sh, shellcmdflag, shrd] = s:chsh(1) + let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command + 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") && isdirectory(v:val.dir)'))) + for name in reverse(names) + let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : 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/.config/nvim/init.vim b/.config/nvim/init.vim @@ -0,0 +1,155 @@ +let mapleader ="," + +if ! filereadable(system('echo -n "${XDG_CONFIG_HOME:-$HOME/.config}/nvim/autoload/plug.vim"')) + echo "Downloading junegunn/vim-plug to manage plugins..." + silent !mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/nvim/autoload/ + silent !curl "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" > ${XDG_CONFIG_HOME:-$HOME/.config}/nvim/autoload/plug.vim + autocmd VimEnter * PlugInstall +endif + +map ,, :keepp /<++><CR>ca< +imap ,, <esc>:keepp /<++><CR>ca< + +call plug#begin(system('echo -n "${XDG_CONFIG_HOME:-$HOME/.config}/nvim/plugged"')) +Plug 'tpope/vim-surround' +Plug 'preservim/nerdtree' +Plug 'junegunn/goyo.vim' +Plug 'jreybert/vimagit' +Plug 'vimwiki/vimwiki' +Plug 'vim-airline/vim-airline' +Plug 'tpope/vim-commentary' +Plug 'ap/vim-css-color' +call plug#end() + +set title +set bg=light +set go=a +set mouse=a +set nohlsearch +set clipboard+=unnamedplus +set noshowmode +set noruler +set laststatus=0 +set noshowcmd +colorscheme vim + +" Some basics: + nnoremap c "_c + filetype plugin on + syntax on + set encoding=utf-8 + set number relativenumber +" Enable autocompletion: + set wildmode=longest,list,full +" Disables automatic commenting on newline: + autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o +" Perform dot commands over visual blocks: + vnoremap . :normal .<CR> +" Goyo plugin makes text more readable when writing prose: + map <leader>f :Goyo \| set bg=light \| set linebreak<CR> +" Spell-check set to <leader>o, 'o' for 'orthography': + map <leader>o :setlocal spell! spelllang=en_us<CR> +" Splits open at the bottom and right, which is non-retarded, unlike vim defaults. + set splitbelow splitright + +" Nerd tree + map <leader>n :NERDTreeToggle<CR> + autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif + let NERDTreeBookmarksFile = stdpath('data') . '/NERDTreeBookmarks' + +" vim-airline + if !exists('g:airline_symbols') + let g:airline_symbols = {} + endif + let g:airline_symbols.colnr = ' C:' + let g:airline_symbols.linenr = ' L:' + let g:airline_symbols.maxlinenr = 'โ˜ฐ ' + +" Shortcutting split navigation, saving a keypress: + map <C-h> <C-w>h + map <C-j> <C-w>j + map <C-k> <C-w>k + map <C-l> <C-w>l + +" Replace ex mode with gq + map Q gq + +" Check file in shellcheck: + map <leader>s :!clear && shellcheck -x %<CR> + +" Open my bibliography file in split + map <leader>b :vsp<space>$BIB<CR> + map <leader>r :vsp<space>$REFER<CR> + +" Replace all is aliased to S. + nnoremap S :%s//g<Left><Left> + +" Compile document, be it groff/LaTeX/markdown/etc. + map <leader>c :w! \| !compiler "%:p"<CR> + +" Open corresponding .pdf/.htm or preview + map <leader>p :!opout "%:p"<CR> + +" Runs a script that cleans out tex build files whenever I close out of a .tex file. + autocmd VimLeave *.tex !texclear % + +" Ensure files are read as what I want: + let g:vimwiki_ext2syntax = {'.Rmd': 'markdown', '.rmd': 'markdown','.md': 'markdown', '.markdown': 'markdown', '.mdown': 'markdown'} + map <leader>v :VimwikiIndex<CR> + let g:vimwiki_list = [{'path': '~/.local/share/nvim/vimwiki', 'syntax': 'markdown', 'ext': '.md'}] + autocmd BufRead,BufNewFile /tmp/calcurse*,~/.calcurse/notes/* set filetype=markdown + autocmd BufRead,BufNewFile *.ms,*.me,*.mom,*.man set filetype=groff + autocmd BufRead,BufNewFile *.tex set filetype=tex + +" Save file as sudo on files that require root permission + cabbrev w!! execute 'silent! write !sudo tee % >/dev/null' <bar> edit! + +" Enable Goyo by default for mutt writing + autocmd BufRead,BufNewFile /tmp/neomutt* :Goyo 80 | call feedkeys("jk") + autocmd BufRead,BufNewFile /tmp/neomutt* map ZZ :Goyo!\|x!<CR> + autocmd BufRead,BufNewFile /tmp/neomutt* map ZQ :Goyo!\|q!<CR> + +" Automatically deletes all trailing whitespace and newlines at end of file on save. & reset cursor position + autocmd BufWritePre * let currPos = getpos(".") + autocmd BufWritePre * %s/\s\+$//e + autocmd BufWritePre * %s/\n\+\%$//e + autocmd BufWritePre *.[ch] %s/\%$/\r/e " add trailing newline for ANSI C standard + autocmd BufWritePre *neomutt* %s/^--$/-- /e " dash-dash-space signature delimiter in emails + autocmd BufWritePre * cal cursor(currPos[1], currPos[2]) + +" When shortcut files are updated, renew bash and ranger configs with new material: + autocmd BufWritePost bm-files,bm-dirs !shortcuts +" Run xrdb whenever Xdefaults or Xresources are updated. + autocmd BufRead,BufNewFile Xresources,Xdefaults,xresources,xdefaults set filetype=xdefaults + autocmd BufWritePost Xresources,Xdefaults,xresources,xdefaults !xrdb % +" Recompile dwmblocks on config edit. + autocmd BufWritePost ~/.local/src/dwmblocks/config.h !cd ~/.local/src/dwmblocks/; sudo make install && { killall -q dwmblocks;setsid -f dwmblocks } + +" Turns off highlighting on the bits of code that are changed, so the line that is changed is highlighted but the actual text that has changed stands out on the line and is readable. +if &diff + highlight! link DiffText MatchParen +endif + +" Function for toggling the bottom statusbar: +let s:hidden_all = 0 +function! ToggleHiddenAll() + if s:hidden_all == 0 + let s:hidden_all = 1 + set noshowmode + set noruler + set laststatus=0 + set noshowcmd + else + let s:hidden_all = 0 + set showmode + set ruler + set laststatus=2 + set showcmd + endif +endfunction +nnoremap <leader>h :call ToggleHiddenAll()<CR> +" Load command shortcuts generated from bm-dirs and bm-files via shortcuts script. +" Here leader is ";". +" So ":vs ;cfz" will expand into ":vs /home/<user>/.config/zsh/.zshrc" +" if typed fast without the timeout. +silent! source ~/.config/nvim/shortcuts.vim diff --git a/.config/nvim/plugged/goyo.vim b/.config/nvim/plugged/goyo.vim @@ -0,0 +1 @@ +Subproject commit fa0263d456dd43f5926484d1c4c7022dfcb21ba9 diff --git a/.config/nvim/plugged/nerdtree b/.config/nvim/plugged/nerdtree @@ -0,0 +1 @@ +Subproject commit 9b465acb2745beb988eff3c1e4aa75f349738230 diff --git a/.config/nvim/plugged/vim-airline b/.config/nvim/plugged/vim-airline @@ -0,0 +1 @@ +Subproject commit 7a552f415c48aed33bf7eaa3c50e78504d417913 diff --git a/.config/nvim/plugged/vim-commentary b/.config/nvim/plugged/vim-commentary @@ -0,0 +1 @@ +Subproject commit 64a654ef4a20db1727938338310209b6a63f60c9 diff --git a/.config/nvim/plugged/vim-css-color b/.config/nvim/plugged/vim-css-color @@ -0,0 +1 @@ +Subproject commit 14fd934cdd9ca1ac0e53511094e612eb9bace373 diff --git a/.config/nvim/plugged/vim-surround b/.config/nvim/plugged/vim-surround @@ -0,0 +1 @@ +Subproject commit 3d188ed2113431cf8dac77be61b842acb64433d9 diff --git a/.config/nvim/plugged/vimagit b/.config/nvim/plugged/vimagit @@ -0,0 +1 @@ +Subproject commit fc7eda97da4f8182c8abbe6ea7befbd789b8b935 diff --git a/.config/nvim/plugged/vimwiki b/.config/nvim/plugged/vimwiki @@ -0,0 +1 @@ +Subproject commit 72792615e739d0eb54a9c8f7e0a46a6e2407c9e8 diff --git a/.config/nvim/shortcuts.vim b/.config/nvim/shortcuts.vim @@ -0,0 +1,29 @@ +cmap ;cac /home/joseph/.cache +cmap ;cf /home/joseph/.config +cmap ;D /home/joseph/Downloads +cmap ;d /home/joseph/Documents +cmap ;dt /home/joseph/.local/share +cmap ;rr /home/joseph/.local/src +cmap ;h /home/joseph +cmap ;m /home/joseph/Music +cmap ;mn /mnt +cmap ;pp /home/joseph/Pictures +cmap ;sc /home/joseph/.local/bin +cmap ;src /home/joseph/.local/src +cmap ;vv /home/joseph/Videos +cmap ;bf /home/joseph/.config/shell/bm-files +cmap ;bd /home/joseph/.config/shell/bm-dirs +cmap ;cfx /home/joseph/.config/x11/xresources +cmap ;cfb ~/.local/src/dwmblocks/config.h +cmap ;cfv /home/joseph/.config/nvim/init.vim +cmap ;cfz /home/joseph/.config/zsh/.zshrc +cmap ;cfa /home/joseph/.config/shell/aliasrc +cmap ;cfp /home/joseph/.config/shell/profile +cmap ;cfm /home/joseph/.config/mutt/muttrc +cmap ;cfn /home/joseph/.config/newsboat/config +cmap ;cfu /home/joseph/.config/newsboat/urls +cmap ;cfmb /home/joseph/.config/ncmpcpp/bindings +cmap ;cfmc /home/joseph/.config/ncmpcpp/config +cmap ;cfl /home/joseph/.config/lf/lfrc +cmap ;cfL /home/joseph/.config/lf/scope +cmap ;cfX /home/joseph/.config/nsxiv/exec/key-handler diff --git a/.config/shell/aliasrc b/.config/shell/aliasrc @@ -0,0 +1,67 @@ +#!/bin/sh + +# Use neovim for vim if present. +[ -x "$(command -v nvim)" ] && alias vim="nvim" vimdiff="nvim -d" + +# Use $XINITRC variable if file exists. +[ -f "$XINITRC" ] && alias startx="startx $XINITRC" + +[ -f "$MBSYNCRC" ] && alias mbsync="mbsync -c $MBSYNCRC" + +# doas not required for some system commands +for command in mount umount sv pacman updatedb su shutdown poweroff reboot ; do + alias $command="doas $command" +done; unset command + +se() { + choice="$(find ~/.local/bin -mindepth 1 -printf '%P\n' | fzf)" + [ -f "$HOME/.local/bin/$choice" ] && $EDITOR "$HOME/.local/bin/$choice" + } + +# Verbosity and settings that you pretty much just always are going to want. +alias \ + cp="cp -iv" \ + mv="mv -iv" \ + rm="rm -vI" \ + bc="bc -ql" \ + rsync="rsync -vrPlu" \ + mkdir="mkdir -pv" \ + yt="yt-dlp --embed-metadata -i" \ + yta="yt -x -f bestaudio/best" \ + ytt="yt --skip-download --write-thumbnail" \ + ffmpeg="ffmpeg -hide_banner" \ + hibernate="echo disk | doas tee /sys/power/state" \ + profanity="proxychains4 profanity" \ + sudo="doas" \ + paint="mtpaint" + +# Colorize commands when possible. +alias \ + ls="ls -hN --color=auto --group-directories-first" \ + grep="grep --color=auto" \ + diff="diff --color=auto" \ + ccat="highlight --out-format=ansi" \ + ip="ip -color=auto" + +# These common commands are just too long! Abbreviate them. +alias \ + ka="killall" \ + g="git" \ + trem="transmission-remote" \ + ytd="yt-dlp" \ + sdn="shutdown -h now" \ + e="$EDITOR" \ + v="$EDITOR" \ + p="pacman" \ + xi="doas xbps-install" \ + xr="doas xbps-remove -R" \ + xq="xbps-query" \ + z="zathura" \ + pipe="pipe-viewer" \ + gpipe="gtk-pipe-viewer" + +alias \ + lf="lfub" \ + magit="nvim -c MagitOnly" \ + ref="shortcuts >/dev/null; source ${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc ; source ${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutenvrc ; source ${XDG_CONFIG_HOME:-$HOME/.config}/shell/zshnameddirrc" + config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME' diff --git a/.config/shell/bm-dirs b/.config/shell/bm-dirs @@ -0,0 +1,14 @@ +# You can add comments to these files with # +cac ${XDG_CACHE_HOME:-$HOME/.cache} +cf ${XDG_CONFIG_HOME:-$HOME/.config} +D ${XDG_DOWNLOAD_DIR:-$HOME/Downloads} +d ${XDG_DOCUMENTS_DIR:-$HOME/Documents} +dt ${XDG_DATA_HOME:-$HOME/.local/share} +rr $HOME/.local/src +h $HOME +m ${XDG_MUSIC_DIR:-$HOME/Music} +mn /mnt +pp ${XDG_PICTURES_DIR:-$HOME/Pictures} +sc $HOME/.local/bin +src $HOME/.local/src +vv ${XDG_VIDEOS_DIR:-$HOME/Videos} diff --git a/.config/shell/bm-files b/.config/shell/bm-files @@ -0,0 +1,23 @@ +# These files automatically update when edited/saved in vim: + +# keys filename description +bf ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-files # This file, a list of bookmarked files +bd ${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs # A list of bookmarked directories similar to this file +cfx ${XDG_CONFIG_HOME:-$HOME/.config}/x11/xresources # Colors, themes and variables for X11 +cfb ~/.local/src/dwmblocks/config.h # dwmblocks: the status bar for dwm + + +# These do not update automatically, but on the next new instance of a program: + +cfv ${XDG_CONFIG_HOME:-$HOME/.config}/nvim/init.vim # vim/neovim config +cfz $ZDOTDIR/.zshrc # zsh (shell) config +cfa ${XDG_CONFIG_HOME:-$HOME/.config}/shell/aliasrc # aliases used by zsh (and potentially other shells) +cfp ${XDG_CONFIG_HOME:-$HOME/.config}/shell/profile # profile file for login settings for zsh +cfm ${XDG_CONFIG_HOME:-$HOME/.config}/mutt/muttrc # mutt (email client) config +cfn ${XDG_CONFIG_HOME:-$HOME/.config}/newsboat/config # newsboat (RSS reader) +cfu ${XDG_CONFIG_HOME:-$HOME/.config}/newsboat/urls # RSS urls for newsboat +cfmb ${XDG_CONFIG_HOME:-$HOME/.config}/ncmpcpp/bindings # ncmpcpp (music player) keybinds file +cfmc ${XDG_CONFIG_HOME:-$HOME/.config}/ncmpcpp/config # ncmpcpp (music player) config +cfl ${XDG_CONFIG_HOME:-$HOME/.config}/lf/lfrc # lf (file browser) config +cfL ${XDG_CONFIG_HOME:-$HOME/.config}/lf/scope # lf's scope/preview file +cfX ${XDG_CONFIG_HOME:-$HOME/.config}/nsxiv/exec/key-handler # nsxiv (image viewer) key/script handler diff --git a/.config/shell/inputrc b/.config/shell/inputrc @@ -0,0 +1,19 @@ +$include /etc/inputrc +set editing-mode vi +$if 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 + +set keymap vi-command +# these are for vi-command mode +Control-l: clear-screen +Control-a: beginning-of-line + +set keymap vi-insert +# these are for vi-insert mode +Control-l: clear-screen +Control-a: beginning-of-line + +$endif diff --git a/.config/shell/profile b/.config/shell/profile @@ -0,0 +1,79 @@ +#!/bin/sh +# shellcheck disable=SC2155 + +# Profile file, runs on login. Environmental variables are set here. + +# Add all directories in `~/.local/bin` to $PATH +export PATH="$PATH:$(find ~/.local/bin -type d | paste -sd ':' -)" + +unsetopt PROMPT_SP 2>/dev/null + +# Default programs: +export EDITOR="nvim" +export TERMINAL="st" +export TERMINAL_PROG="st" +export BROWSER="librewolf" + +# Change the default crypto/weather monitor sites. +# export CRYPTOURL="rate.sx" +# export WTTRURL="wttr.in" + +# ~/ Clean-up: +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_DATA_HOME="$HOME/.local/share" +export XDG_CACHE_HOME="$HOME/.cache" +export XINITRC="$XDG_CONFIG_HOME/x11/xinitrc" +#export XAUTHORITY="$XDG_RUNTIME_DIR/Xauthority" # This line will break some DMs. +export NOTMUCH_CONFIG="$XDG_CONFIG_HOME/notmuch-config" +export GTK2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc-2.0" +export WGETRC="$XDG_CONFIG_HOME/wget/wgetrc" +export INPUTRC="$XDG_CONFIG_HOME/shell/inputrc" +export ZDOTDIR="$XDG_CONFIG_HOME/zsh" +#export GNUPGHOME="$XDG_DATA_HOME/gnupg" +export WINEPREFIX="$XDG_DATA_HOME/wineprefixes/default" +export KODI_DATA="$XDG_DATA_HOME/kodi" +export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store" +export TMUX_TMPDIR="$XDG_RUNTIME_DIR" +export ANDROID_SDK_HOME="$XDG_CONFIG_HOME/android" +export CARGO_HOME="$XDG_DATA_HOME/cargo" +export GOPATH="$XDG_DATA_HOME/go" +export GOMODCACHE="$XDG_CACHE_HOME/go/mod" +export ANSIBLE_CONFIG="$XDG_CONFIG_HOME/ansible/ansible.cfg" +export UNISON="$XDG_DATA_HOME/unison" +export HISTFILE="$XDG_DATA_HOME/history" +export MBSYNCRC="$XDG_CONFIG_HOME/mbsync/config" +export ELECTRUMDIR="$XDG_DATA_HOME/electrum" +export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/pythonrc" +export SQLITE_HISTORY="$XDG_DATA_HOME/sqlite_history" + +# Other program settings: +export DICS="/usr/share/stardict/dic/" +export SUDO_ASKPASS="$HOME/.local/bin/dmenupass" +export FZF_DEFAULT_OPTS="--layout=reverse --height 40%" +export LESS="R" +export LESS_TERMCAP_mb="$(printf '%b' '')" +export LESS_TERMCAP_md="$(printf '%b' '')" +export LESS_TERMCAP_me="$(printf '%b' '')" +export LESS_TERMCAP_so="$(printf '%b' '')" +export LESS_TERMCAP_se="$(printf '%b' '')" +export LESS_TERMCAP_us="$(printf '%b' '')" +export LESS_TERMCAP_ue="$(printf '%b' '')" +export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null" +export QT_QPA_PLATFORMTHEME="gtk2" # Have QT use gtk2 theme. +export MOZ_USE_XINPUT2=1 # Mozilla smooth scrolling/touchpads. +export AWT_TOOLKIT="MToolkit wmname LG3D" # May have to install wmname +export _JAVA_AWT_WM_NONREPARENTING=1 # Fix for Java applications in dwm + +[ ! -f "$XDG_CONFIG_HOME/shell/shortcutrc" ] && setsid -f shortcuts >/dev/null 2>&1 + +# Start graphical server on user's current tty if not already running. +[ "$(tty)" = "/dev/tty1" ] && ! pidof -s Xorg >/dev/null 2>&1 && exec startx "$XINITRC" + +# Switch escape and caps if tty and no passwd required: +doas -n loadkeys "$XDG_DATA_HOME/larbs/ttymaps.kmap" 2>/dev/null + +# eval $(ssh-agent -s) +# ssh-add ~/.ssh/id_ed25519 &> /dev/null + +# export GEM_HOME="$(ruby -e 'puts Gem.user_dir')" +# export PATH="$PATH:$GEM_HOME/bin" diff --git a/.config/shell/shortcutenvrc b/.config/shell/shortcutenvrc @@ -0,0 +1,30 @@ +# vim: filetype=sh +[ -n "cac" ] && export cac="/home/lain/.cache" +[ -n "cf" ] && export cf="/home/lain/.config" +[ -n "D" ] && export D="/home/lain/dnl" +[ -n "d" ] && export d="/home/lain/dox" +[ -n "dt" ] && export dt="/home/lain/.local/share" +[ -n "rr" ] && export rr="/home/lain/.local/src" +[ -n "h" ] && export h="/home/lain" +[ -n "m" ] && export m="/home/lain/mus" +[ -n "mn" ] && export mn="/mnt" +[ -n "pp" ] && export pp="/home/lain/pix" +[ -n "sc" ] && export sc="/home/lain/.local/bin" +[ -n "src" ] && export src="/home/lain/.local/src" +[ -n "vv" ] && export vv="/home/lain/vid" +[ -n "bf" ] && export bf="/home/lain/.config/shell/bm-files" +[ -n "bd" ] && export bd="/home/lain/.config/shell/bm-dirs" +[ -n "cfx" ] && export cfx="/home/lain/.config/x11/xresources" +[ -n "cfb" ] && export cfb="~/.local/src/dwmblocks/config.h" +[ -n "cfv" ] && export cfv="/home/lain/.config/nvim/init.vim" +[ -n "cfz" ] && export cfz="/home/lain/.config/zsh/.zshrc" +[ -n "cfa" ] && export cfa="/home/lain/.config/shell/aliasrc" +[ -n "cfp" ] && export cfp="/home/lain/.config/shell/profile" +[ -n "cfm" ] && export cfm="/home/lain/.config/mutt/muttrc" +[ -n "cfn" ] && export cfn="/home/lain/.config/newsboat/config" +[ -n "cfu" ] && export cfu="/home/lain/.config/newsboat/urls" +[ -n "cfmb" ] && export cfmb="/home/lain/.config/ncmpcpp/bindings" +[ -n "cfmc" ] && export cfmc="/home/lain/.config/ncmpcpp/config" +[ -n "cfl" ] && export cfl="/home/lain/.config/lf/lfrc" +[ -n "cfL" ] && export cfL="/home/lain/.config/lf/scope" +[ -n "cfX" ] && export cfX="/home/lain/.config/nsxiv/exec/key-handler" diff --git a/.config/shell/shortcutrc b/.config/shell/shortcutrc @@ -0,0 +1,30 @@ +# vim: filetype=sh +alias cac="cd /home/lain/.cache && ls -A" \ +cf="cd /home/lain/.config && ls -A" \ +D="cd /home/lain/dnl && ls -A" \ +d="cd /home/lain/dox && ls -A" \ +dt="cd /home/lain/.local/share && ls -A" \ +rr="cd /home/lain/.local/src && ls -A" \ +h="cd /home/lain && ls -A" \ +m="cd /home/lain/mus && ls -A" \ +mn="cd /mnt && ls -A" \ +pp="cd /home/lain/pix && ls -A" \ +sc="cd /home/lain/.local/bin && ls -A" \ +src="cd /home/lain/.local/src && ls -A" \ +vv="cd /home/lain/vid && ls -A" \ +bf="$EDITOR /home/lain/.config/shell/bm-files" \ +bd="$EDITOR /home/lain/.config/shell/bm-dirs" \ +cfx="$EDITOR /home/lain/.config/x11/xresources" \ +cfb="$EDITOR ~/.local/src/dwmblocks/config.h" \ +cfv="$EDITOR /home/lain/.config/nvim/init.vim" \ +cfz="$EDITOR /home/lain/.config/zsh/.zshrc" \ +cfa="$EDITOR /home/lain/.config/shell/aliasrc" \ +cfp="$EDITOR /home/lain/.config/shell/profile" \ +cfm="$EDITOR /home/lain/.config/mutt/muttrc" \ +cfn="$EDITOR /home/lain/.config/newsboat/config" \ +cfu="$EDITOR /home/lain/.config/newsboat/urls" \ +cfmb="$EDITOR /home/lain/.config/ncmpcpp/bindings" \ +cfmc="$EDITOR /home/lain/.config/ncmpcpp/config" \ +cfl="$EDITOR /home/lain/.config/lf/lfrc" \ +cfL="$EDITOR /home/lain/.config/lf/scope" \ +cfX="$EDITOR /home/lain/.config/nsxiv/exec/key-handler" \ diff --git a/.config/shell/zshnameddirrc b/.config/shell/zshnameddirrc @@ -0,0 +1,29 @@ +hash -d cac=/home/lain/.cache +hash -d cf=/home/lain/.config +hash -d D=/home/lain/dnl +hash -d d=/home/lain/dox +hash -d dt=/home/lain/.local/share +hash -d rr=/home/lain/.local/src +hash -d h=/home/lain +hash -d m=/home/lain/mus +hash -d mn=/mnt +hash -d pp=/home/lain/pix +hash -d sc=/home/lain/.local/bin +hash -d src=/home/lain/.local/src +hash -d vv=/home/lain/vid +hash -d bf=/home/lain/.config/shell/bm-files +hash -d bd=/home/lain/.config/shell/bm-dirs +hash -d cfx=/home/lain/.config/x11/xresources +hash -d cfb=~/.local/src/dwmblocks/config.h +hash -d cfv=/home/lain/.config/nvim/init.vim +hash -d cfz=/home/lain/.config/zsh/.zshrc +hash -d cfa=/home/lain/.config/shell/aliasrc +hash -d cfp=/home/lain/.config/shell/profile +hash -d cfm=/home/lain/.config/mutt/muttrc +hash -d cfn=/home/lain/.config/newsboat/config +hash -d cfu=/home/lain/.config/newsboat/urls +hash -d cfmb=/home/lain/.config/ncmpcpp/bindings +hash -d cfmc=/home/lain/.config/ncmpcpp/config +hash -d cfl=/home/lain/.config/lf/lfrc +hash -d cfL=/home/lain/.config/lf/scope +hash -d cfX=/home/lain/.config/nsxiv/exec/key-handler diff --git a/.local/bin/arkenfox-auto-update b/.local/bin/arkenfox-auto-update @@ -0,0 +1,23 @@ +#!/bin/sh + +# A wrapper for the arkenfox-updater that runs it on all pre-existing Arkenfox +# user.js files on the machine. + +# On installation of LARBS, this file is copied to /usr/local/lib/ where it is +# run by a pacman hook set up. The user should not have to run this manually. + +# Search for all Firefox and Librewolf profiles using Arkenfox. +profiles="$(grep -sH "arkenfox user.js" \ + /home/*/.librewolf/*.default-release/user.js \ + /home/*/.mozilla/firefox/*.default-release/user.js)" + +IFS=' +' + +# Update each found profile. +for profile in $profiles; do + userjs=${profile%%/user.js*} + user=$(stat -c '%U' "$userjs") || continue + + su -l "$user" -c "arkenfox-updater -c -p $userjs -s" +done diff --git a/.local/bin/booksplit b/.local/bin/booksplit @@ -0,0 +1,43 @@ +#!/bin/sh + +# Requires ffmpeg + +[ ! -f "$2" ] && printf "The first file should be the audio, the second should be the timecodes.\\n" && exit + +echo "Enter the album/book title:"; read -r booktitle +echo "Enter the artist/author:"; read -r author +echo "Enter the publication year:"; read -r year + +inputaudio="$1" +ext="${1##*.}" + +# Get a safe file name from the book. +escbook="$(echo "$booktitle" | iconv -c -f UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g")" + +! mkdir -p "$escbook" && + echo "Do you have write access in this directory?" && + exit 1 + +# Get the total number of tracks from the number of lines. +total="$(wc -l < "$2")" + +cmd="ffmpeg -i \"$inputaudio\" -nostdin -y" + +while read -r x; +do + end="$(echo "$x" | cut -d' ' -f1)" + file="$escbook/$(printf "%.2d" "$track")-$esctitle.$ext" + if [ -n "$start" ]; then + cmd="$cmd -metadata artist=\"$author\" -metadata title=\"$title\" -metadata album=\"$booktitle\" -metadata year=\"$year\" -metadata track=\"$track\" -metadata total=\"$total\" -ss \"$start\" -to \"$end\" -vn -c:a copy \"$file\" " + fi + title="$(echo "$x" | cut -d' ' -f2-)" + esctitle="$(echo "$title" | iconv -c -f UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g")" + track="$((track+1))" + start="$end" +done < "$2" + +# Last track must be added out of the loop. +file="$escbook/$(printf "%.2d" "$track")-$esctitle.$ext" +cmd="$cmd -metadata artist=\"$author\" -metadata title=\"$title\" -metadata album=\"$booktitle\" -metadata year=\"$year\" -metadata track=\"$track\" -ss \"$start\" -vn -c copy \"$file\"" + +eval "$cmd" diff --git a/.local/bin/compiler b/.local/bin/compiler @@ -0,0 +1,52 @@ +#!/bin/sh + +# This script will compile or run another finishing operation on a document. I +# have this script run via vim. + +# Compiles .tex. groff (.mom, .ms), .rmd, .md, .org. Opens .sent files as sent +# presentations. Runs scripts based on extension or shebang. + +# Note that .tex files which you wish to compile with XeLaTeX should have the +# string "xelatex" somewhere in a comment/command in the first 5 lines. + +file="${1}" +ext="${file##*.}" +dir=${file%/*} +base="${file%.*}" + +cd "${dir}" || exit "1" + +case "${ext}" in + [0-9]) preconv "${file}" | refer -PS -e | groff -mandoc -T pdf > "${base}.pdf" ;; + mom|ms) preconv "${file}" | refer -PS -e | groff -T pdf -m"${ext}" > "${base}.pdf" ;; + c) cc "${file}" -o "${base}" && "./${base}" ;; + cpp) g++ "${file}" -o "${base}" && "./${base}" ;; + cs) mcs "${file}" && mono "${base}.exe" ;; + go) go run "${file}" ;; + h) sudo make install ;; + java) javac -d classes "${file}" && java -cp classes "${base}" ;; + m) octave "${file}" ;; + md) [ -x "$(command -v lowdown)" ] && \ + lowdown --parse-no-intraemph "${file}" -Tms | groff -mpdfmark -ms -kept -T pdf > "${base}.pdf" || \ + [ -x "$(command -v groffdown)" ] && \ + groffdown -i "${file}" | groff -T pdf > "${base}.pdf" || \ + pandoc -t ms --highlight-style="kate" -s -o "${base}.pdf" "${file}" ;; + org) emacs "${file}" --batch -u "${USER}" -f org-latex-export-to-pdf ;; + py) python "${file}" ;; + [rR]md) Rscript -e "rmarkdown::render('${file}', quiet=TRUE)" ;; + rs) cargo build ;; + sass) sassc -a "${file}" "${base}.css" ;; + scad) openscad -o "${base}.stl" "${file}" ;; + sent) setsid -f sent "${file}" 2> "/dev/null" ;; + tex) + textarget="$(getcomproot "${file}" || echo "${file}")" + command="pdflatex" + head -n5 "${textarget}" | grep -qi "xelatex" && command="xelatex" + ${command} --output-directory="${textarget%/*}" "${textarget%.*}" && + grep -qi addbibresource "${textarget}" && + biber --input-directory "${textarget%/*}" "${textarget%.*}" && + ${command} --output-directory="${textarget%/*}" "${textarget%.*}" && + ${command} --output-directory="${textarget%/*}" "${textarget%.*}" + ;; + *) sed -n '/^#!/s/^#!//p; q' "${file}" | xargs -r -I % "${file}" ;; +esac diff --git a/.local/bin/cron/README.md b/.local/bin/cron/README.md @@ -0,0 +1,11 @@ +# Important Note + +These cronjobs have components that require information about your current display to display notifications correctly. + +When you add them as cronjobs, I recommend you precede the command with commands as those below: + +``` +export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $USER)/bus; export DISPLAY=:0; . $HOME/.zprofile; then_command_goes_here +``` + +This ensures that notifications will display, xdotool commands will function and environmental variables will work as well. diff --git a/.local/bin/cron/checkup b/.local/bin/cron/checkup @@ -0,0 +1,17 @@ +#!/bin/sh + +# Syncs repositories and downloads updates, meant to be run as a cronjob. + +notify-send "๐Ÿ“ฆ Repository Sync" "Checking for package updates..." + +sudo pacman -Syyuw --noconfirm || notify-send "Error downloading updates. + +Check your internet connection, if pacman is already running, or run update manually to see errors." +pkill -RTMIN+8 "${STATUSBAR:-dwmblocks}" + +if pacman -Qu | grep -v "\[ignored\]" +then + notify-send "๐ŸŽ Repository Sync" "Updates available. Click statusbar icon (๐Ÿ“ฆ) for update." +else + notify-send "๐Ÿ“ฆ Repository Sync" "Sync complete. No new packages for update." +fi diff --git a/.local/bin/cron/crontog b/.local/bin/cron/crontog @@ -0,0 +1,6 @@ +#!/bin/sh + +# Toggles all cronjobs off/on. +# Stores disabled crontabs in ~/.config/cronsaved until restored. + +([ -f "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved ] && crontab - < "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && rm "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && notify-send "๐Ÿ•“ Cronjobs re-enabled.") || ( crontab -l > "${XDG_CONFIG_HOME:-$HOME/.config}"/cronsaved && crontab -r && notify-send "๐Ÿ•“ Cronjobs saved and disabled.") diff --git a/.local/bin/cron/newsup b/.local/bin/cron/newsup @@ -0,0 +1,15 @@ +#!/bin/sh + +# Set as a cron job to check for new RSS entries for newsboat. +# If newsboat is open, sends it an "R" key to refresh. + +/usr/bin/notify-send "๐Ÿ“ฐ Updating RSS feeds..." + +pgrep -f newsboat$ && /usr/bin/xdotool key --window "$(/usr/bin/xdotool search --name "^newsboat$")" R && exit + +echo ๐Ÿ”ƒ > /tmp/newsupdate +pkill -RTMIN+6 "${STATUSBAR:-dwmblocks}" +/usr/bin/newsboat -x reload +rm -f /tmp/newsupdate +pkill -RTMIN+6 "${STATUSBAR:-dwmblocks}" +/usr/bin/notify-send "๐Ÿ“ฐ RSS feed update complete." diff --git a/.local/bin/displayselect b/.local/bin/displayselect @@ -0,0 +1,82 @@ +#!/bin/sh + +# A UI for detecting and selecting all displays. Probes xrandr for connected +# displays and lets user select one to use. User may also select "manual +# selection" which opens arandr. + +twoscreen() { # If multi-monitor is selected and there are two screens. + + mirror=$(printf "no\\nyes" | dmenu -i -p "Mirror displays?") + # Mirror displays using native resolution of external display and a scaled + # version for the internal display + if [ "$mirror" = "yes" ]; then + external=$(echo "$screens" | dmenu -i -p "Optimize resolution for:") + internal=$(echo "$screens" | grep -v "$external") + + res_external=$(xrandr --query | sed -n "/^$external/,/\+/p" | \ + tail -n 1 | awk '{print $1}') + res_internal=$(xrandr --query | sed -n "/^$internal/,/\+/p" | \ + tail -n 1 | awk '{print $1}') + + res_ext_x=$(echo "$res_external" | sed 's/x.*//') + res_ext_y=$(echo "$res_external" | sed 's/.*x//') + res_int_x=$(echo "$res_internal" | sed 's/x.*//') + res_int_y=$(echo "$res_internal" | sed 's/.*x//') + + scale_x=$(echo "$res_ext_x / $res_int_x" | bc -l) + scale_y=$(echo "$res_ext_y / $res_int_y" | bc -l) + + xrandr --output "$external" --auto --scale 1.0x1.0 \ + --output "$internal" --auto --same-as "$external" \ + --scale "$scale_x"x"$scale_y" + else + + primary=$(echo "$screens" | dmenu -i -p "Select primary display:") + secondary=$(echo "$screens" | grep -v ^"$primary"$) + direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") + xrandr --output "$primary" --auto --scale 1.0x1.0 --output "$secondary" --"$direction"-of "$primary" --auto --scale 1.0x1.0 + fi + } + +morescreen() { # If multi-monitor is selected and there are more than two screens. + primary=$(echo "$screens" | dmenu -i -p "Select primary display:") + secondary=$(echo "$screens" | grep -v ^"$primary"$ | dmenu -i -p "Select secondary display:") + direction=$(printf "left\\nright" | dmenu -i -p "What side of $primary should $secondary be on?") + tertiary=$(echo "$screens" | grep -v ^"$primary"$ | grep -v ^"$secondary"$ | dmenu -i -p "Select third display:") + xrandr --output "$primary" --auto --output "$secondary" --"$direction"-of "$primary" --auto --output "$tertiary" --"$(printf "left\\nright" | grep -v "$direction")"-of "$primary" --auto + } + +multimon() { # Multi-monitor handler. + case "$(echo "$screens" | wc -l)" in + 2) twoscreen ;; + *) morescreen ;; + esac ;} + +onescreen() { # If only one output available or chosen. + xrandr --output "$1" --auto --scale 1.0x1.0 $(echo "$allposs" | grep -v "\b$1" | awk '{print "--output", $1, "--off"}' | paste -sd ' ' -) + } + +postrun() { # Stuff to run to clean up. + setbg # Fix background if screen size/arangement has changed. + { killall dunst ; setsid -f dunst ;} >/dev/null 2>&1 # Restart dunst to ensure proper location on screen + } + +# Get all possible displays +allposs=$(xrandr -q | grep "connected") + +# Get all connected screens. +screens=$(echo "$allposs" | awk '/ connected/ {print $1}') + +# If there's only one screen +[ "$(echo "$screens" | wc -l)" -lt 2 ] && + { onescreen "$screens"; postrun; notify-send "๐Ÿ’ป Only one screen detected." "Using it in its optimal settings..."; exit ;} + +# Get user choice including multi-monitor and manual selection: +chosen=$(printf "%s\\nmulti-monitor\\nmanual selection" "$screens" | dmenu -i -p "Select display arangement:") && +case "$chosen" in + "manual selection") arandr ; exit ;; + "multi-monitor") multimon ;; + *) onescreen "$chosen" ;; +esac + +postrun diff --git a/.local/bin/dmenuhandler b/.local/bin/dmenuhandler @@ -0,0 +1,21 @@ +#!/bin/sh + +# Feed this script a link and it will give dmenu +# some choice programs to use to open it. +feed="${1:-$(true | dmenu -p 'Paste URL or file path')}" + +case "$(printf "copy url\\nnsxiv\\nsetbg\\nPDF\\nbrowser\\nlynx\\nvim\\nmpv\\nmpv loop\\nmpv float\\nqueue download\\nqueue yt-dlp\\nqueue yt-dlp audio" | dmenu -i -p "Open it with?")" in + "copy url") echo "$feed" | xclip -selection clipboard ;; + mpv) setsid -f mpv -quiet "$feed" >/dev/null 2>&1 ;; + "mpv loop") setsid -f mpv -quiet --loop "$feed" >/dev/null 2>&1 ;; + "mpv float") setsid -f "$TERMINAL" -e mpv --geometry=+0-0 --autofit=30% --title="mpvfloat" "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp") qndl "$feed" >/dev/null 2>&1 ;; + "queue yt-dlp audio") qndl "$feed" 'yt-dlp -o "%(title)s.%(ext)s" -f bestaudio --embed-metadata --restrict-filenames' ;; + "queue download") qndl "$feed" 'curl -LO' >/dev/null 2>&1 ;; + PDF) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && zathura "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + nsxiv) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && nsxiv -a "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + vim) curl -sL "$feed" > "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" && setsid -f "$TERMINAL" -e "$EDITOR" "/tmp/$(echo "$feed" | sed "s|.*/||;s/%20/ /g")" >/dev/null 2>&1 ;; + setbg) curl -L "$feed" > $XDG_CACHE_HOME/pic ; xwallpaper --zoom $XDG_CACHE_HOME/pic >/dev/null 2>&1 ;; + browser) setsid -f "$BROWSER" "$feed" >/dev/null 2>&1 ;; + lynx) lynx "$feed" >/dev/null 2>&1 ;; +esac diff --git a/.local/bin/dmenumountcifs b/.local/bin/dmenumountcifs @@ -0,0 +1,19 @@ +#!/bin/sh +# Gives a dmenu prompt to mount unmounted local NAS shares for read/write. +# Requirements - "%wheel ALL=(ALL) NOPASSWD: ALL" +# +# Browse for mDNS/DNS-SD services using the Avahi daemon... +srvname=$(avahi-browse _smb._tcp -t | awk '{print $4}' | dmenu -i -p "Which NAS?") || exit 1 +notify-send "Searching for network shares..." "Please wait..." +# Choose share disk... +share=$(smbclient -L "$srvname" -N | grep Disk | awk '{print $1}' | dmenu -i -p "Mount which share?") || exit 1 +# Format URL... +share2mnt=//"$srvname".local/"$share" + +sharemount() { + mounted=$(mount -v | grep "$share2mnt") || ([ ! -d /mnt/"$share" ] && sudo mkdir /mnt/"$share") + [ -z "$mounted" ] && sudo mount -t cifs "$share2mnt" -o user=nobody,password="",noperm /mnt/"$share" && notify-send "Netshare $share mounted" && exit 0 + notify-send "Netshare $share already mounted"; exit 1 +} + +sharemount diff --git a/.local/bin/dmenupass b/.local/bin/dmenupass @@ -0,0 +1,6 @@ +#!/bin/sh + +# This script is the SUDO_ASKPASS variable, meaning that it will be used as a +# password prompt if needed. + +dmenu -fn Monospace-18 -P -p "$1" <&- && echo diff --git a/.local/bin/dmenurecord b/.local/bin/dmenurecord @@ -0,0 +1,123 @@ +#!/bin/sh + +# Usage: +# `$0`: Ask for recording type via dmenu +# `$0 screencast`: Record both audio and screen +# `$0 video`: Record only screen +# `$0 audio`: Record only audio +# `$0 kill`: Kill existing recording +# +# If there is already a running instance, user will be prompted to end it. + +getdim() { xrandr | grep -oP '(?<=current ).*(?=,)' | tr -d ' ' ;} + +updateicon() { \ + echo "$1" > /tmp/recordingicon + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + } + +killrecording() { + recpid="$(cat /tmp/recordingpid)" + kill -15 "$recpid" + rm -f /tmp/recordingpid + updateicon "" + pkill -RTMIN+9 "${STATUSBAR:-dwmblocks}" + } + +screencast() { \ + ffmpeg -y \ + -f x11grab \ + -framerate 30 \ + -s "$(getdim)" \ + -i "$DISPLAY" \ + -r 24 \ + -use_wallclock_as_timestamps 1 \ + -f alsa -thread_queue_size 1024 -i default \ + -c:v h264 \ + -crf 0 -preset ultrafast -c:a aac \ + "$HOME/screencast-$(date '+%y%m%d-%H%M-%S').mp4" & + echo $! > /tmp/recordingpid + updateicon "โบ๏ธ๐ŸŽ™๏ธ" + } + +video() { ffmpeg \ + -f x11grab \ + -framerate 30 \ + -s "$(getdim)" \ + -i "$DISPLAY" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/video-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "โบ๏ธ" + } + +webcamhidef() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 1920x1080 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "๐ŸŽฅ" + } + +webcam() { ffmpeg \ + -f v4l2 \ + -i /dev/video0 \ + -video_size 640x480 \ + "$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "๐ŸŽฅ" + } + + +audio() { \ + ffmpeg \ + -f alsa -i default \ + -c:a flac \ + "$HOME/audio-$(date '+%y%m%d-%H%M-%S').flac" & + echo $! > /tmp/recordingpid + updateicon "๐ŸŽ™๏ธ" + } + +askrecording() { \ + choice=$(printf "screencast\\nvideo\\nvideo selected\\naudio\\nwebcam\\nwebcam (hi-def)" | dmenu -i -p "Select recording style:") + case "$choice" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + webcam) webcam;; + "webcam (hi-def)") webcamhidef;; + esac + } + +asktoend() { \ + response=$(printf "No\\nYes" | dmenu -i -p "Recording still active. End recording?") && + [ "$response" = "Yes" ] && killrecording + } + +videoselected() +{ + slop -f "%x %y %w %h" > /tmp/slop + read -r X Y W H < /tmp/slop + rm /tmp/slop + + ffmpeg \ + -f x11grab \ + -framerate 30 \ + -video_size "$W"x"$H" \ + -i :0.0+"$X,$Y" \ + -c:v libx264 -qp 0 -r 30 \ + "$HOME/box-$(date '+%y%m%d-%H%M-%S').mkv" & + echo $! > /tmp/recordingpid + updateicon "โบ๏ธ" +} + +case "$1" in + screencast) screencast;; + audio) audio;; + video) video;; + *selected) videoselected;; + kill) killrecording;; + *) ([ -f /tmp/recordingpid ] && asktoend && exit) || askrecording;; +esac diff --git a/.local/bin/dmenuunicode b/.local/bin/dmenuunicode @@ -0,0 +1,18 @@ +#!/bin/sh + +# The famous "get a menu of emojis to copy" script. + +# Get user selection via dmenu from emoji file. +chosen=$(cut -d ';' -f1 ~/.local/share/larbs/chars/* | dmenu -i -l 30 | sed "s/ .*//") + +# Exit if none chosen. +[ -z "$chosen" ] && exit + +# If you run this command with an argument, it will automatically insert the +# character. Otherwise, show a message that the emoji has been copied. +if [ -n "$1" ]; then + xdotool type "$chosen" +else + printf "%s" "$chosen" | xclip -selection clipboard + notify-send "'$chosen' copied to clipboard." & +fi diff --git a/.local/bin/getbib b/.local/bin/getbib @@ -0,0 +1,14 @@ +#!/bin/sh +[ -z "$1" ] && echo "Give either a pdf file or a DOI as an argument." && exit + +if [ -f "$1" ]; then + # Try to get DOI from pdfinfo or pdftotext output. + doi=$(pdfinfo "$1" | grep -io "doi:.*") || + doi=$(pdftotext "$1" 2>/dev/null - | sed -n '/[dD][oO][iI]:/{s/.*[dD][oO][iI]:\s*\(\S\+[[:alnum:]]\).*/\1/p;q}') || + exit 1 +else + doi="$1" +fi + +# Check crossref.org for the bib citation. +curl -s "https://api.crossref.org/works/$doi/transform/application/x-bibtex" -w "\\n" diff --git a/.local/bin/getcomproot b/.local/bin/getcomproot @@ -0,0 +1,9 @@ +#!/bin/sh + +# A helper script for LaTeX/groff files used by `compiler` and `opout`. +# The user can add the root file of a larger project as a comment as below: +# % root = mainfile.tex +# And the compiler script will run on that instead of the opened file. + +texroot="$(sed -n 's/^\s*%.*root\s*=\s*\(\S\+\).*/\1/p' "${1}")" +[ -f "${texroot}" ] && readlink -f "${texroot}" || exit "1" diff --git a/.local/bin/getkeys b/.local/bin/getkeys @@ -0,0 +1,5 @@ +#!/bin/sh + +cat "${XDG_DATA_HOME:-$HOME/.local/share}"/larbs/getkeys/"$1" 2>/dev/null && exit +echo "Run command with one of the following arguments for info about that program:" +ls "${XDG_DATA_HOME:-$HOME/.local/share}"/larbs/getkeys diff --git a/.local/bin/ifinstalled b/.local/bin/ifinstalled @@ -0,0 +1,12 @@ +#!/bin/sh + +# Some optional functions in LARBS require programs not installed by default. I +# use this little script to check to see if a command exists and if it doesn't +# it informs the user that they need that command to continue. This is used in +# various other scripts for clarity's sake. + +for x in "$@"; do + if ! which "$x" >/dev/null 2>&1 && ! pacman -Qq "$x" >/dev/null 2>&1; then + notify-send "๐Ÿ“ฆ $x" "must be installed for this function." && exit 1 ; + fi +done diff --git a/.local/bin/lfub b/.local/bin/lfub @@ -0,0 +1,24 @@ +#!/bin/sh + +# This is a wrapper script for lf that allows it to create image previews with +# ueberzug. This works in concert with the lf configuration file and the +# lf-cleaner script. + +set -e + +cleanup() { + exec 3>&- + rm "$FIFO_UEBERZUG" +} + +if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then + lf "$@" +else + [ ! -d "$HOME/.cache/lf" ] && mkdir -p "$HOME/.cache/lf" + export FIFO_UEBERZUG="$HOME/.cache/lf/ueberzug-$$" + mkfifo "$FIFO_UEBERZUG" + ueberzug layer -s <"$FIFO_UEBERZUG" -p json & + exec 3>"$FIFO_UEBERZUG" + trap cleanup HUP INT QUIT TERM PWR EXIT + lf "$@" 3>&- +fi diff --git a/.local/bin/linkhandler b/.local/bin/linkhandler @@ -0,0 +1,26 @@ +#!/bin/sh + +# Feed script a url or file location. +# If an image, it will view in nsxiv, +# if a video or gif, it will view in mpv +# if a music file or pdf, it will download, +# otherwise it opens link in browser. + +if [ -z "$1" ]; then + url="$(xclip -o)" +else + url="$1" +fi + +case "$url" in + *mkv|*webm|*mp4|*youtube.com/watch*|*youtube.com/playlist*|*youtube.com/shorts*|*youtu.be*|*hooktube.com*|*bitchute.com*|*videos.lukesmith.xyz*|*odysee.com*) + setsid -f mpv -quiet "$url" >/dev/null 2>&1 ;; + *png|*jpg|*jpe|*jpeg|*gif|*webp) + curl -sL "$url" > "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && nsxiv -a "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 & ;; + *pdf|*cbz|*cbr) + curl -sL "$url" > "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" && zathura "/tmp/$(echo "$url" | sed "s/.*\///;s/%20/ /g")" >/dev/null 2>&1 & ;; + *mp3|*flac|*opus|*mp3?source*) + qndl "$url" 'curl -LO' >/dev/null 2>&1 ;; + *) + [ -f "$url" ] && setsid -f "$TERMINAL" -e "$EDITOR" "$url" >/dev/null 2>&1 || setsid -f "$BROWSER" "$url" >/dev/null 2>&1 +esac diff --git a/.local/bin/maimpick b/.local/bin/maimpick @@ -0,0 +1,20 @@ +#!/bin/sh + +# This is bound to Shift+PrintScreen by default, requires maim. It lets you +# choose the kind of screenshot to take, including copying the image or even +# highlighting an area to copy. scrotcucks on suicidewatch right now. + +# variables +output="$(date '+%y%m%d-%H%M-%S').png" +xclip_cmd="xclip -sel clip -t image/png" +ocr_cmd="xclip -sel clip" + +case "$(printf "a selected area\\ncurrent window\\nfull screen\\na selected area (copy)\\ncurrent window (copy)\\nfull screen (copy)\\ncopy selected image to text" | dmenu -l 7 -i -p "Screenshot which area?")" in + "a selected area") maim -u -s pic-selected-"${output}" ;; + "current window") maim -B -q -d 0.2 -i "$(xdotool getactivewindow)" pic-window-"${output}" ;; + "full screen") maim -q -d 0.2 pic-full-"${output}" ;; + "a selected area (copy)") maim -u -s | ${xclip_cmd} ;; + "current window (copy)") maim -q -d 0.2 -i "$(xdotool getactivewindow)" | ${xclip_cmd} ;; + "full screen (copy)") maim -q -d 0.2 | ${xclip_cmd} ;; + "copy selected image to text") tmpfile=$(mktemp /tmp/ocr-XXXXXX.png) && maim -u -s > "$tmpfile" && tesseract "$tmpfile" - -l eng | ${ocr_cmd} && rm "$tmpfile" ;; +esac diff --git a/.local/bin/mounter b/.local/bin/mounter @@ -0,0 +1,119 @@ +#!/bin/bash + +# Mounts Android Phones and USB drives (encrypted or not). This script will +# replace the older `dmenumount` which had extra steps and couldn't handle +# encrypted drives. +# TODO: Try decrypt for drives in crtypttab +# TODO: Add some support for connecting iPhones (although they are annoying). + +IFS=' +' +# Function for escaping cell-phone names. +escape(){ echo "$@" | iconv -cf UTF-8 -t ASCII//TRANSLIT | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed "s/-\+/-/g;s/\(^-\|-\$\)//g" ;} + +# Check for phones. +phones="$(simple-mtpfs -l 2>/dev/null | sed "s/^/๐Ÿ“ฑ/")" +mountedphones="$(grep "simple-mtpfs" /etc/mtab)" +# If there are already mounted phones, remove them from the list of mountables. +[ -n "$mountedphones" ] && phones="$(for phone in $phones; do + for mounted in $mountedphones; do + escphone="$(escape "$phone")" + [[ "$mounted" =~ "$escphone" ]] && break 1 + done && continue 1 + echo "$phone" +done)" + +# Check for drives. +lsblkoutput="$(lsblk -rpo "uuid,name,type,size,label,mountpoint,fstype")" +# Get all LUKS drives +allluks="$(echo "$lsblkoutput" | grep crypto_LUKS)" +# Get a list of the LUKS drive UUIDs already decrypted. +decrypted="$(find /dev/disk/by-id/dm-uuid-CRYPT-LUKS2-* | sed "s|.*LUKS2-||;s|-.*||")" +# Functioning for formatting drives correctly for dmenu: +filter() { sed "s/ /:/g" | awk -F':' '$7==""{printf "%s%s (%s) %s\n",$1,$3,$5,$6}' ; } + +# Get only LUKS drives that are not decrypted. +unopenedluks="$(for drive in $allluks; do + uuid="${drive%% *}" + uuid="${uuid//-}" # This is a bashism. + [ -n "$decrypted" ] && for open in $decrypted; do + [ "$uuid" = "$open" ] && break 1 + done && continue 1 + echo "๐Ÿ”’ $drive" +done | filter)" + +# Get all normal, non-encrypted or decrypted partitions that are not mounted. +normalparts="$(echo "$lsblkoutput"| grep -v crypto_LUKS | grep 'part\|rom\|crypt' | sed "s/^/๐Ÿ’พ /" | filter )" + +# Add all to one variable. If no mountable drives found, exit. +alldrives="$(echo "$phones +$unopenedluks +$normalparts" | sed "/^$/d;s/ *$//")" + +# Quit the script if a sequential command fails. +set -e + +test -n "$alldrives" + +# Feed all found drives to dmenu and get user choice. +chosen="$(echo "$alldrives" | dmenu -p "Mount which drive?" -i)" + +# Function for prompting user for a mountpoint. +getmount(){ + mp="$(find /mnt /media /mount /home -maxdepth 1 -type d 2>/dev/null | dmenu -i -p "Mount this drive where?")" + test -n "$mp" + if [ ! -d "$mp" ]; then + mkdiryn=$(printf "No\\nYes" | dmenu -i -p "$mp does not exist. Create it?") + [ "$mkdiryn" = "Yes" ] && (mkdir -p "$mp" || sudo -A mkdir -p "$mp") + fi +} + +attemptmount(){ + # Attempt to mount without a mountpoint, to see if drive is in fstab. + sudo -A mount "$chosen" || return 1 + notify-send "๐Ÿ’พDrive Mounted." "$chosen mounted." + exit +} + +case "$chosen" in + ๐Ÿ’พ*) + chosen="${chosen%% *}" + chosen="${chosen:1}" # This is a bashism. + parttype="$(echo "$lsblkoutput" | grep "$chosen")" + attemptmount || getmount + case "${parttype##* }" in + vfat) sudo -A mount -t vfat "$chosen" "$mp" -o rw,umask=0000 ;; + btrfs) sudo -A mount "$chosen" "$mp" ;; + *) sudo -A mount "$chosen" "$mp" -o uid="$(id -u)",gid="$(id -g)" ;; + esac + notify-send "๐Ÿ’พDrive Mounted." "$chosen mounted to $mp." + ;; + + ๐Ÿ”’*) + chosen="${chosen%% *}" + chosen="${chosen:1}" # This is a bashism. + # Number the drive. + while true; do + [ -f "/dev/mapper/usb$num" ] || break + num="$(printf "%02d" "$((num +1))")" + done + + # Decrypt in a terminal window + ${TERMINAL:-st} -n floatterm -g 60x1 -e sudo cryptsetup open "$chosen" "usb$num" + # Check if now decrypted. + test -b "/dev/mapper/usb$num" + + attemptmount || getmount + sudo -A mount "/dev/mapper/usb$num" "$mp" -o uid="$(id -u)",gid="$(id -g)" + notify-send "๐Ÿ”“Decrypted drive Mounted." "$chosen decrypted and mounted to $mp." + ;; + + ๐Ÿ“ฑ*) + notify-send "โ—Note" "Remember to allow file access on your phone now." + getmount + number="${chosen%%:*}" + number="${chosen:1}" # This is a bashism. + sudo -A simple-mtpfs -o allow_other -o fsname="simple-mtpfs-$(escape "$chosen")" --device "$number" "$mp" + notify-send "๐Ÿค– Android Mounted." "Android device mounted to $mp." + ;; +esac diff --git a/.local/bin/noisereduce b/.local/bin/noisereduce @@ -0,0 +1,81 @@ +#!/usr/bin/sh + +usage () +{ + printf "Usage : noisereduce <input video file> <output video file>\n" + exit +} + +# Tests for requirements +ifinstalled ffmpeg || { echo >&2 "We require 'ffmpeg' but it's not installed."; exit 1; } +ifinstalled sox || { echo >&2 "We require 'ffmpeg' but it's not installed."; exit 1; } + +if [ "$#" -ne 2 ] +then + usage +fi + +if [ ! -e "$1" ] +then + printf "File not found: %s\n" "$1" + exit +fi + +if [ -e "$2" ] +then + printf "File %s already exists, overwrite? [y/N]\n: " "$2" + read -r yn + case $yn in + [Yy]* ) ;; + * ) exit;; + esac +fi + +inBasename=$(basename "$1") +inExt="${inBasename##*.}" + +isVideoStr=$(ffprobe -v warning -show_streams "$1" | grep codec_type=video) +if [ -n "$isVideoStr" ] +then + isVideo=1 + printf "Detected %s as a video file\n" "$inBasename" +else + isVideo=0 + printf "Detected %s as an audio file\n" "$inBasename" +fi + +printf "Sample noise start time [00:00:00]: " +read -r sampleStart +if [ -z "$sampleStart" ] ; then sampleStart="00:00:00"; fi +printf "Sample noise end time [00:00:00.900]: " +read -r sampleEnd +if [ -z "$sampleEnd" ] ; then sampleEnd="00:00:00.900"; fi +printf "Noise reduction amount [0.21]: " +read -r sensitivity +if [ -z "$sensitivity" ] ; then sensitivity="0.21"; fi + + +tmpVidFile="/tmp/noiseclean_tmpvid.$inExt" +tmpAudFile="/tmp/noiseclean_tmpaud.wav" +noiseAudFile="/tmp/noiseclean_noiseaud.wav" +noiseProfFile="/tmp/noiseclean_noise.prof" +tmpAudCleanFile="/tmp/noiseclean_tmpaud-clean.wav" + +printf "Cleaning noise on %s...\n" "$1" + +if [ $isVideo -eq "1" ]; then + ffmpeg -v warning -y -i "$1" -qscale:v 0 -vcodec copy -an "$tmpVidFile" + ffmpeg -v warning -y -i "$1" -qscale:a 0 "$tmpAudFile" +else + cp "$1" "$tmpAudFile" +fi +ffmpeg -v warning -y -i "$1" -vn -ss "$sampleStart" -t "$sampleEnd" "$noiseAudFile" +sox "$noiseAudFile" -n noiseprof "$noiseProfFile" +sox "$tmpAudFile" "$tmpAudCleanFile" noisered "$noiseProfFile" "$sensitivity" +if [ $isVideo -eq "1" ]; then + ffmpeg -v warning -y -i "$tmpAudCleanFile" -i "$tmpVidFile" -vcodec copy -qscale:v 0 -qscale:a 0 "$2" +else + cp "$tmpAudCleanFile" "$2" +fi + +printf "Done" diff --git a/.local/bin/opout b/.local/bin/opout @@ -0,0 +1,13 @@ +#!/bin/sh + +# opout: "open output": A general handler for opening a file's intended output, +# usually the pdf of a compiled document. I find this useful especially +# running from vim. + +basename="${1%.*}" + +case "${*}" in + *.tex|*.sil|*.m[dse]|*.[rR]md|*.mom|*.[0-9]) target="$(getcomproot "$1" || echo "$1")" ; setsid -f xdg-open "${target%.*}".pdf >/dev/null 2>&1 ;; + *.htm) setsid -f "$BROWSER" "$basename".htm >/dev/null 2>&1 ;; + *.sent) setsid -f sent "$1" >/dev/null 2>&1 ;; +esac diff --git a/.local/bin/otp b/.local/bin/otp @@ -0,0 +1,50 @@ +#!/bin/sh + +# Get a one-time password, or add a OTP secret to your pass-otp store. + +# The assumption of this script is that all otp passwords are stored with the +# suffix `-otp`. This script automatically appends newly added otps as such. + +# For OTP passwords to be generated properly, it is important for the local +# computer to have its time properly synced. This can be done with the command +# below which requires the package `ntp`. + +ifinstalled pass pass-otp || exit 1 + +dir="${PASSWORD_STORE_DIR}" + +choice="$({ echo "๐Ÿ†•add" ; echo "๐Ÿ•™sync-time" ; ls "$dir"/*-otp.gpg ;} | sed "s/.*\///;s/-otp.gpg//" | dmenu -p "Pick a 2FA:")" + +case $choice in + ๐Ÿ†•add ) + ifinstalled maim zbar || exit 1 + + temp=$(mktemp -p "$XDG_RUNTIME_DIR" --suffix=.png) + otp="otp-test-script" + trap 'rm -f $temp; pass rm -f $otp' HUP INT QUIT TERM PWR EXIT + + notify-send "Scan the image." "Scan the OTP QR code." + + maim -s "$temp" || exit 1 + info="$(zbarimg -q "$temp")" + info="${info#QR-Code:}" + + if echo "$info" | pass otp insert "$otp"; then + while true ; do + export name="$(echo | dmenu -p "Give this One Time Password a one-word name:")" + echo "$name" | grep -q -- "^[A-z0-9-]\+$" && break + done + pass mv "$otp" "$name-otp" + notify-send "Successfully added." "$name-otp has been created." + else + notify-send "No OTP data found." "Try to scan the image again more precisely." + fi + ;; + ๐Ÿ•™sync-time ) + ifinstalled ntp || exit 1 + notify-send -u low "๐Ÿ•™ Synchronizing Time..." "Synching time with remote NTP servers..." + updatedata="$(sudo ntpdate pool.ntp.org)" && + notify-send -u low "๐Ÿ•™ Synchronizing Time..." "Done. Time changed by ${updatedata#*offset }" + ;; + *) pass otp -c ${choice}-otp ;; +esac diff --git a/.local/bin/pauseallmpv b/.local/bin/pauseallmpv @@ -0,0 +1,10 @@ +#!/bin/sh + +# You might notice all mpv commands are aliased to have this input-ipc-server +# thing. That's just for this particular command, which allows us to pause +# every single one of them with one command! This is bound to super + shift + p +# (with other things) by default and is used in some other places. + +for i in $(ls /tmp/mpvSockets/*); do + echo '{ "command": ["set_property", "pause", true] }' | socat - "$i"; +done diff --git a/.local/bin/peertubetorrent b/.local/bin/peertubetorrent @@ -0,0 +1,9 @@ +#!/bin/sh +# torrent peertube videos, requires the transadd script +# first argument is the video link, second is the quality (360, 480 or 1080) +# 13/07/20 - Arthur Bais + +instance=$(echo "$1" | sed "s|/w.\+||") +vidid=$(echo "$1" | sed "s|.\+/||") +link=$(curl -s "$instance/api/v1/videos/$vidid" | grep -o "$instance/download/torrents/.\{37\}$2.torrent") +transadd "$link" diff --git a/.local/bin/podentr b/.local/bin/podentr @@ -0,0 +1,7 @@ +#!/bin/sh + +# entr command to run `queueandnotify` when newsboat queue is changed + +[ "$(pgrep -x "$(basename "$0")" | wc -l)" -gt 2 ] && exit + +echo "${XDG_DATA_HOME:-$HOME/.local/share}"/newsboat/queue | entr -p queueandnotify 2>/dev/null diff --git a/.local/bin/qndl b/.local/bin/qndl @@ -0,0 +1,12 @@ +#!/bin/sh + +# $1 is a url; $2 is a command +[ -z "$1" ] && exit +base="$(basename "$1")" +notify-send "โณ Queuing $base..." +cmd="$2" +[ -z "$cmd" ] && cmd="yt-dlp --embed-metadata -ic" +idnum="$(tsp $cmd "$1")" +realname="$(echo "$base" | sed "s/?\(source\|dest\).*//;s/%20/ /g")" +tsp -D "$idnum" mv "$base" "$realname" +tsp -D "$idnum" notify-send "๐Ÿ‘ $realname done." diff --git a/.local/bin/queueandnotify b/.local/bin/queueandnotify @@ -0,0 +1,14 @@ +#!/bin/sh + +# Podboat sucks. This script replaces it. +# It reads the newsboat queue, queuing downloads with taskspooler. +# It also removes the junk from extensions. +queuefile="${XDG_DATA_HOME:-$HOME/.local/share}/newsboat/queue" + +while read -r line; do + [ -z "$line" ] && continue + url="${line%%[ ]*}" + qndl "$url" "curl -LO" +done < "$queuefile" + +echo > "$queuefile" diff --git a/.local/bin/remapd b/.local/bin/remapd @@ -0,0 +1,8 @@ +#!/bin/bash + +# Rerun the remaps script whenever a new input device is added. + +while :; do + remaps + grep -qP -m1 '[^un]bind.+\/[^:]+\(usb\)' <(udevadm monitor -u -t seat -s input -s usb) +done diff --git a/.local/bin/remaps b/.local/bin/remaps @@ -0,0 +1,11 @@ +#!/bin/sh + +# This script is called on startup to remap keys. +# Decrease key repeat delay to 300ms and increase key repeat rate to 50 per second. +xset r rate 300 50 +# Map the caps lock key to super, and map the menu key to right super. +setxkbmap -option caps:super,altwin:menu_win +# When caps lock is pressed only once, treat it as escape. +killall xcape 2>/dev/null ; xcape -e 'Super_L=Escape' +# Turn off caps lock if on since there is no longer a key for it. +xset -q | grep -q "Caps Lock:\s*on" && xdotool key Caps_Lock diff --git a/.local/bin/rotdir b/.local/bin/rotdir @@ -0,0 +1,12 @@ +#!/bin/sh + +# When I open an image from the file manager in nsxiv (the image viewer), I want +# to be able to press the next/previous keys to key through the rest of the +# images in the same directory. This script "rotates" the content of a +# directory based on the first chosen file, so that if I open the 15th image, +# if I press next, it will go to the 16th etc. Autistic, I know, but this is +# one of the reasons that nsxiv is great for being able to read standard input. + +[ -z "$1" ] && echo "usage: rotdir regex 2>&1" && exit 1 +base="$(basename "$1")" +ls "$PWD" | awk -v BASE="$base" 'BEGIN { lines = ""; m = 0; } { if ($0 == BASE) { m = 1; } } { if (!m) { if (lines) { lines = lines"\n"; } lines = lines""$0; } else { print $0; } } END { print lines; }' diff --git a/.local/bin/rssadd b/.local/bin/rssadd @@ -0,0 +1,18 @@ +#!/bin/sh + +if echo "$1" | grep -q "https*://\S\+\.[A-Za-z]\+\S*" ; then + url="$1" +else + url="$(grep -Eom1 '<[^>]+(rel="self"|application/[a-z]+\+xml)[^>]+>' "$1" | + grep -o "https?://[^\" ]")" + + echo "$url" | grep -q "https*://\S\+\.[A-Za-z]\+\S*" || + notify-send "That doesn't look like a full URL." && exit 1 +fi + +RSSFILE="${XDG_CONFIG_HOME:-$HOME/.config}/newsboat/urls" +if awk '{print $1}' "$RSSFILE" | grep "^$url$" >/dev/null; then + notify-send "You already have this RSS feed." +else + echo "$url" >> "$RSSFILE" && notify-send "RSS feed added." +fi diff --git a/.local/bin/sd b/.local/bin/sd @@ -0,0 +1,22 @@ +#!/bin/sh + +# Open a terminal window in the same directory as the currently active window. + +windowPID=$(xprop -id "$(xprop -root | sed -n "/_NET_ACTIVE_WINDOW/ s/^.*# // p")" | sed -n "/PID/ s/^.*= // p") +PIDlist=$(pstree -lpATna "$windowPID" | sed -En 's/.*,([0-9]+).*/\1/p' | tac) +for PID in $PIDlist; do + cmdline=$(ps -o args= -p "$PID") + process_group_leader=$(ps -o comm= -p "$(ps -o pgid= -p "$PID" | tr -d ' ')") + cwd=$(readlink /proc/"$PID"/cwd) + # zsh and lf won't be ignored even if it shows ~ or / + case "$cmdline" in + 'lf -server') continue ;; + "${SHELL##*/}"|'lf'|'lf '*) break ;; + esac + # git (and its sub-processes) will show the root of a repository instead of the actual cwd, so they're ignored + [ "$process_group_leader" = 'git' ] || [ ! -d "$cwd" ] && continue + # This is to ignore programs that show ~ or / instead of the actual working directory + [ "$cwd" != "$HOME" ] && [ "$cwd" != '/' ] && break +done +[ "$PWD" != "$cwd" ] && [ -d "$cwd" ] && { cd "$cwd" || exit 1; } +"$TERMINAL" diff --git a/.local/bin/setbg b/.local/bin/setbg @@ -0,0 +1,41 @@ +#!/bin/sh + +# This script does the following: +# Run by itself, set the wallpaper (at X start). +# If given a file, set that as the new wallpaper. +# If given a directory, choose random file in it. +# If wal is installed, also generates a colorscheme. + +# Location of link to wallpaper link. +bgloc="${XDG_DATA_HOME:-$HOME/.local/share}/bg" + +# Configuration files of applications that have their themes changed by pywal. +dunstconf="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc" +zathuraconf="${XDG_CONFIG_HOME:-$HOME/.config}/zathura/zathurarc" + +# Give -s as parameter to make notifications silent. +while getopts "s" o; do case "${o}" in + s) silent='1' ;; +esac done + +shift $((OPTIND - 1)) + +trueloc="$(readlink -f "$1")" && +case "$(file --mime-type -b "$trueloc")" in + image/* ) ln -sf "$trueloc" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Changing wallpaper..." ;; + inode/directory ) ln -sf "$(find "$trueloc" -iregex '.*.\(jpg\|jpeg\|png\|gif\)' -type f | shuf -n 1)" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Random Wallpaper chosen." ;; + *) [ -z "$silent" ] && notify-send "๐Ÿ–ผ๏ธ Error" "Not a valid image or directory." ; exit 1;; +esac + +# If pywal is installed, use it. +if command -v wal >/dev/null 2>&1 ; then + wal -n -i "$(readlink -f $bgloc)" -o "${XDG_CONFIG_HOME:-$HOME/.config}/wal/postrun" >/dev/null 2>&1 +# If pywal is removed, return config files to normal. +else + [ -f "$dunstconf.bak" ] && unlink "$dunstconf" && mv "$dunstconf.bak" "$dunstconf" + [ -f "$zathuraconf.bak" ] && unlink "$zathuraconf" && mv "$zathuraconf.bak" "$zathuraconf" +fi + +xwallpaper --zoom "$bgloc" +# If running, dwm hit the key to refresh the color scheme. +pidof dwm >/dev/null && xdotool key super+F5 diff --git a/.local/bin/shortcuts b/.local/bin/shortcuts @@ -0,0 +1,44 @@ +#!/bin/sh + +bmdirs="${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-dirs" +bmfiles="${XDG_CONFIG_HOME:-$HOME/.config}/shell/bm-files" + +# Output locations. Unactivated progs should go to /dev/null. +shell_shortcuts="${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc" +shell_env_shortcuts="${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutenvrc" +zsh_named_dirs="${XDG_CONFIG_HOME:-$HOME/.config}/shell/zshnameddirrc" +lf_shortcuts="${XDG_CONFIG_HOME:-$HOME/.config}/lf/shortcutrc" +vim_shortcuts="${XDG_CONFIG_HOME:-$HOME/.config}/nvim/shortcuts.vim" +qute_shortcuts="/dev/null" +fish_shortcuts="/dev/null" +vifm_shortcuts="/dev/null" + +# Remove, prepare files +rm -f "$lf_shortcuts" "$qute_shortcuts" "$zsh_named_dirs" "$vim_shortcuts" 2>/dev/null +printf "# vim: filetype=sh\\n" > "$fish_shortcuts" +printf "# vim: filetype=sh\\nalias " > "$shell_shortcuts" +printf "# vim: filetype=sh\\n" > "$shell_env_shortcuts" +printf "\" vim: filetype=vim\\n" > "$vifm_shortcuts" + +# Format the `directories` file in the correct syntax and sent it to all three configs. +eval "echo \"$(cat "$bmdirs")\"" | \ +awk "!/^\s*#/ && !/^\s*\$/ {gsub(\"\\\s*#.*$\",\"\"); + printf(\"%s=\42cd %s && ls -A\42 \\\\\n\",\$1,\$2) >> \"$shell_shortcuts\" ; + printf(\"[ -n \42%s\42 ] && export %s=\42%s\42 \n\",\$1,\$1,\$2) >> \"$shell_env_shortcuts\" ; + printf(\"hash -d %s=%s \n\",\$1,\$2) >> \"$zsh_named_dirs\" ; + printf(\"abbr %s \42cd %s; and ls -A\42\n\",\$1,\$2) >> \"$fish_shortcuts\" ; + printf(\"map g%s :cd %s<CR>\nmap t%s <tab>:cd %s<CR><tab>\nmap M%s <tab>:cd %s<CR><tab>:mo<CR>\nmap Y%s <tab>:cd %s<CR><tab>:co<CR> \n\",\$1,\$2, \$1, \$2, \$1, \$2, \$1, \$2) >> \"$vifm_shortcuts\" ; + printf(\"config.bind(';%s', \42set downloads.location.directory %s ;; hint links download\42) \n\",\$1,\$2) >> \"$qute_shortcuts\" ; + printf(\"map C%s cd \42%s\42 \n\",\$1,\$2) >> \"$lf_shortcuts\" ; + printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$vim_shortcuts\" }" + +# Format the `files` file in the correct syntax and sent it to both configs. +eval "echo \"$(cat "$bmfiles")\"" | \ +awk "!/^\s*#/ && !/^\s*\$/ {gsub(\"\\\s*#.*$\",\"\"); + printf(\"%s=\42\$EDITOR %s\42 \\\\\n\",\$1,\$2) >> \"$shell_shortcuts\" ; + printf(\"[ -n \42%s\42 ] && export %s=\42%s\42 \n\",\$1,\$1,\$2) >> \"$shell_env_shortcuts\" ; + printf(\"hash -d %s=%s \n\",\$1,\$2) >> \"$zsh_named_dirs\" ; + printf(\"abbr %s \42\$EDITOR %s\42 \n\",\$1,\$2) >> \"$fish_shortcuts\" ; + printf(\"map %s :e %s<CR> \n\",\$1,\$2) >> \"$vifm_shortcuts\" ; + printf(\"map E%s \$\$EDITOR \42%s\42 \n\",\$1,\$2) >> \"$lf_shortcuts\" ; + printf(\"cmap ;%s %s\n\",\$1,\$2) >> \"$vim_shortcuts\" }" diff --git a/.local/bin/slider b/.local/bin/slider @@ -0,0 +1,126 @@ +#!/bin/sh + +# Give a file with images and timecodes and creates a video slideshow of them. +# +# Timecodes must be in format 00:00:00. +# +# Imagemagick and ffmpeg required. + +# Application cache if not stated elsewhere. +cache="${XDG_CACHE_HOME:-$HOME/.cache}/slider" + +while getopts "hvrpi:c:a:o:d:f:t:e:x:s:" o; do case "${o}" in + c) bgc="$OPTARG" ;; + t) fgc="$OPTARG" ;; + f) font="$OPTARG" ;; + i) file="$OPTARG" ;; + a) audio="$OPTARG" ;; + o) outfile="$OPTARG" ;; + d) prepdir="$OPTARG" ;; + r) redo="$OPTARG" ;; + s) ppt="$OPTARG" ;; + e) endtime="$OPTARG" ;; + x) res="$OPTARG" + echo "$res" | grep -qv "^[0-9]\+x[0-9]\+$" && + echo "Resolution must be dimensions separated by a 'x': 1280x720, etc." && + exit 1 ;; + p) echo "Purge old build files in $cache? [y/N]" + read -r confirm + echo "$confirm" | grep -iq "^y$" && rm -rf "$cache" && echo "Done." + exit ;; + v) verbose=True ;; + *) echo "$(basename "$0") usage: + -i input timecode list (required) + -a audio file + -c color of background (use html names, black is default) + -t text color for text slides (white is default) + -s text font size for text slides (150 is default) + -f text font for text slides (sans serif is default) + -o output video file + -e if no audio given, the time in seconds that the last slide will be shown (5 is default) + -x resolution (1920x1080 is default) + -d tmp directory + -r rerun imagemagick commands even if done previously (in case files or background has changed) + -p purge old build files instead of running + -v be verbose" && exit 1 + +esac done + +# Check that the input file looks like it should. +{ head -n 1 "$file" 2>/dev/null | grep -q "^00:00:00 " ;} || { + echo "Give an input file with -i." && + echo "The file should look as this example: + +00:00:00 first_image.jpg +00:00:03 otherdirectory/next_image.jpg +00:00:09 this_image_starts_at_9_seconds.jpg +etc... + +Timecodes and filenames must be separated by Tabs." && + exit 1 + } + +if [ -n "${audio+x}" ]; then + # Check that the audio file looks like an actual audio file. + case "$(file --dereference --brief --mime-type -- "$audio")" in + audio/*) ;; + *) echo "That doesn't look like an audio file."; exit 1 ;; + esac + totseconds="$(date '+%s' -d $(ffmpeg -i "$audio" 2>&1 | awk '/Duration/ {print $2}' | sed s/,//))" +fi + +prepdir="${prepdir:-$cache/$file}" +outfile="${outfile:-$file.mp4}" +prepfile="$prepdir/$file.prep" + +[ -n "${verbose+x}" ] && echo "Preparing images... May take a while depending on the number of files." +mkdir -p "$prepdir" + +{ +while read -r x; +do + # Get the time from the first column. + time="${x%% *}" + seconds="$(date '+%s' -d "$time")" + # Duration is not used on the first looped item. + duration="$((seconds - prevseconds))" + + # Get the filename/text content from the rest. + content="${x#* }" + base="$(basename "$content")" + base="${base%.*}.jpg" + + if [ -f "$content" ]; then + # If images have already been made in a previous run, do not recreate + # them unless -r was given. + { [ ! -f "$prepdir/$base" ] || [ -n "${redo+x}" ] ;} && + magick -size "${res:-1920x1080}" canvas:"${bgc:-black}" -gravity center "$content" -resize 1920x1080 -composite "$prepdir/$base" + else + { [ ! -f "$prepdir/$base" ] || [ -n "${redo+x}" ] ;} && + magick -size "${res:-1920x1080}" -background "${bgc:-black}" -fill "${fgc:-white}" -font "${font:-Sans}" -pointsize "${ppt:-150}" -gravity center label:"$content" "$prepdir/$base" + fi + + # If the first line, do not write yet. + [ "$time" = "00:00:00" ] || echo "file '$prevbase' +duration $duration" + + # Keep the information required for the next file. + prevbase="$base" + prevtime="$time" + prevseconds="$(date '+%s' -d "$prevtime")" +done < "$file" +# Do last file which must be given twice as follows +endtime="$((totseconds-seconds))" +echo "file '$base' +duration ${endtime:-5} +file '$base'" +} > "$prepfile" +if [ -n "${audio+x}" ]; then + ffmpeg -hide_banner -y -f concat -safe 0 -i "$prepfile" -i "$audio" -c:a aac -vsync vfr -c:v libx264 -pix_fmt yuv420p "$outfile" +else + ffmpeg -hide_banner -y -f concat -safe 0 -i "$prepfile" -vsync vfr -c:v libx264 -pix_fmt yuv420p "$outfile" +fi + +# Might also try: +# -vf "fps=${fps:-24},format=yuv420p" "$outfile" +# but has given some problems. diff --git a/.local/bin/start-redshift.sh b/.local/bin/start-redshift.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +redshift -P -O 4500 diff --git a/.local/bin/statusbar/sb-battery b/.local/bin/statusbar/sb-battery @@ -0,0 +1,37 @@ +#!/bin/sh + +# Prints all batteries, their percentage remaining and an emoji corresponding +# to charge status (P for plugged up, D for discharging on battery, etc.). + +case $BLOCK_BUTTON in + 3) notify-send "Battery module" "D: discharging +N: not charging +S: stagnant charge +P: charging +:3: charged +uwaa!: battery very low! +- Scroll to change adjust xbacklight." ;; + 4) xbacklight -inc 10 ;; + 5) xbacklight -dec 10 ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# Loop through all attached batteries and format the info +for battery in /sys/class/power_supply/BAT?*; do + # If non-first battery, print a space separator. + [ -n "${capacity+x}" ] && printf " " + # Sets up the status and capacity + case "$(cat "$battery/status" 2>&1)" in + "Full") status=":3 " ;; + "Discharging") status="D " ;; + "Charging") status="P " ;; + "Not charging") status="N " ;; + "Unknown") status="idk " ;; + *) exit 1 ;; + esac + capacity="$(cat "$battery/capacity" 2>&1)" + # Will make a warn variable if discharging and low + [ "$status" = "D " ] && [ "$capacity" -le 5 ] && warn="uwaa! " + # Prints the info + printf "%s%s%d%%" "$status" "$warn" "$capacity"; unset warn +done && printf "\\n" diff --git a/.local/bin/statusbar/sb-brightness b/.local/bin/statusbar/sb-brightness @@ -0,0 +1,23 @@ +#!/bin/sh + +# current brightness +curr_brightness=$(cat /sys/class/backlight/*/brightness) + +# max_brightness +max_brightness=$(cat /sys/class/backlight/*/max_brightness) + +# brightness percentage +brightness_per=$((100 * curr_brightness / max_brightness)) + +case $BLOCK_BUTTON in + 1) + ;; + 3) + notify-send "๐Ÿ’ก Brightness module" "\- Shows current brightness level โ˜€๏ธ." + ;; + 6) + setsid -f "$TERMINAL" -e "$EDITOR" "$0" + ;; +esac + +echo "๐Ÿ’ก ${brightness_per}%" diff --git a/.local/bin/statusbar/sb-clock b/.local/bin/statusbar/sb-clock @@ -0,0 +1,29 @@ +#!/bin/sh + +clock=$(date '+%I') + +case "$clock" in + "00") icon="" ;; + "01") icon="" ;; + "02") icon="" ;; + "03") icon="" ;; + "04") icon="" ;; + "05") icon="" ;; + "06") icon="" ;; + "07") icon="" ;; + "08") icon="" ;; + "09") icon="" ;; + "10") icon="" ;; + "11") icon="" ;; + "12") icon="" ;; +esac + +case $BLOCK_BUTTON in + 1) notify-send "This Month" "$(cal | sed "s/\<$(date +'%e'|tr -d ' ')\>/<b><span color='red'>&<\/span><\/b>/")" && notify-send "Appointments" "$(calcurse -d3)" ;; + 2) setsid -f "$TERMINAL" -e calcurse ;; + 3) notify-send "Time/date module" "\- Left click to show upcoming appointments for the next three days via \`calcurse -d3\` and show the month via \`cal\` +- Middle click opens calcurse if installed" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +date "+%Y %b %d (%a) $icon%I:%M%p" diff --git a/.local/bin/statusbar/sb-cpu b/.local/bin/statusbar/sb-cpu @@ -0,0 +1,12 @@ +#!/bin/sh + +case $BLOCK_BUTTON in + 1) notify-send "๐Ÿ–ฅ CPU hogs" "$(ps axch -o cmd:15,%cpu --sort=-%cpu | head)\\n(100% per core)" ;; + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "๐Ÿ–ฅ CPU module " "\- Shows CPU temperature. +- Click to show intensive processes. +- Middle click to open htop." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +sensors | awk '/Core 0/ {print "๐ŸŒก" $3}' diff --git a/.local/bin/statusbar/sb-cpubars b/.local/bin/statusbar/sb-cpubars @@ -0,0 +1,44 @@ +#!/bin/sh + +# Module showing CPU load as a changing bars. +# Just like in polybar. +# Each bar represents amount of load on one core since +# last run. + +# Cache in tmpfs to improve speed and reduce SSD load +cache=/tmp/cpubarscache + +case $BLOCK_BUTTON in + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "๐Ÿชจ CPU load module" "Each bar represents +one CPU core";; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# id total idle +stats=$(awk '/cpu[0-9]+/ {printf "%d %d %d\n", substr($1,4), ($2 + $3 + $4 + $5), $5 }' /proc/stat) +[ ! -f $cache ] && echo "$stats" > "$cache" +old=$(cat "$cache") +printf "๐Ÿชจ" +echo "$stats" | while read -r row; do + id=${row%% *} + rest=${row#* } + total=${rest%% *} + idle=${rest##* } + + case "$(echo "$old" | awk '{if ($1 == id) + printf "%d\n", (1 - (idle - $3) / (total - $2))*100 /12.5}' \ + id="$id" total="$total" idle="$idle")" in + + "0") printf "โ–";; + "1") printf "โ–‚";; + "2") printf "โ–ƒ";; + "3") printf "โ–„";; + "4") printf "โ–…";; + "5") printf "โ–†";; + "6") printf "โ–‡";; + "7") printf "โ–ˆ";; + "8") printf "โ–ˆ";; + esac +done; printf "\\n" +echo "$stats" > "$cache" diff --git a/.local/bin/statusbar/sb-disk b/.local/bin/statusbar/sb-disk @@ -0,0 +1,23 @@ +#!/bin/sh + +# Status bar module for disk space +# $1 should be drive mountpoint, otherwise assumed /. + +location=${1:-/} + +[ -d "$location" ] || exit + +case $BLOCK_BUTTON in + 1) notify-send "๐Ÿ’ฝ Disk space" "$(df -h --output=target,used,size)" ;; + 3) notify-send "๐Ÿ’ฝ Disk module" "\- Shows used hard drive space. +- Click to show all disk info." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +case "$location" in + "/home"* ) icon="๐Ÿ " ;; + "/mnt"* ) icon="๐Ÿ’พ" ;; + *) icon="๐Ÿ–ฅ";; +esac + +printf "%s: %s\n" "$icon" "$(df -h "$location" | awk ' /[0-9]/ {print $3 "/" $2}')" diff --git a/.local/bin/statusbar/sb-doppler b/.local/bin/statusbar/sb-doppler @@ -0,0 +1,280 @@ +#!/bin/sh + +# Show a Doppler RADAR of a user's preferred location. + +secs=600 # Download a new doppler radar if one hasn't been downloaded in $secs seconds. +radarloc="${XDG_CACHE_HOME:-$HOME/.cache}/radar" +doppler="${XDG_CACHE_HOME:-$HOME/.cache}/doppler.gif" + +pickloc() { chosen="$(echo "US: CONUS: Continental United States +US: Northeast +US: Southeast +US: PacNorthWest +US: PacSouthWest +US: UpperMissVly +US: SouthMissVly +US: SouthPlains +US: NorthRockies +US: SouthRockies +US: Alaska +US: Carib +US: Hawaii +US: CentGrLakes +US: Conus-Large +US: KABR: Aberdeen, SD +US: KBIS: Bismarck, ND +US: KFTG: Denver/Boulder, CO +US: KDMX: Des Moines, IA +US: KDTX: Detroit, MI +US: KDDC: Dodge City, KS +US: KDLH: Duluth, MN +US: KCYS: Cheyenne, WY +US: KLOT: Chicago, IL +US: KGLD: Goodland, KS +US: KUEX: Hastings, NE +US: KGJX: Grand Junction, CO +US: KGRR: Grand Rapids, MI +US: KMVX: Fargo/Grand Forks, ND +US: KGRB: Green Bay, WI +US: KIND: Indianapolis, IN +US: KJKL: Jackson, KY +US: KARX: La Crosse, WI +US: KILX: Lincoln/Central Illinois, IL +US: KLVX: Louisville, KY +US: KMQT: Marquette +US: KMKX: Milwaukee, WI +US: KMPX: Minneapolis, MN +US: KAPX: Gaylord/Alpena, MI +US: KLNX: North Platte, NE +US: KIWX: N. Webster/Northern, IN +US: KOAX: Omaha, NE +US: KPAH: Paducah, KY +US: KEAX: Pleasant Hill, MO +US: KPUX: Pueblo, CO +US: KDVN: Quad Cities, IA +US: KUDX: Rapid City, SD +US: KRIW: Riverton, WY +US: KSGF: Springfield, MO +US: KLSX: St. LOUIS, MO +US: KFSD: Sioux Falls, SD +US: KTWX: Topeka, KS +US: KICT: Wichita, KS +US: KVWX: Paducah, KY +US: ICAO: Responsible Wfo +US: KLTX: WILMINGTON, NC +US: KCCX: State College/Central, PA +US: KLWX: Sterling, VA +US: KFCX: Blacksburg/Roanoke, VA +US: KRAX: Raleigh/Durham, NC +US: KGYX: Portland, ME +US: KDIX: Mt Holly/Philadelphia, PA +US: KPBZ: Pittsburgh, PA +US: KAKQ: Wakefield, VA +US: KMHX: Morehead City, NC +US: KGSP: Greer/Greenville/Sprtbg, SC +US: KILN: Wilmington/Cincinnati, OH +US: KCLE: Cleveland, OH +US: KCAE: Columbia, SC +US: KBGM: Binghamton, NY +US: KENX: Albany, NY +US: KBUF: Buffalo, NY +US: KCXX: Burlington, VT +US: KCBW: Caribou, ME +US: KBOX: Boston /Taunton, MA +US: KOKX: New York City, NY +US: KCLX: Charleston, SC +US: KRLX: Charleston, WV +US: ICAO: Responsible WFO +US: KBRO: Brownsville, TX +US: KABX: Albuquerque, NM +US: KAMA: Amarillo, TX +US: KFFC: Peachtree City/Atlanta, GA +US: KEWX: Austin/Sanantonio, TX +US: KBMX: Birmingham, AL +US: KCRP: Corpus Christi, TX +US: KFWS: Dallas / Ft. Worth, TX +US: KEPZ: El Paso, TX +US: KHGX: Houston/ Galveston, TX +US: KJAX: Jacksonville, FL +US: KBYX: Key West, FL +US: KMRX: Morristown/knoxville, TN +US: KLBB: Lubbock, TX +US: KLZK: Little Rock, AR +US: KLCH: Lake Charles, LA +US: KOHX: Nashville, TN +US: KMLB: Melbourne, FL +US: KNQA: Memphis, TN +US: KAMX: Miami, FL +US: KMAF: Midland/odessa, TX +US: KTLX: Norman, OK +US: KHTX: Huntsville, AL +US: KMOB: Mobile, AL +US: KTLH: Tallahassee, FL +US: KTBW: Tampa Bay Area, FL +US: KSJT: San Angelo, TX +US: KINX: Tulsa, OK +US: KSRX: Tulsa, OK +US: KLIX: New Orleans/slidell, LA +US: KDGX: Jackson, MS +US: KSHV: Shreveport, LA +US: ICAO: Responsible WFO +US: KLGX: Seattle / Tacoma, WA +US: KOTX: Spokane, WA +US: KEMX: Tucson, AZ +US: KYUX: Phoenix, AZ +US: KNKX: San Diego, CA +US: KMUX: Monterey/san Francisco, CA +US: KHNX: San Joaquin/hanford, CA +US: KSOX: San Diego, CA +US: KATX: Seattle / Tacoma, WA +US: KIWA: Phoenix, AZ +US: KRTX: Portland, OR +US: KSFX: Pocatello, ID +US: KRGX: Reno, NV +US: KDAX: Sacramento, CA +US: KMTX: Salt Lake City, UT +US: KPDT: Pendleton, OR +US: KMSX: Missoula, MT +US: KESX: Las Vegas, NV +US: KVTX: Los Angeles, CA +US: KMAX: Medford, OR +US: KFSX: Flagstaff, AZ +US: KGGW: Glasgow, MT +US: KLRX: Elko, NV +US: KBHX: Eureka, CA +US: KTFX: Great Falls, MT +US: KCBX: Boise, ID +US: KBLX: Billings, MT +US: KICX: Salt Lake City, UT +US: ICAO: Responsible Wfo W/ MSCF +US: PABC: Anchorage, AK +US: PAPD: Fairbanks, AK +US: PHKM: Honolulu, HI +US: PAHG: Anchorage, AK +US: PAKC: Anchorage, AK +US: PAIH: Anchorage, AK +US: PHMO: Honolulu, HI +US: PAEC: Fairbanks, AK +US: TJUA: San Juan, PR +US: PACG: Juneau, AK +US: PHKI: Honolulu, HI +US: PHWA: Honolulu, HI +US: ICAO: Responsible Wfo W/ MSCF +US: KFDR: Norman, OK +US: PGUA: Guam +US: KBBX: Sacramento, CA +US: KFDX: Albuquerque, NM +US: KGWX: Jackson, MS +US: KDOX: Wakefield, VA +US: KDYX: San Angelo, TX +US: KEYX: Las Vegas, NV +US: KEVX: Mobile, AL +US: KHPX: Paducah, KY +US: KTYX: Burlington, VT +US: KGRK: Dallas / Ft. Worth, TX +US: KPOE: Lake Charles, LA +US: KEOX: Tallahassee, FL +US: KHDX: El Paso, TX +US: KDFX: San Antonio, TX +US: KMXX: Birmingham, AL +US: KMBX: Bismarck, ND +US: KVAX: Jacksonville, FL +US: KJGX: Peachtree City/atlanta, GA +US: KVNX: Norman, OK +US: KVBX: Vandenberg Afb: Orcutt, CA +EU: Europe +EU: GB: Great Brittain +EU: SCAN: Scandinavia. Norway, Sweden And Denmark +EU: ALPS: The Alps +EU: NL: The Netherlands +EU: DE: Germany +EU: SP: Spain +EU: FR: France +EU: IT: Italy +EU: PL: Poland +EU: GR: Greece +EU: TU: Turkey +EU: RU: Russia +EU: BA: Bahrain +EU: BC: Botswana +EU: SE: Republic of Seychelles +EU: HU: Hungary +EU: UK: Ukraine +AF: AF: Africa +AF: WA: West Africa +AF: ZA: South Africa +AF: DZ: Algeria +AF: CE: Canary Islands +AF: NG: Nigeria +AF: TD: Chad +AF: CG: Democratic Republic of Congo +AF: EG: Egypt +AF: ET: Ethiopia +AF: CM: Cameroon +AF: IS: Israel +AF: LY: Libya +AF: MG: Madagascar +AF: MO: Morocco +AF: BW: Namibia +AF: SA: Saudi Arabia +AF: SO: Somalia +AF: SD: Sudan +AF: TZ: Tanzania +AF: TN: Tunisia +AF: ZM: Zambia +AF: KE: Kenya +AF: AO: Angola +DE: BAW: Baden-Wรผrttemberg +DE: BAY: Bavaria +DE: BBB: Berlin +DE: BBB: Brandenburg +DE: HES: Hesse +DE: MVP: Mecklenburg-Western Pomerania +DE: NIB: Lower Saxony +DE: NIB: Bremen +DE: NRW: North Rhine-Westphalia +DE: RPS: Rhineland-Palatinate +DE: RPS: Saarland +DE: SAC: Saxony +DE: SAA: Saxony-Anhalt +DE: SHH: Schleswig-Holstein +DE: SHH: Hamburg +DE: THU: Thuringia" | dmenu -r -i -l 50 -p "Select a radar to use as default:" | tr "[:lower:]" "[:upper:]")" + +# Ensure user did not escape. +[ -z "$chosen" ] && exit 1 + +# Set continent code and radar code. +continentcode=${chosen%%:*} +radarcode=${chosen#* } radarcode=${radarcode%:*} + +# Print codes to $radarloc file. + printf "%s,%s\\n" "$continentcode" "$radarcode" > "$radarloc" ;} + +getdoppler() { + cont=$(cut -c -2 "$radarloc") + loc=$(cut -c 4- "$radarloc") + notify-send "๐ŸŒฆ๏ธ Doppler RADAR" "Pulling most recent Doppler RADAR for $loc." + case "$cont" in + "US") curl -sL "https://radar.weather.gov/ridge/standard/${loc}_loop.gif" > "$doppler" ;; + "EU") curl -sL "https://api.sat24.com/animated/${loc}/rainTMC/2/" > "$doppler" ;; + "AF") curl -sL "https://api.sat24.com/animated/${loc}/rain/2/" > "$doppler" ;; + "DE") loc="$(echo "$loc" | tr "[:upper:]" "[:lower:]")" + curl -sL "https://www.dwd.de/DWD/wetter/radar/radfilm_${loc}_akt.gif" > "$doppler" ;; + esac +} + +showdoppler() { setsid -f mpv --no-osc --loop=inf --no-terminal "$doppler" ;} + +case $BLOCK_BUTTON in + 1) [ ! -f "$radarloc" ] && pickloc && getdoppler + [ $(($(date '+%s') - $(stat -c %Y "$doppler"))) -gt "$secs" ] && getdoppler + showdoppler ;; + 2) pickloc && getdoppler && showdoppler ;; + 3) notify-send "๐Ÿ—บ๏ธ Doppler RADAR module" "\- Left click for local Doppler RADAR. +- Middle click to update RADAR location. +After $secs seconds, new clicks will also automatically update the doppler RADAR." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +echo ๐ŸŒ… diff --git a/.local/bin/statusbar/sb-forecast b/.local/bin/statusbar/sb-forecast @@ -0,0 +1,53 @@ +#!/bin/sh + +# Displays today's precipication chance (โ˜”), and daily low (๐Ÿฅถ) and high (๐ŸŒž). +# Usually intended for the statusbar. + +url="${WTTRURL:-wttr.in}" +weatherreport="${XDG_CACHE_HOME:-$HOME/.cache}/weatherreport" + +# Get a weather report from 'wttr.in' and save it locally. +getforecast() { timeout --signal=1 2s curl -sf "$url/$LOCATION" > "$weatherreport" || exit 1; } + +# Forecast should be updated only once a day. +checkforecast() { + [ -s "$weatherreport" ] && [ "$(stat -c %y "$weatherreport" 2>/dev/null | + cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] +} + +getprecipchance() { + echo "$weatherdata" | sed '16q;d' | # Extract line 16 from file + grep -wo "[0-9]*%" | # Find a sequence of digits followed by '%' + sort -rn | # Sort in descending order + head -1q # Extract first line +} + +getdailyhighlow() { + echo "$weatherdata" | sed '13q;d' | # Extract line 13 from file + grep -o "m\\([-+]\\)*[0-9]\\+" | # Find temperatures in the format "m<signed number>" + sed 's/[+m]//g' | # Remove '+' and 'm' + sort -g | # Sort in ascending order + sed -e 1b -e '$!d' # Extract the first and last lines +} + +readfile() { weatherdata="$(cat "$weatherreport")" ;} + +showweather() { + readfile + printf "โ˜”%s ๐Ÿฅถ%sยฐ ๐ŸŒž%sยฐ\n" "$(getprecipchance)" $(getdailyhighlow) +} + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e less -Sf "$weatherreport" ;; + 2) getforecast && showweather ;; + 3) notify-send "๐ŸŒˆ Weather module" "\- Left click for full forecast. +- Middle click to update forecast. +โ˜”: Chance of rain/snow +๐Ÿฅถ: Daily low +๐ŸŒž: Daily high" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +checkforecast || getforecast + +showweather diff --git a/.local/bin/statusbar/sb-help-icon b/.local/bin/statusbar/sb-help-icon @@ -0,0 +1,17 @@ +#!/bin/sh + +# The clickable help menu. Middle click to restart wm. + +# If dwm is running, use dwm's readme and restart. +pidof dwm >/dev/null && + READMEFILE=/usr/local/share/dwm/larbs.mom + restartwm() { pkill -HUP dwm ;} || + restartwm() { i3 restart ;} + +case $BLOCK_BUTTON in + 1) groff -mom "${READMEFILE:-${XDG_DATA_HOME:-$HOME/.local/share}/larbs/readme.mom}" -Tpdf | zathura - ;; + 2) restartwm ;; + 3) notify-send "โ“ Help module" "\- Left click to open LARBS guide. +- Middle click to refresh window manager." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac; echo "โ“" diff --git a/.local/bin/statusbar/sb-internet b/.local/bin/statusbar/sb-internet @@ -0,0 +1,33 @@ +#!/bin/sh + +# Show wifi w and percent strength or nw if none. +# Show ew if connected to ethernet or ne if none. +# Show v if a vpn connection is active + +case $BLOCK_BUTTON in + 1) "$TERMINAL" -e nmtui; pkill -RTMIN+4 dwmblocks ;; + 3) notify-send "Internet module" "\- Click to connect +WD: wifi disabled +NW: no wifi connection +W: wifi connection with quality +NE: no ethernet +EW: ethernet working +V: vpn is active +" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +# Wifi +if [ "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'up' ] ; then + wifiicon="$(awk '/^\s*w/ { print "W", int($3 * 100 / 70) "% " }' /proc/net/wireless)" +elif [ "$(cat /sys/class/net/w*/operstate 2>/dev/null)" = 'down' ] ; then + [ "$(cat /sys/class/net/w*/flags 2>/dev/null)" = '0x1003' ] && wifiicon="NW " || wifiicon="W " +fi + +# Ethernet +[ "$(cat /sys/class/net/e*/operstate 2>/dev/null)" = 'up' ] && ethericon="EW" || ethericon="NE" + +# TUN +[ -n "$(cat /sys/class/net/tun*/operstate 2>/dev/null)" ] && tunicon=" V" + +printf "%s%s%s\n" "$wifiicon" "$ethericon" "$tunicon" diff --git a/.local/bin/statusbar/sb-iplocate b/.local/bin/statusbar/sb-iplocate @@ -0,0 +1,15 @@ +#!/bin/sh + +# Gets your public ip address checks which country you are in and +# displays that information in the statusbar +# +# https://www.maketecheasier.com/ip-address-geolocation-lookups-linux/ + +set -e + +ifinstalled "geoip" +addr="$(geoiplookup "$(curl -sfm 1 ifconfig.me 2>/dev/null)")" +name="${addr##*, }" +flag="$(grep "flag: $name" "${XDG_DATA_HOME:-$HOME/.local/share}/larbs/emoji")" +flag="${flag%% *}" +printf "%s %s\\n" "$flag" "$name" diff --git a/.local/bin/statusbar/sb-kbselect b/.local/bin/statusbar/sb-kbselect @@ -0,0 +1,17 @@ +#!/bin/sh +# works on any init system +# requirements: dmenu, xorg-setxkbmap +kb="$(setxkbmap -query | grep -oP 'layout:\s*\K\w+')" || exit 1 + +case $BLOCK_BUTTON in + 1) kb_choice="$(awk '/! layout/{flag=1; next} /! variant/{flag=0} flag {print $2, "- " $1}' /usr/share/X11/xkb/rules/base.lst | dmenu -l 15)" + [ -z "$kb_choice" ] && exit 0 + kb="$(echo "$kb_choice" | awk '{print $3}')" + setxkbmap "$kb" + pkill -RTMIN+30 "${STATUSBAR:-dwmblocks}";; + 3) notify-send "โŒจ Keyboard/language module" "$(printf "%s" "\- Current layout: $(setxkbmap -query | grep -oP 'layout:\s*\K\w+')") +- Left click to change keyboard.";; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +echo "$kb" diff --git a/.local/bin/statusbar/sb-mailbox b/.local/bin/statusbar/sb-mailbox @@ -0,0 +1,20 @@ +#!/bin/sh + +# Displays number of unread mail and an loading icon if updating. +# When clicked, brings up `neomutt`. + +case $BLOCK_BUTTON in + 1) setsid -w -f "$TERMINAL" -e neomutt; pkill -RTMIN+12 "${STATUSBAR:-dwmblocks}" ;; + 2) setsid -f mw -Y >/dev/null ;; + 3) notify-send "M Mail module" "\- Shows unread mail +- Shows S if syncing mail +- Left click opens neomutt +- Middle click syncs mail" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +unread="$(find "${XDG_DATA_HOME:-$HOME/.local/share}"/mail/*/[Ii][Nn][Bb][Oo][Xx]/new/* -type f | wc -l 2>/dev/null)" + +pidof mbsync >/dev/null 2>&1 && icon="S" + +[ "$unread" = "0" ] && [ "$icon" = "" ] || echo "M$unread$icon" diff --git a/.local/bin/statusbar/sb-memory b/.local/bin/statusbar/sb-memory @@ -0,0 +1,12 @@ +#!/bin/sh + +case $BLOCK_BUTTON in + 1) notify-send "๐Ÿง  Memory hogs" "$(ps axch -o cmd:15,%mem --sort=-%mem | head)" ;; + 2) setsid -f "$TERMINAL" -e htop ;; + 3) notify-send "๐Ÿง  Memory module" "\- Shows Memory Used/Total. +- Click to show memory hogs. +- Middle click to open htop." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +free --mebi | sed -n '2{p;q}' | awk '{printf ("๐Ÿง %2.2fGiB/%2.2fGiB\n", ( $3 / 1024), ($2 / 1024))}' diff --git a/.local/bin/statusbar/sb-moonphase b/.local/bin/statusbar/sb-moonphase @@ -0,0 +1,37 @@ +#!/bin/sh + +# Shows the current moon phase. + +moonfile="${XDG_DATA_HOME:-$HOME/.local/share}/moonphase" + +[ -s "$moonfile" ] && [ "$(stat -c %y "$moonfile" 2>/dev/null | cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] || + { curl -sf "wttr.in/?format=%m" > "$moonfile" || exit 1 ;} + +icon="$(cat "$moonfile")" + +case "$icon" in + ๐ŸŒ‘) name="New" ;; + ๐ŸŒ’) name="Waxing Crescent" ;; + ๐ŸŒ“) name="First Quarter" ;; + ๐ŸŒ”) name="Waxing Gibbous" ;; + ๐ŸŒ•) name="Full" ;; + ๐ŸŒ–) name="Waning Gibbous" ;; + ๐ŸŒ—) name="Last Quarter" ;; + ๐ŸŒ˜) name="Waning Crescent" ;; + *) exit 1 ;; +esac + +echo "${icon-?}" + +case $BLOCK_BUTTON in + 3) notify-send "๐ŸŒœ Moon phase module" "Displays current moon phase. +- ๐ŸŒ‘: New +- ๐ŸŒ’: Waxing Crescent +- ๐ŸŒ“: First Quarter +- ๐ŸŒ”: Waxing Gibbous +- ๐ŸŒ•: Full +- ๐ŸŒ–: Waning Gibbous +- ๐ŸŒ—: Last Quarter +- ๐ŸŒ˜: Waning Crescent" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac diff --git a/.local/bin/statusbar/sb-mpdup b/.local/bin/statusbar/sb-mpdup @@ -0,0 +1,8 @@ +#!/bin/sh + +# This loop will update the mpd statusbar module whenever a command changes the +# music player's status. mpd must be running on X's start for this to work. + +while : ; do + mpc idle >/dev/null && kill -45 "$(pidof "${STATUSBAR:-dwmblocks}")" || break +done diff --git a/.local/bin/statusbar/sb-music b/.local/bin/statusbar/sb-music @@ -0,0 +1,19 @@ +#!/bin/sh + +filter() { sed "/^volume:/d;s/\\[paused\\].*/โธ/g;/\\[playing\\].*/d;/^ERROR/Q" | paste -sd ' ' -;} + +pidof -x sb-mpdup >/dev/null 2>&1 || sb-mpdup >/dev/null 2>&1 & + +case $BLOCK_BUTTON in + 1) mpc status | filter ; setsid -f "$TERMINAL" -e ncmpcpp ;; # right click, pause/unpause + 2) mpc toggle | filter ;; # right click, pause/unpause + 3) mpc status | filter ; notify-send "๐ŸŽต Music module" "\- Shows mpd song playing. +- โธ when paused. +- Left click opens ncmpcpp. +- Middle click pauses. +- Scroll changes track.";; # right click, pause/unpause + 4) mpc prev | filter ;; # scroll up, previous + 5) mpc next | filter ;; # scroll down, next + 6) mpc status | filter ; setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; + *) mpc status | filter ;; +esac diff --git a/.local/bin/statusbar/sb-nettraf b/.local/bin/statusbar/sb-nettraf @@ -0,0 +1,29 @@ +#!/bin/sh + +# Module showing network traffic. Shows how much data has been received (RX) or +# transmitted (TX) since the previous time this script ran. So if run every +# second, gives network traffic per second. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e bmon ;; + 3) notify-send "Network traffic module" "โ†“: Traffic received +โ†‘: Traffic transmitted" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +update() { + sum=0 + for arg; do + read -r i < "$arg" + sum=$(( sum + i )) + done + cache=/tmp/${1##*/} + [ -f "$cache" ] && read -r old < "$cache" || old=0 + printf %d\\n "$sum" > "$cache" + printf %d\\n $(( sum - old )) +} + +rx=$(update /sys/class/net/[ew]*/statistics/rx_bytes) +tx=$(update /sys/class/net/[ew]*/statistics/tx_bytes) + +printf "u%2sB n%2sB\\n" $(numfmt --to=iec $rx $tx) diff --git a/.local/bin/statusbar/sb-news b/.local/bin/statusbar/sb-news @@ -0,0 +1,17 @@ +#!/bin/sh + +# Displays number of unread news items and an loading icon if updating. +# When clicked, brings up `newsboat`. + +case $BLOCK_BUTTON in + 1) setsid "$TERMINAL" -e newsboat ;; + 2) setsid -f newsup >/dev/null && exit ;; + 3) notify-send "๐Ÿ“ฐ News module" "\- Shows unread news items +- Shows ๐Ÿ”ƒ if updating with \`newsup\` +- Left click opens newsboat +- Middle click syncs RSS feeds +<b>Note:</b> Only one instance of newsboat (including updates) may be running at a time." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + + cat /tmp/newsupdate 2>/dev/null || echo "$(newsboat -x print-unread | awk '{ if($1>0) print "๐Ÿ“ฐ" $1}')$(cat "${XDG_CONFIG_HOME:-$HOME/.config}"/newsboat/.update 2>/dev/null)" diff --git a/.local/bin/statusbar/sb-pacpackages b/.local/bin/statusbar/sb-pacpackages @@ -0,0 +1,29 @@ +#!/bin/sh + +# Displays number of upgradeable packages. +# For this to work, have a `pacman -Sy` command run in the background as a +# cronjob every so often as root. This script will then read those packages. +# When clicked, it will run an upgrade via pacman. +# +# Add the following text as a file in /usr/share/libalpm/hooks/statusbar.hook: +# +# [Trigger] +# Operation = Upgrade +# Type = Package +# Target = * +# +# [Action] +# Description = Updating statusbar... +# When = PostTransaction +# Exec = /usr/bin/pkill -RTMIN+8 dwmblocks # Or i3blocks if using i3. + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e sb-popupgrade ;; + 2) notify-send "$(/usr/bin/pacman -Qu)" ;; + 3) notify-send "๐ŸŽ Upgrade module" "๐Ÿ“ฆ: number of upgradable packages +- Left click to upgrade packages +- Middle click to show upgradable packages" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +pacman -Qu | grep -Fcv "[ignored]" | sed "s/^/๐Ÿ“ฆ/;s/^๐Ÿ“ฆ0$//g" diff --git a/.local/bin/statusbar/sb-popupgrade b/.local/bin/statusbar/sb-popupgrade @@ -0,0 +1,9 @@ +#!/bin/sh + +printf "Beginning upgrade.\\n" + +yay -Syu +pkill -RTMIN+8 "${STATUSBAR:-dwmblocks}" + +printf "\\nUpgrade complete.\\nPress <Enter> to exit window.\\n\\n" +read -r _ diff --git a/.local/bin/statusbar/sb-price b/.local/bin/statusbar/sb-price @@ -0,0 +1,58 @@ +#!/bin/sh + +# Usage: +# price <currency-base currency> <name of currency> <icon> <signal> +# price bat-btc "Basic Attention Token" ๐Ÿฆ 24 +# This will give the price of BAT denominated in BTC and will update on +# signal 24. +# When the name of the currency is multi-word, put it in quotes. + +[ -z "$1" ] && exit 1 + +url="${CRYPTOURL:-rate.sx}" +target="${1%%-*}" +denom="${1##*-}" +name="${2:-$1}" +icon="${3:-๐Ÿ’ฐ}" +case "$denom" in + "$target"|usd) denom="usd"; symb="$" ;; + gbp) symb="ยฃ" ;; + eur) symb="โ‚ฌ" ;; + btc) symb="๏…š" ;; +esac +interval="@14d" # History contained in chart preceded by '@' (7d = 7 days) +dir="${XDG_CACHE_HOME:-$HOME/.cache}/crypto-prices" +pricefile="$dir/$target-$denom" +chartfile="$dir/$target-$denom-chart" +filestat="$(stat -c %x "$pricefile" 2>/dev/null)" + +[ -d "$dir" ] || mkdir -p "$dir" + +updateprice() { curl -sf \ + --fail-early "${denom}.${url}/1${target}" "${denom}.${url}/${target}${interval}" \ + --output "$pricefile" --output "$chartfile" || + rm -f "$pricefile" "$chartfile" ;} + +[ "${filestat%% *}" != "$(date '+%Y-%m-%d')" ] && + updateme="1" + +case $BLOCK_BUTTON in + 1) setsid "$TERMINAL" -e less -Srf "$chartfile" ;; + 2) notify-send -u low "$icon Updating..." "Updating $name price..." ; updateme="1" ; showupdate="1" ;; + 3) uptime="$(date -d "$filestat" '+%D at %T' | sed "s|$(date '+%D')|Today|")" + notify-send "$icon $name module" "\- <b>Exact price: \$$(cat "$pricefile")</b> +- Left click for chart of changes. +- Middle click to update. +- Shows ๐Ÿ”ƒ if updating prices. +- <b>Last updated: + $uptime</b>" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +[ -n "$updateme" ] && + updateprice "$target" && + [ -n "$showupdate" ] && + notify-send "$icon Update complete." "$name price is now +\$$(cat "$pricefile")" + +[ -f "$pricefile" ] && printf "%s%s%0.2f" "$icon" "$symb" "$(cat "$pricefile")" diff --git a/.local/bin/statusbar/sb-tasks b/.local/bin/statusbar/sb-tasks @@ -0,0 +1,20 @@ +#!/bin/sh + +# Originally by Andr3as07 <https://github.com/Andr3as07> +# Some changes by Luke +# Rebuild by Tenyun + +# This block displays the number running background tasks. Requires tsp. + +num=$(tsp -l | awk -v numr=0 -v numq=0 '{if (/running/)numr++; if (/queued/)numq++} END{print numr+numq"("numq")"}') + +# Handle mouse clicks +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e tsp -l ;; + 3) notify-send "Tasks module" "๐Ÿค–: number of running/queued background tasks +- Left click opens tsp" ;; # Right click + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +[ "$num" != "0(0)" ] && + echo "๐Ÿค–$num" diff --git a/.local/bin/statusbar/sb-torrent b/.local/bin/statusbar/sb-torrent @@ -0,0 +1,27 @@ +#!/bin/sh + +transmission-remote -l | grep % | + sed " # The letters are for sorting and will not appear. + s/.*Stopped.*/A ๐Ÿ›‘/; + s/.*Seeding.*/Z ๐ŸŒฑ/; + s/.*100%.*/N โœ…/; + s/.*Idle.*/B ๐Ÿ•ฐ๏ธ/; + s/.*Uploading.*/L โฌ†๏ธ/; + s/.*%.*/M โฌ‡๏ธ/" | + sort -h | uniq -c | awk '{print $3 $1}' | paste -sd ' ' - + +case $BLOCK_BUTTON in + 1) setsid -f "$TERMINAL" -e stig ;; + 2) td-toggle ;; + 3) notify-send "๐ŸŒฑ Torrent module" "\- Left click to open stig. +- Middle click to toggle transmission. +- Shift click to edit script. +Module shows number of torrents: +๐Ÿ›‘: paused +๐Ÿ•ฐ: idle (seeds needed) +๐Ÿ”ผ: uploading (unfinished) +๐Ÿ”ฝ: downloading +โœ…: done +๐ŸŒฑ: done and seeding" ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac diff --git a/.local/bin/statusbar/sb-volume b/.local/bin/statusbar/sb-volume @@ -0,0 +1,39 @@ +#!/bin/sh + +# Prints the current volume or M if muted. + +case $BLOCK_BUTTON in + 1) setsid -w -f "$TERMINAL" -e pulsemixer; pkill -RTMIN+10 "${STATUSBAR:-dwmblocks}" ;; + 2) wpctl set-mute @DEFAULT_SINK@ toggle ;; + 4) wpctl set-volume @DEFAULT_SINK@ 1%+ ;; + 5) wpctl set-volume @DEFAULT_SINK@ 1%- ;; + 3) notify-send "Volume module" "\- Shows volume V, M if muted. +- Middle click to mute. +- Scroll to change." ;; + 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; +esac + +vol="$(wpctl get-volume @DEFAULT_AUDIO_SINK@)" + +# If muted, print M and exit. +[ "$vol" != "${vol%\[MUTED\]}" ] && echo 'M ' && exit + +vol="${vol#Volume: }" + +split() { + # For ommiting the . without calling and external program. + IFS=$2 + set -- $1 + printf '%s' "$@" +} + +vol="$(printf "%.0f" "$(split "$vol" ".")")" + +case 1 in + $((vol >= 70)) ) icon="V" ;; + $((vol >= 30)) ) icon="V" ;; + $((vol >= 1)) ) icon="V" ;; + * ) echo M && exit ;; +esac + +echo "$icon $vol%" diff --git a/.local/bin/sysact b/.local/bin/sysact @@ -0,0 +1,26 @@ +#!/bin/sh + +# A dmenu wrapper script for system functions. +export WM="dwm" +case "$(readlink -f /sbin/init)" in + *systemd*) ctl='systemctl' ;; + *) ctl='loginctl' ;; +esac + +wmpid(){ # This function is needed if there are multiple instances of the window manager. + tree="$(pstree -ps $$)" + tree="${tree#*$WM(}" + echo "${tree%%)*}" +} + +case "$(printf "๐Ÿ”’ lock\n๐Ÿšช leave $WM\nโ™ป๏ธ renew $WM\n๐Ÿป hibernate\n๐Ÿ”ƒ reboot\n๐Ÿ–ฅ๏ธshutdown\n๐Ÿ’ค sleep\n๐Ÿ“บ display off" | dmenu -i -p 'Action: ')" in + '๐Ÿ”’ lock') slock ;; + "๐Ÿšช leave $WM") kill -TERM "$(wmpid)" ;; + "โ™ป๏ธ renew $WM") kill -HUP "$(wmpid)" ;; + '๐Ÿป hibernate') slock $ctl hibernate -i ;; + '๐Ÿ’ค sleep') slock $ctl suspend -i ;; + '๐Ÿ”ƒ reboot') $ctl reboot -i ;; + '๐Ÿ–ฅ๏ธshutdown') $ctl poweroff -i ;; + '๐Ÿ“บ display off') xset dpms force off ;; + *) exit 1 ;; +esac diff --git a/.local/bin/tag b/.local/bin/tag @@ -0,0 +1,49 @@ +#!/bin/sh + +err() { echo "Usage: + tag [OPTIONS] file +Options: + -a: artist/author + -t: song/chapter title + -A: album/book title + -n: track/chapter number + -N: total number of tracks/chapters + -d: year of publication + -g: genre + -c: comment +You will be prompted for title, artist, album and track if not given." && exit 1 ;} + +while getopts "a:t:A:n:N:d:g:c:" o; do case "${o}" in + a) artist="${OPTARG}" ;; + t) title="${OPTARG}" ;; + A) album="${OPTARG}" ;; + n) track="${OPTARG}" ;; + N) total="${OPTARG}" ;; + d) date="${OPTARG}" ;; + g) genre="${OPTARG}" ;; + c) comment="${OPTARG}" ;; + *) printf "Invalid option: -%s\\n" "$OPTARG" && err ;; +esac done + +shift $((OPTIND - 1)) + +file="$1" + +temp="$(mktemp -p "$(dirname "$file")")" +trap 'rm -f $temp' HUP INT QUIT TERM PWR EXIT + +[ ! -f "$file" ] && echo 'Provide file to tag.' && err + +[ -z "$title" ] && echo 'Enter a title.' && read -r title +[ -z "$artist" ] && echo 'Enter an artist.' && read -r artist +[ -z "$album" ] && echo 'Enter an album.' && read -r album +[ -z "$track" ] && echo 'Enter a track number.' && read -r track + +cp -f "$file" "$temp" && ffmpeg -i "$temp" -map 0 -y -codec copy \ + -metadata title="$title" \ + -metadata album="$album" \ + -metadata artist="$artist" \ + -metadata track="${track}${total:+/"$total"}" \ + ${date:+-metadata date="$date"} \ + ${genre:+-metadata genre="$genre"} \ + ${comment:+-metadata comment="$comment"} "$file" diff --git a/.local/bin/td-toggle b/.local/bin/td-toggle @@ -0,0 +1,12 @@ +#!/bin/sh + +# If transmission-daemon is running, will ask to kill, else will ask to start. + +if pidof transmission-daemon >/dev/null ; +then + [ "$(printf "No\\nYes" | dmenu -i -p "Turn off transmission-daemon?")" = "Yes" ] && killall transmission-daemon && notify-send "transmission-daemon disabled." +else + ifinstalled transmission-cli || exit + [ "$(printf "No\\nYes" | dmenu -i -p "Turn on transmission daemon?")" = "Yes" ] && transmission-daemon && notify-send "transmission-daemon enabled." +fi +sleep 3 && pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}" diff --git a/.local/bin/texclear b/.local/bin/texclear @@ -0,0 +1,9 @@ +#!/bin/sh + +# Clears the build files of a LaTeX/XeLaTeX build. +# I have vim run this file whenever I exit a .tex file. + +[ "${1##*.}" = "tex" ] && { + find "$(dirname "${1}")" -regex '.*\(_minted.*\|.*\.\(4tc\|xref\|tmp\|pyc\|pyg\|pyo\|fls\|vrb\|fdb_latexmk\|bak\|swp\|aux\|log\|synctex\(busy\)\|lof\|lot\|maf\|idx\|mtc\|mtc0\|nav\|out\|snm\|toc\|bcf\|run\.xml\|synctex\.gz\|blg\|bbl\)\)' -delete +} || printf "Provide a .tex file.\n" + diff --git a/.local/bin/torwrap b/.local/bin/torwrap @@ -0,0 +1,7 @@ +#!/bin/sh + +ifinstalled stig transmission-cli || exit 1 + +! pidof transmission-daemon >/dev/null && transmission-daemon && notify-send "Starting torrent daemon..." + +$TERMINAL -e stig; pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}" diff --git a/.local/bin/transadd b/.local/bin/transadd @@ -0,0 +1,9 @@ +#!/bin/sh + +# Mimeapp script for adding torrent to transmission-daemon, but will also start the daemon first if not running. + +# transmission-daemon sometimes fails to take remote requests in its first moments, hence the sleep. + +pidof transmission-daemon >/dev/null || (transmission-daemon && notify-send "Starting transmission daemon..." && sleep 3 && pkill -RTMIN+7 "${STATUSBAR:-dwmblocks}") + +transmission-remote -a "$@" && notify-send "๐Ÿ”ฝ Torrent added." diff --git a/.local/bin/tutorialvids b/.local/bin/tutorialvids @@ -0,0 +1,26 @@ +#!/bin/sh + +# This gives the user a list of videos they can select and watch without a +# browser. If you want to check a tutorial video, it makes it easy. I'll +# add/remove videos from this list as I go on. + +vidlist=" +dwm (window manager) https://videos.lukesmith.xyz/videos/watch/f6b78db7-b368-4647-bc64-28c08fff1988 +dwmblocks (status bar) https://videos.lukesmith.xyz/w/mmxHMbqZZEr5FManB57Yy1 +pacman (installing/managing programs) https://videos.lukesmith.xyz/videos/watch/8e7cadb9-0fed-47ce-a2a8-6635fa48614b +sxiv/nsxiv (image viewer) https://videos.lukesmith.xyz/videos/watch/ad4c8d85-90c3-4f3d-a1f3-89129e64a3c2 +st (terminal) https://videos.lukesmith.xyz/videos/watch/efddd39d-bac5-4599-b572-177beb4ce6e8 +i3 (old window manager) https://videos.lukesmith.xyz/videos/watch/b861525c-7ada-40ee-a2bb-b5e1ffe0f48b +neomutt (email) https://videos.lukesmith.xyz/videos/watch/83122e83-52d9-4278-ae1a-7d1beeb50c8e +ncmpcpp (music player) https://videos.lukesmith.xyz/videos/watch/b5ac6f0d-a220-4433-88e3-e98fc791dc0a +newsboat (RSS reader) https://videos.lukesmith.xyz/videos/watch/bd2c3fff-40fa-47ea-aa98-5b1ec0c903b6 +lf (file manager) https://videos.lukesmith.xyz/w/rKeHsF5ZHDNDbR1buUKB1c +zathura (pdf viewer) https://videos.lukesmith.xyz/videos/watch/c780f75a-11f6-48a9-a191-d079ebc36ea4 +gpg keys https://videos.lukesmith.xyz/videos/watch/040f5530-4830-4583-9ddc-2080b421531b +calcurse (calendar) https://videos.lukesmith.xyz/videos/watch/4b937e8b-7654-46e3-8d01-79392ec5b3d1 +urlview https://videos.lukesmith.xyz/videos/watch/31a4918f-633b-4bd6-b08e-956ac75d0324 +colorschemes with pywal https://videos.lukesmith.xyz/videos/watch/1b476003-61b2-4609-ac4b-820c3d128643 +vi mode in shell https://videos.lukesmith.xyz/videos/watch/228aa50c-836f-456f-9f0d-a45157fe4313 +pass (password manager) https://videos.lukesmith.xyz/videos/watch/432fc942-5e28-4682-9beb-f5cb237a1dd6 +" +echo "$vidlist" | grep -P "^$(echo "$vidlist" | grep "https:" | sed 's/\t.*//g' | dmenu -i -p "Learn about what? (ESC to cancel)" -l 20 | awk '{print $1}')\s" | sed 's/.*\t//' | xargs -r mpv diff --git a/.local/bin/unix b/.local/bin/unix @@ -0,0 +1,26 @@ +#!/bin/sh + +#original artwork by http://www.sanderfocus.nl/#/portfolio/tech-heroes +#converted to shell by #nixers @ irc.unix.chat + +cat << 'eof' + ,_ ,_==โ–„โ–‚ + , โ–‚โ–ƒโ–„โ–„โ–…โ–…โ–…โ–‚โ–…ยพ. / / + โ–„โ–†<ยด "ยปโ–“โ–“โ–“%\ / / / / + ,โ–…7" ยด>โ–“โ–“โ–“% / / > / >/% + โ–ยถโ–“ ,ยปโ–“โ–“ยพยด /> %/%// / / + โ–“โ–ƒโ–…โ–…โ–…โ–ƒ,,โ–„โ–…โ–…โ–…ร†\// ///>// />/ / + Vโ•‘ยซยผ.;โ†’ โ•‘<ยซ.,`=// />//%/% / / + //โ• <ยด -ยฒ,)(โ–“~"-โ•/ยพ/ %/>/ /> + / / / โ–% -./โ–„โ–ƒโ–„โ–…โ–, /7//;//% / / + / ////`โ–Œโ– %zWv xXโ–“โ–‡โ–Œ//&;% / / + / / / %//%/ยพยฝยดโ–Œโ–ƒโ–„โ–„โ–„โ–„โ–ƒโ–ƒโ–ยถ\/& / + </ /</%//`โ–“!%โ–“%โ•ฃ[38;5;255;โ•ฃWY<Y)y&/`\ + / / %/%//</%//\i7; โ• N>)VY>7; \_ UNIX IS VERY SIMPLE IT JUST NEEDS A + / /</ //<///<_/%\โ–“ V%W%ยฃ)XY _/%โ€พ\_, GENIUS TO UNDERSTAND ITS SIMPLICITY + / / //%/_,=--^/%/%%\ยพ%ยถ%%} /%%%%%%;\, + %/< /_/ %%%%%;X%%\%%;, _/%%%;, \ + / / %%%%%%;, \%%l%%;// _/%;, dmr + / %%%;, <;\-=-/ / + ;, l +eof diff --git a/.local/bin/unmounter b/.local/bin/unmounter @@ -0,0 +1,28 @@ +#!/bin/sh + +# Unmount USB drives or Android phones. Replaces the older `dmenuumount`. Fewer +# prompt and also de-decrypts LUKS drives that are unmounted. + +set -e + +mounteddroids="$(grep simple-mtpfs /etc/mtab | awk '{print "๐Ÿ“ฑ" $2}')" +lsblkoutput="$(lsblk -nrpo "name,type,size,mountpoint")" +mounteddrives="$(echo "$lsblkoutput" | awk '($2=="part"||$2="crypt")&&$4!~/\/boot|\/home$|SWAP/&&length($4)>1{printf "๐Ÿ’พ%s (%s)\n",$4,$3}')" + +allunmountable="$(echo "$mounteddroids +$mounteddrives" | sed "/^$/d;s/ *$//")" +test -n "$allunmountable" + +chosen="$(echo "$allunmountable" | dmenu -i -p "Unmount which drive?")" +chosen="${chosen%% *}" +test -n "$chosen" + +sudo -A umount -l "/${chosen#*/}" +notify-send "Device unmounted." "$chosen has been unmounted." + +# Close the chosen drive if decrypted. +cryptid="$(echo "$lsblkoutput" | grep "/${chosen#*/}$")" +cryptid="${cryptid%% *}" +test -b /dev/mapper/"${cryptid##*/}" +sudo -A cryptsetup close "$cryptid" +notify-send "๐Ÿ”’Device dencryption closed." "Drive is now securely locked again." diff --git a/.local/bin/weath b/.local/bin/weath @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Get the weather on the terminal. You can pass an alternative location as a parameter, +# and/or use the 'cp' option to copy the forecast as plaintext to the clipboard. + +report="${XDG_CACHE_HOME:-$HOME/.cache}/weatherreport" + +if [ "$1" = 'cp' ]; then + # shellcheck disable=SC2015 + [ -z "$2" ] && sed 's/\x1b\[[^m]*m//g' "$report" | xclip -selection clipboard && + notify-send "Weather forecast for '${LOCATION:-$(head -n 1 "$report" | cut -d' ' -f3-)}' copied to clipboard." || + { data="$(curl -sfm 5 "${WTTRURL:-wttr.in}/$2?T")" && + notify-send "Weather forecast for '$2' copied to clipboard." && + echo "$data" | xclip -selection clipboard || + notify-send 'Failed to get weather forecast!' 'Check your internet connection and the supplied location.'; } +else + [ -n "$2" ] && + notify-send "Invalid option '$1'! The only valid option is 'cp'." && + exit 1 + + # shellcheck disable=SC2015 + [ -z "$1" ] && less -S "$report" || + data="$(curl -sfm 5 "${WTTRURL:-wttr.in}/$1")" && echo "$data" | less -S || + notify-send 'Failed to get weather forecast!' 'Check your internet connection and the supplied location.' +fi diff --git a/.local/bin/xdg-terminal-exec b/.local/bin/xdg-terminal-exec @@ -0,0 +1,3 @@ +#!/bin/sh + +"$TERMINAL" -e "$@" diff --git a/.local/share/asuka.jpeg b/.local/share/asuka.jpeg Binary files differ. diff --git a/.local/share/bg b/.local/share/bg @@ -0,0 +1 @@ +/home/lain/.local/share/asuka.jpeg +\ No newline at end of file diff --git a/.local/share/lain.jpeg b/.local/share/lain.jpeg Binary files differ. diff --git a/.local/share/lain.jpg b/.local/share/lain.jpg Binary files differ. diff --git a/.local/share/rena.jpg b/.local/share/rena.jpg Binary files differ. diff --git a/.local/src/dmenu b/.local/src/dmenu @@ -0,0 +1 @@ +Subproject commit c1819f18c07df6984bbfd2ca7207295eec85806f diff --git a/.local/src/dwm b/.local/src/dwm @@ -0,0 +1 @@ +Subproject commit 8282394bdc273a8b573fda7ea1e5a9e490978c6b diff --git a/.local/src/dwmblocks b/.local/src/dwmblocks @@ -0,0 +1 @@ +Subproject commit 1c9744ac7ded4fff8171169bc0b9736f3acd4cfe diff --git a/.local/src/st b/.local/src/st @@ -0,0 +1 @@ +Subproject commit 36d225d71d448bfe307075580f0d8ef81eeb5a87 diff --git a/.local/src/yay b/.local/src/yay @@ -0,0 +1 @@ +Subproject commit 179b744dec6648ff7c7b051c692ba20c906f348e