sync to master
This commit is contained in:
commit
9f30d80e4d
|
@ -1 +1 @@
|
||||||
Subproject commit 8fb1afa21aad5f078797e373ebb64fb506dfe19e
|
Subproject commit 54fe9c5a793a2fe57cb99f4c466fe92aa5208c9e
|
|
@ -7,9 +7,13 @@
|
||||||
!.config/nvim
|
!.config/nvim
|
||||||
!.config/nvim/**
|
!.config/nvim/**
|
||||||
!.config/kitty
|
!.config/kitty
|
||||||
!.config/kitty/*
|
!.config/kitty/**
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!.zsh
|
!.zsh
|
||||||
!.zsh/**
|
!.zsh/**
|
||||||
!.tmux.conf
|
!.tmux.conf
|
||||||
!.gitconfig
|
!.gitconfig
|
||||||
|
!.local/fzf
|
||||||
|
!.local/fzf/**
|
||||||
|
!.local/nvim
|
||||||
|
!.local/nvim/**
|
||||||
|
|
|
@ -0,0 +1,381 @@
|
||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ completion.bash
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX (default: 0)
|
||||||
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
|
_fzf_compgen_path() {
|
||||||
|
echo "$1"
|
||||||
|
command find -L "$1" \
|
||||||
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
|
_fzf_compgen_dir() {
|
||||||
|
command find -L "$1" \
|
||||||
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
|
|
||||||
|
__fzf_comprun() {
|
||||||
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
|
_fzf_comprun "$@"
|
||||||
|
elif [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; }; then
|
||||||
|
shift
|
||||||
|
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_orig_completion() {
|
||||||
|
local l comp f cmd
|
||||||
|
while read -r l; do
|
||||||
|
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
|
||||||
|
comp="${BASH_REMATCH[1]}"
|
||||||
|
f="${BASH_REMATCH[2]}"
|
||||||
|
cmd="${BASH_REMATCH[3]}"
|
||||||
|
[[ "$f" = _fzf_* ]] && continue
|
||||||
|
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
||||||
|
if [[ "$l" = *" -o nospace "* ]] && [[ ! "${__fzf_nospace_commands-}" = *" $cmd "* ]]; then
|
||||||
|
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_opts_completion() {
|
||||||
|
local cur prev opts
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
opts="
|
||||||
|
-x --extended
|
||||||
|
-e --exact
|
||||||
|
--algo
|
||||||
|
-i +i
|
||||||
|
-n --nth
|
||||||
|
--with-nth
|
||||||
|
-d --delimiter
|
||||||
|
+s --no-sort
|
||||||
|
--tac
|
||||||
|
--tiebreak
|
||||||
|
-m --multi
|
||||||
|
--no-mouse
|
||||||
|
--bind
|
||||||
|
--cycle
|
||||||
|
--no-hscroll
|
||||||
|
--jump-labels
|
||||||
|
--height
|
||||||
|
--literal
|
||||||
|
--reverse
|
||||||
|
--margin
|
||||||
|
--inline-info
|
||||||
|
--prompt
|
||||||
|
--pointer
|
||||||
|
--marker
|
||||||
|
--header
|
||||||
|
--header-lines
|
||||||
|
--ansi
|
||||||
|
--tabstop
|
||||||
|
--color
|
||||||
|
--no-bold
|
||||||
|
--history
|
||||||
|
--history-size
|
||||||
|
--preview
|
||||||
|
--preview-window
|
||||||
|
-q --query
|
||||||
|
-1 --select-1
|
||||||
|
-0 --exit-0
|
||||||
|
-f --filter
|
||||||
|
--print-query
|
||||||
|
--expect
|
||||||
|
--sync"
|
||||||
|
|
||||||
|
case "${prev}" in
|
||||||
|
--tiebreak)
|
||||||
|
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--color)
|
||||||
|
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--history)
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$cur" =~ ^-|\+ ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_handle_dynamic_completion() {
|
||||||
|
local cmd orig_var orig ret orig_cmd orig_complete
|
||||||
|
cmd="$1"
|
||||||
|
shift
|
||||||
|
orig_cmd="$1"
|
||||||
|
orig_var="_fzf_orig_completion_$cmd"
|
||||||
|
orig="${!orig_var-}"
|
||||||
|
orig="${orig##*#}"
|
||||||
|
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
||||||
|
$orig "$@"
|
||||||
|
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
||||||
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
|
_completion_loader "$@"
|
||||||
|
ret=$?
|
||||||
|
# _completion_loader may not have updated completion for the command
|
||||||
|
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||||
|
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
|
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
|
||||||
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
|
else
|
||||||
|
eval "$orig_complete"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return $ret
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_generic_path_completion() {
|
||||||
|
local cur base dir leftover matches trigger cmd
|
||||||
|
cmd="${COMP_WORDS[0]}"
|
||||||
|
if [[ $cmd == \\* ]]; then
|
||||||
|
cmd="${cmd:1}"
|
||||||
|
fi
|
||||||
|
cmd="${cmd//[^A-Za-z0-9_=]/_}"
|
||||||
|
COMPREPLY=()
|
||||||
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
|
eval "base=$base"
|
||||||
|
|
||||||
|
dir=
|
||||||
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
|
while true; do
|
||||||
|
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
|
||||||
|
leftover=${base/#"$dir"}
|
||||||
|
leftover=${leftover/#\/}
|
||||||
|
[[ -z "$dir" ]] && dir='.'
|
||||||
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
|
printf "%q " "${item%$3}$3"
|
||||||
|
done)
|
||||||
|
matches=${matches% }
|
||||||
|
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
|
if [[ -n "$matches" ]]; then
|
||||||
|
COMPREPLY=( "$matches" )
|
||||||
|
else
|
||||||
|
COMPREPLY=( "$cur" )
|
||||||
|
fi
|
||||||
|
printf '\e[5n'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir=$(dirname "$dir")
|
||||||
|
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
||||||
|
done
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
_fzf_handle_dynamic_completion "$cmd" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete() {
|
||||||
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in "${!args[@]}"; do
|
||||||
|
if [[ "${args[$i]}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cur selected trigger cmd post
|
||||||
|
post="$(caller 0 | awk '{print $2}')_post"
|
||||||
|
type -t "$post" > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
|
if [[ -n "$selected" ]]; then
|
||||||
|
COMPREPLY=("$selected")
|
||||||
|
else
|
||||||
|
COMPREPLY=("$cur")
|
||||||
|
fi
|
||||||
|
printf '\e[5n'
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_path_completion() {
|
||||||
|
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deprecated. No file only completion.
|
||||||
|
_fzf_file_completion() {
|
||||||
|
_fzf_path_completion "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_dir_completion() {
|
||||||
|
__fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_kill() {
|
||||||
|
_fzf_proc_completion "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_proc_completion() {
|
||||||
|
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
||||||
|
command ps -ef | sed 1d
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_proc_completion_post() {
|
||||||
|
awk '{print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_host_completion() {
|
||||||
|
_fzf_complete +m -- "$@" < <(
|
||||||
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_var_completion() {
|
||||||
|
_fzf_complete -m -- "$@" < <(
|
||||||
|
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_alias_completion() {
|
||||||
|
_fzf_complete -m -- "$@" < <(
|
||||||
|
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# fzf options
|
||||||
|
complete -o default -F _fzf_opts_completion fzf
|
||||||
|
# fzf-tmux is a thin fzf wrapper that has only a few more options than fzf
|
||||||
|
# itself. As a quick improvement we take fzf's completion. Adding the few extra
|
||||||
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||||
|
a_cmds="
|
||||||
|
awk cat diff diff3
|
||||||
|
emacs emacsclient ex file ftp g++ gcc gvim head hg java
|
||||||
|
javac ld less more mvim nvim patch perl python ruby
|
||||||
|
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
||||||
|
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||||
|
find git grep gunzip gzip hg jar
|
||||||
|
ln ls mv open rm rsync scp
|
||||||
|
svn tar unzip zip"
|
||||||
|
|
||||||
|
# Preserve existing completion
|
||||||
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
|
||||||
|
|
||||||
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
|
_fzf_completion_loader=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
__fzf_defc() {
|
||||||
|
local cmd func opts orig_var orig def
|
||||||
|
cmd="$1"
|
||||||
|
func="$2"
|
||||||
|
opts="$3"
|
||||||
|
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||||
|
orig="${!orig_var-}"
|
||||||
|
if [[ -n "$orig" ]]; then
|
||||||
|
printf -v def "$orig" "$func"
|
||||||
|
eval "$def"
|
||||||
|
else
|
||||||
|
complete -F "$func" $opts "$cmd"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Anything
|
||||||
|
for cmd in $a_cmds; do
|
||||||
|
__fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Directory
|
||||||
|
for cmd in $d_cmds; do
|
||||||
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
|
done
|
||||||
|
|
||||||
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
|
_fzf_setup_completion() {
|
||||||
|
local kind fn cmd
|
||||||
|
kind=$1
|
||||||
|
fn=_fzf_${1}_completion
|
||||||
|
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
||||||
|
echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host|proc COMMANDS..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
__fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
|
||||||
|
for cmd in "$@"; do
|
||||||
|
case "$kind" in
|
||||||
|
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||||
|
var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
|
||||||
|
alias) __fzf_defc "$cmd" "$fn" "-a" ;;
|
||||||
|
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Environment variables / Aliases / Hosts / Process
|
||||||
|
_fzf_setup_completion 'var' export unset
|
||||||
|
_fzf_setup_completion 'alias' unalias
|
||||||
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
|
fi
|
|
@ -0,0 +1,324 @@
|
||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ completion.zsh
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX (default: 0)
|
||||||
|
# - $FZF_TMUX_OPTS (default: '-d 40%')
|
||||||
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
# Both branches of the following `if` do the same thing -- define
|
||||||
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
|
# all options to the same values they currently have. We'll do just that at
|
||||||
|
# the bottom of the file after changing options to what we prefer.
|
||||||
|
#
|
||||||
|
# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted
|
||||||
|
# *must* be quoted in order to prevent alias expansion. In addition, code must
|
||||||
|
# be written in a way works with any set of zsh options. This is very tricky, so
|
||||||
|
# careful when you change it.
|
||||||
|
#
|
||||||
|
# Start by loading the builtin zsh/parameter module. It provides `options`
|
||||||
|
# associative array that stores current shell options.
|
||||||
|
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
|
||||||
|
# This is the fast branch and it gets taken on virtually all Zsh installations.
|
||||||
|
#
|
||||||
|
# ${(kv)options[@]} expands to array of keys (option names) and values ("on"
|
||||||
|
# or "off"). The subsequent expansion# with (j: :) flag joins all elements
|
||||||
|
# together separated by spaces. __fzf_completion_options ends up with a value
|
||||||
|
# like this: "options=(shwordsplit off aliases on ...)".
|
||||||
|
__fzf_completion_options="options=(${(j: :)${(kv)options[@]}})"
|
||||||
|
else
|
||||||
|
# This branch is much slower because it forks to get the names of all
|
||||||
|
# zsh options. It's possible to eliminate this fork but it's not worth the
|
||||||
|
# trouble because this branch gets taken only on very ancient or broken
|
||||||
|
# zsh installations.
|
||||||
|
() {
|
||||||
|
# That `()` above defines an anonymous function. This is essentially a scope
|
||||||
|
# for local parameters. We use it to avoid polluting global scope.
|
||||||
|
'local' '__fzf_opt'
|
||||||
|
__fzf_completion_options="setopt"
|
||||||
|
# `set -o` prints one line for every zsh option. Each line contains option
|
||||||
|
# name, some spaces, and then either "on" or "off". We just want option names.
|
||||||
|
# Expansion with (@f) flag splits a string into lines. The outer expansion
|
||||||
|
# removes spaces and everything that follow them on every line. __fzf_opt
|
||||||
|
# ends up iterating over option names: shwordsplit, aliases, etc.
|
||||||
|
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
|
||||||
|
if [[ -o "$__fzf_opt" ]]; then
|
||||||
|
# Option $__fzf_opt is currently on, so remember to set it back on.
|
||||||
|
__fzf_completion_options+=" -o $__fzf_opt"
|
||||||
|
else
|
||||||
|
# Option $__fzf_opt is currently off, so remember to set it back off.
|
||||||
|
__fzf_completion_options+=" +o $__fzf_opt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# The value of __fzf_completion_options here looks like this:
|
||||||
|
# "setopt +o shwordsplit -o aliases ..."
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable the default zsh options (those marked with <Z> in `man zshoptions`)
|
||||||
|
# but without `aliases`. Aliases in functions are expanded when functions are
|
||||||
|
# defined, so if we disable aliases here, we'll be sure to have no pesky
|
||||||
|
# aliases in any of our functions. This way we won't need prefix every
|
||||||
|
# command with `command` or to quote every word to defend against global
|
||||||
|
# aliases. Note that `aliases` is not the only option that's important to
|
||||||
|
# control. There are several others that could wreck havoc if they are set
|
||||||
|
# to values we don't expect. With the following `emulate` command we
|
||||||
|
# sidestep this issue entirely.
|
||||||
|
'emulate' 'zsh' '-o' 'no_aliases'
|
||||||
|
|
||||||
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
|
{
|
||||||
|
|
||||||
|
# Bail out if not interactive shell.
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
|
_fzf_compgen_path() {
|
||||||
|
echo "$1"
|
||||||
|
command find -L "$1" \
|
||||||
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
|
_fzf_compgen_dir() {
|
||||||
|
command find -L "$1" \
|
||||||
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
__fzf_comprun() {
|
||||||
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
|
_fzf_comprun "$@"
|
||||||
|
elif [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; }; then
|
||||||
|
shift
|
||||||
|
if [ -n "${FZF_TMUX_OPTS-}" ]; then
|
||||||
|
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
|
||||||
|
else
|
||||||
|
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
fzf "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
|
||||||
|
__fzf_extract_command() {
|
||||||
|
local token tokens
|
||||||
|
tokens=(${(z)1})
|
||||||
|
for token in $tokens; do
|
||||||
|
token=${(Q)token}
|
||||||
|
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
||||||
|
echo "$token"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "${tokens[1]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_generic_path_completion() {
|
||||||
|
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
|
||||||
|
base=$1
|
||||||
|
lbuf=$2
|
||||||
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
|
compgen=$3
|
||||||
|
fzf_opts=$4
|
||||||
|
suffix=$5
|
||||||
|
tail=$6
|
||||||
|
|
||||||
|
setopt localoptions nonomatch
|
||||||
|
eval "base=$base"
|
||||||
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
|
while [ 1 ]; do
|
||||||
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
|
leftover=${base/#"$dir"}
|
||||||
|
leftover=${leftover/#\/}
|
||||||
|
[ -z "$dir" ] && dir='.'
|
||||||
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
|
item="${item%$suffix}$suffix"
|
||||||
|
echo -n "${(q)item} "
|
||||||
|
done)
|
||||||
|
matches=${matches% }
|
||||||
|
if [ -n "$matches" ]; then
|
||||||
|
LBUFFER="$lbuf$matches$tail"
|
||||||
|
fi
|
||||||
|
zle reset-prompt
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
dir=$(dirname "$dir")
|
||||||
|
dir=${dir%/}/
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_path_completion() {
|
||||||
|
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
|
||||||
|
"-m" "" " "
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_dir_completion() {
|
||||||
|
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
|
||||||
|
"" "/" ""
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_feed_fifo() (
|
||||||
|
command rm -f "$1"
|
||||||
|
mkfifo "$1"
|
||||||
|
cat <&0 > "$1" &
|
||||||
|
)
|
||||||
|
|
||||||
|
_fzf_complete() {
|
||||||
|
setopt localoptions ksh_arrays
|
||||||
|
# Split arguments around --
|
||||||
|
local args rest str_arg i sep
|
||||||
|
args=("$@")
|
||||||
|
sep=
|
||||||
|
for i in {0..${#args[@]}}; do
|
||||||
|
if [[ "${args[$i]-}" = -- ]]; then
|
||||||
|
sep=$i
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -n "$sep" ]]; then
|
||||||
|
str_arg=
|
||||||
|
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
|
||||||
|
args=("${args[@]:0:$sep}")
|
||||||
|
else
|
||||||
|
str_arg=$1
|
||||||
|
args=()
|
||||||
|
shift
|
||||||
|
rest=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fifo lbuf cmd matches post
|
||||||
|
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||||
|
lbuf=${rest[0]}
|
||||||
|
cmd=$(__fzf_extract_command "$lbuf")
|
||||||
|
post="${funcstack[1]}_post"
|
||||||
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
|
_fzf_feed_fifo "$fifo"
|
||||||
|
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
|
if [ -n "$matches" ]; then
|
||||||
|
LBUFFER="$lbuf$matches"
|
||||||
|
fi
|
||||||
|
command rm -f "$fifo"
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_telnet() {
|
||||||
|
_fzf_complete +m -- "$@" < <(
|
||||||
|
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
||||||
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
_fzf_complete +m -- "$@" < <(
|
||||||
|
setopt localoptions nonomatch
|
||||||
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_export() {
|
||||||
|
_fzf_complete -m -- "$@" < <(
|
||||||
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_unset() {
|
||||||
|
_fzf_complete -m -- "$@" < <(
|
||||||
|
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_unalias() {
|
||||||
|
_fzf_complete +m -- "$@" < <(
|
||||||
|
alias | sed 's/=.*//'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_kill() {
|
||||||
|
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
||||||
|
command ps -ef | sed 1d
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_kill_post() {
|
||||||
|
awk '{print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
fzf-completion() {
|
||||||
|
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
||||||
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
|
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
|
||||||
|
tokens=(${(z)LBUFFER})
|
||||||
|
if [ ${#tokens} -lt 1 ]; then
|
||||||
|
zle ${fzf_default_completion:-expand-or-complete}
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmd=$(__fzf_extract_command "$LBUFFER")
|
||||||
|
|
||||||
|
# Explicitly allow for empty trigger.
|
||||||
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
|
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
||||||
|
|
||||||
|
# When the trigger starts with ';', it becomes a separate token
|
||||||
|
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
|
||||||
|
tokens[-2]="${tokens[-2]-}${tokens[-1]}"
|
||||||
|
tokens=(${tokens[0,-2]})
|
||||||
|
fi
|
||||||
|
|
||||||
|
lbuf=$LBUFFER
|
||||||
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
|
|
||||||
|
# Trigger sequence given
|
||||||
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
|
|
||||||
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
|
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
||||||
|
zle reset-prompt
|
||||||
|
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
||||||
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
|
else
|
||||||
|
_fzf_path_completion "$prefix" "$lbuf"
|
||||||
|
fi
|
||||||
|
# Fall back to default completion
|
||||||
|
else
|
||||||
|
zle ${fzf_default_completion:-expand-or-complete}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -z "$fzf_default_completion" ] && {
|
||||||
|
binding=$(bindkey '^I')
|
||||||
|
[[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(s: :w)2]
|
||||||
|
unset binding
|
||||||
|
}
|
||||||
|
|
||||||
|
zle -N fzf-completion
|
||||||
|
bindkey '^I' fzf-completion
|
||||||
|
|
||||||
|
} always {
|
||||||
|
# Restore the original options.
|
||||||
|
eval $__fzf_completion_options
|
||||||
|
'unset' '__fzf_completion_options'
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.bash
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_CTRL_T_COMMAND
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
# Key bindings
|
||||||
|
# ------------
|
||||||
|
__fzf_select__() {
|
||||||
|
local cmd opts
|
||||||
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
|
-o -type f -print \
|
||||||
|
-o -type d -print \
|
||||||
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
|
eval "$cmd" |
|
||||||
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
|
while read -r item; do
|
||||||
|
printf '%q ' "$item" # escape special chars
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
__fzfcmd() {
|
||||||
|
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
||||||
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
|
}
|
||||||
|
|
||||||
|
fzf-file-widget() {
|
||||||
|
local selected="$(__fzf_select__ "$@")"
|
||||||
|
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
||||||
|
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_cd__() {
|
||||||
|
local cmd opts dir
|
||||||
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||||
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_history__() {
|
||||||
|
local output opts script
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
|
output=$(
|
||||||
|
builtin fc -lnr -2147483648 |
|
||||||
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
||||||
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
|
) || return
|
||||||
|
READLINE_LINE=${output#*$'\t'}
|
||||||
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
echo "$READLINE_LINE"
|
||||||
|
else
|
||||||
|
READLINE_POINT=0x7fffffff
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Required to refresh the prompt after fzf
|
||||||
|
bind -m emacs-standard '"\er": redraw-current-line'
|
||||||
|
|
||||||
|
bind -m vi-command '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m vi-insert '"\C-z": emacs-editing-mode'
|
||||||
|
bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||||
|
|
||||||
|
if (( BASH_VERSINFO[0] < 4 )); then
|
||||||
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
|
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|
||||||
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
|
||||||
|
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
||||||
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
|
else
|
||||||
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
|
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||||
|
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||||
|
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||||
|
|
||||||
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
|
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||||
|
bind -m vi-command -x '"\C-r": __fzf_history__'
|
||||||
|
bind -m vi-insert -x '"\C-r": __fzf_history__'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ALT-C - cd into the selected directory
|
||||||
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
|
||||||
|
fi
|
|
@ -0,0 +1,121 @@
|
||||||
|
# ____ ____
|
||||||
|
# / __/___ / __/
|
||||||
|
# / /_/_ / / /_
|
||||||
|
# / __/ / /_/ __/
|
||||||
|
# /_/ /___/_/ key-bindings.zsh
|
||||||
|
#
|
||||||
|
# - $FZF_TMUX_OPTS
|
||||||
|
# - $FZF_CTRL_T_COMMAND
|
||||||
|
# - $FZF_CTRL_T_OPTS
|
||||||
|
# - $FZF_CTRL_R_OPTS
|
||||||
|
# - $FZF_ALT_C_COMMAND
|
||||||
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
# Key bindings
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# The code at the top and the bottom of this file is the same as in completion.zsh.
|
||||||
|
# Refer to that file for explanation.
|
||||||
|
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
|
||||||
|
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
|
||||||
|
else
|
||||||
|
() {
|
||||||
|
__fzf_key_bindings_options="setopt"
|
||||||
|
'local' '__fzf_opt'
|
||||||
|
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
|
||||||
|
if [[ -o "$__fzf_opt" ]]; then
|
||||||
|
__fzf_key_bindings_options+=" -o $__fzf_opt"
|
||||||
|
else
|
||||||
|
__fzf_key_bindings_options+=" +o $__fzf_opt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
'emulate' 'zsh' '-o' 'no_aliases'
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
|
__fsel() {
|
||||||
|
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
|
-o -type f -print \
|
||||||
|
-o -type d -print \
|
||||||
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
|
local item
|
||||||
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
|
echo -n "${(q)item} "
|
||||||
|
done
|
||||||
|
local ret=$?
|
||||||
|
echo
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzfcmd() {
|
||||||
|
[ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } &&
|
||||||
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
|
}
|
||||||
|
|
||||||
|
fzf-file-widget() {
|
||||||
|
LBUFFER="${LBUFFER}$(__fsel)"
|
||||||
|
local ret=$?
|
||||||
|
zle reset-prompt
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
zle -N fzf-file-widget
|
||||||
|
bindkey -M emacs '^T' fzf-file-widget
|
||||||
|
bindkey -M vicmd '^T' fzf-file-widget
|
||||||
|
bindkey -M viins '^T' fzf-file-widget
|
||||||
|
|
||||||
|
# ALT-C - cd into the selected directory
|
||||||
|
fzf-cd-widget() {
|
||||||
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
||||||
|
if [[ -z "$dir" ]]; then
|
||||||
|
zle redisplay
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
|
BUFFER="builtin cd -- ${(q)dir}"
|
||||||
|
zle accept-line
|
||||||
|
local ret=$?
|
||||||
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
|
zle reset-prompt
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
zle -N fzf-cd-widget
|
||||||
|
bindkey -M emacs '\ec' fzf-cd-widget
|
||||||
|
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||||
|
bindkey -M viins '\ec' fzf-cd-widget
|
||||||
|
|
||||||
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
|
# EDIT BY plex: removed --scheme-history , as it causes errors on some debian based systems as it seems.
|
||||||
|
fzf-history-widget() {
|
||||||
|
local selected num
|
||||||
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
|
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
|
local ret=$?
|
||||||
|
if [ -n "$selected" ]; then
|
||||||
|
num=$selected[1]
|
||||||
|
if [ -n "$num" ]; then
|
||||||
|
zle vi-fetch-history -n $num
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
zle reset-prompt
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
zle -N fzf-history-widget
|
||||||
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
|
||||||
|
} always {
|
||||||
|
eval $__fzf_key_bindings_options
|
||||||
|
'unset' '__fzf_key_bindings_options'
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Copy selection to system clipboard as newline-separated entries
|
||||||
|
# Dependencies:
|
||||||
|
# - tr
|
||||||
|
# - xclip/xsel (Linux)
|
||||||
|
# - pbcopy (macOS)
|
||||||
|
# - termux-clipboard-set (Termux)
|
||||||
|
# - clip.exe (WSL)
|
||||||
|
# - clip (Cygwin)
|
||||||
|
# - wl-copy (Wayland)
|
||||||
|
# - clipboard (Haiku)
|
||||||
|
#
|
||||||
|
# Limitation: breaks if a filename has newline in it
|
||||||
|
#
|
||||||
|
# Note: For a space-separated list:
|
||||||
|
# xargs -0 < "$SELECTION"
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
[ -s "$selection" ] || { echo "plugin .cbcp error: empty selection" >&2 ; exit 1; }
|
||||||
|
|
||||||
|
if type xsel >/dev/null 2>&1; then
|
||||||
|
# Linux
|
||||||
|
tr '\0' '\n' < "$selection" | xsel -bi
|
||||||
|
elif type xclip >/dev/null 2>&1; then
|
||||||
|
# Linux
|
||||||
|
tr '\0' '\n' < "$selection" | xclip -sel clip
|
||||||
|
elif type pbcopy >/dev/null 2>&1; then
|
||||||
|
# macOS
|
||||||
|
tr '\0' '\n' < "$selection" | pbcopy
|
||||||
|
elif type termux-clipboard-set >/dev/null 2>&1; then
|
||||||
|
# Termux
|
||||||
|
tr '\0' '\n' < "$selection" | termux-clipboard-set
|
||||||
|
elif type clip.exe >/dev/null 2>&1; then
|
||||||
|
# WSL
|
||||||
|
tr '\0' '\n' < "$selection" | clip.exe
|
||||||
|
elif type clip >/dev/null 2>&1; then
|
||||||
|
# Cygwin
|
||||||
|
tr '\0' '\n' < "$selection" | clip
|
||||||
|
elif type wl-copy >/dev/null 2>&1; then
|
||||||
|
# Wayland
|
||||||
|
tr '\0' '\n' < "$selection" | wl-copy
|
||||||
|
elif type clipboard >/dev/null 2>&1; then
|
||||||
|
# Haiku
|
||||||
|
tr '\0' '\n' < "$selection" | clipboard --stdin
|
||||||
|
fi
|
|
@ -0,0 +1,428 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Print icons in front of list of directories/files
|
||||||
|
|
||||||
|
# Dependencies: awk
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
# 1. Set colors and/or icons to your liking
|
||||||
|
# 2. Pipe any directory listing to iconlookup and it will output prepended icons
|
||||||
|
# 3. preview-tui uses the script to prepend icon to directory listings
|
||||||
|
# 4. Aditionally you can consider adding it to your PATH and/or FZF_DEFAULT_COMMAND to
|
||||||
|
# make it work with various fzf plugins (make sure you also add --ansi to your FZF_DEFAULT_OPTS)
|
||||||
|
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
|
||||||
|
# Author: Luuk van Baal (https://github.com/luukvbaal/iconlookup)
|
||||||
|
|
||||||
|
icon_lookup() {
|
||||||
|
awk 'BEGIN {
|
||||||
|
# Set your ANSI colorscheme below (https://en.wikipedia.org/wiki/ANSI_escape_code#Colors).
|
||||||
|
# Default uses standard nnn icon colors, 8 and 24-bit nord themes are commented out.
|
||||||
|
colordepth=8 #colordepth=8 #colordepth=24
|
||||||
|
color_dirtxt=39 #color_dirtxt=111 #color_dirtxt="129;161;193"
|
||||||
|
color_filetxt=15 #color_filetxt=111 #color_filetxt="129;161;193"
|
||||||
|
color_default=39 #color_default=111 #color_default="129;161;193"
|
||||||
|
color_video=93 #color_video=110 #color_video="136;192;208"
|
||||||
|
color_audio=220 #color_audio=150 #color_audio="163;190;140"
|
||||||
|
color_image=82 #color_image=150 #color_image="163;190;140"
|
||||||
|
color_docs=202 #color_docs=173 #color_docs="208;135;112"
|
||||||
|
color_archive=209 #color_archive=179 #color_archive="235;203;139"
|
||||||
|
color_c=81 #color_c=150 #color_c="163;190;140"
|
||||||
|
color_java=32 #color_java=139 #color_java="180;142;173"
|
||||||
|
color_js=47 #color_js=109 #color_js="143;188;187"
|
||||||
|
color_react=39 #color_react=111 #color_react="129;161;193"
|
||||||
|
color_css=199 #color_css=110 #color_css="136;192;208"
|
||||||
|
color_python=227 #color_python=68 #color_python="94;129;172"
|
||||||
|
color_lua=19 #color_lua=167 #color_lua="191;97;106"
|
||||||
|
color_document=15 #color_document=173 #color_document="208;135;112"
|
||||||
|
color_fsharp=31 #color_fsharp=179 #color_fsharp="180;142;173"
|
||||||
|
color_ruby=160 #color_ruby=150 #color_ruby="163;190;140"
|
||||||
|
color_scala=196 #color_scala=139 #color_scala="143;188;187"
|
||||||
|
color_shell=47 #color_shell=109 #color_shell="143;188;187"
|
||||||
|
color_vim=28 #color_vim=109 #color_vim="143;188;187"
|
||||||
|
|
||||||
|
# icons[][1] contains icon and icons[][2] contains color
|
||||||
|
icons["directory"][1] = ""; icons["directory"][2] = color_default
|
||||||
|
icons["file"][1] = ""; icons["file"][2] = color_default
|
||||||
|
icons["exec"][1] = ""; icons["exec"][2] = color_default
|
||||||
|
icons["manual"][1] = ""; icons["manual"][2] = color_docs
|
||||||
|
icons["pipe"][1] = "ﳣ"; icons["pipe"][2] = color_default
|
||||||
|
icons["socket"][1] = "ﳧ"; icons["socket"][2] = color_default
|
||||||
|
icons["door"][1] = "➡"; icons["door"][2] = color_default
|
||||||
|
|
||||||
|
# top level and common icons
|
||||||
|
icons[".git"][1] = ""; icons[".git"][2] = color_default
|
||||||
|
icons["desktop"][1] = "ﲾ"; icons["desktop"][2] = color_default
|
||||||
|
icons["briefcase"][1] = ""; icons["briefcase"][2] = color_default
|
||||||
|
icons["document"][1] = ""; icons["document"][2] = color_default
|
||||||
|
icons["downloads"][1] = ""; icons["downloads"][2] = color_default
|
||||||
|
icons["music"][1] = ""; icons["music"][2] = color_default
|
||||||
|
icons["musicfile"][1] = ""; icons["musicfile"][2] = color_audio
|
||||||
|
icons["pictures"][1] = ""; icons["pictures"][2] = color_default
|
||||||
|
icons["picturefile"][1] = ""; icons["picturefile"][2] = color_image
|
||||||
|
icons["public"][1] = ""; icons["public"][2] = color_default
|
||||||
|
icons["templates"][1] = "陼"; icons["templates"][2] = color_default
|
||||||
|
icons["videos"][1] = ""; icons["videos"][2] = color_default
|
||||||
|
icons["videofile"][1] = "ﳜ"; icons["videofile"][2] = color_video
|
||||||
|
icons["changelog"][1] = ""; icons["changelog"][2] = color_docs
|
||||||
|
icons["configure"][1] = ""; icons["configure"][2] = color_default
|
||||||
|
icons["license"][1] = ""; icons["license"][2] = color_docs
|
||||||
|
icons["makefile"][1] = ""; icons["makefile"][2] = color_default
|
||||||
|
icons["archive"][1] = ""; icons["archive"][2] = color_archive
|
||||||
|
icons["script"][1] = ""; icons["script"][2] = color_shell
|
||||||
|
icons["cplusplus"][1] = ""; icons["cplusplus"][2] = color_c
|
||||||
|
icons["java"][1] = ""; icons["java"][2] = color_java
|
||||||
|
icons["clojure"][1] = ""; icons["clojure"][2] = color_default
|
||||||
|
icons["js"][1] = ""; icons["js"][2] = color_js
|
||||||
|
icons["linux"][1] = ""; icons["linux"][2] = color_default
|
||||||
|
icons["fsharp"][1] = ""; icons["fsharp"][2] = color_fsharp
|
||||||
|
icons["ruby"][1] = ""; icons["ruby"][2] = color_ruby
|
||||||
|
icons["c"][1] = ""; icons["c"][2] = color_c
|
||||||
|
icons["chess"][1] = ""; icons["chess"][2] = color_default
|
||||||
|
icons["haskell"][1] = ""; icons["haskell"][2] = color_vim
|
||||||
|
icons["html"][1] = ""; icons["html"][2] = color_default
|
||||||
|
icons["react"][1] = ""; icons["react"][2] = color_react
|
||||||
|
icons["python"][1] = ""; icons["python"][2] = color_python
|
||||||
|
icons["database"][1] = ""; icons["database"][2] = color_default
|
||||||
|
icons["worddoc"][1] = ""; icons["worddoc"][2] = color_document
|
||||||
|
icons["playlist"][1] = "蘿"; icons["playlist"][2] = color_audio
|
||||||
|
icons["opticaldisk"][1] = ""; icons["opticaldisk"][2] = color_archive
|
||||||
|
|
||||||
|
# numbers
|
||||||
|
icons["1"][1] = icons["manual"][1]; icons["1"][2] = icons["manual"][2]
|
||||||
|
icons["7z"][1] = icons["archive"][1]; icons["7z"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# a
|
||||||
|
icons["a"][1] = icons["manual"][1]; icons["a"][2] = icons["manual"][2]
|
||||||
|
icons["apk"][1] = icons["archive"][1]; icons["apk"][2] = icons["archive"][2]
|
||||||
|
icons["asm"][1] = icons["file"][1]; icons["asm"][2] = icons["file"][2]
|
||||||
|
icons["aup"][1] = icons["musicfile"][1]; icons["aup"][2] = icons["musicfile"][2]
|
||||||
|
icons["avi"][1] = icons["videofile"][1]; icons["avi"][2] = icons["videofile"][2]
|
||||||
|
|
||||||
|
# b
|
||||||
|
icons["bat"][1] = icons["script"][1]; icons["bat"][2] = icons["script"][2]
|
||||||
|
icons["bin"][1] = ""; icons["bin"][2] = color_default
|
||||||
|
icons["bmp"][1] = icons["picturefile"][1]; icons["bmp"][2] = icons["picturefile"][2]
|
||||||
|
icons["bz2"][1] = icons["archive"][1]; icons["bz2"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# c
|
||||||
|
icons["cplusplus"][1] = icons["cplusplus"][1]; icons["cplusplus"][2] = icons["cplusplus"][2]
|
||||||
|
icons["cabal"][1] = icons["haskell"][1]; icons["cab"][2] = icons["haskell"][2]
|
||||||
|
icons["cab"][1] = icons["archive"][1]; icons["cab"][2] = icons["archive"][2]
|
||||||
|
icons["cbr"][1] = icons["archive"][1]; icons["cbr"][2] = icons["archive"][2]
|
||||||
|
icons["cbz"][1] = icons["archive"][1]; icons["cbz"][2] = icons["archive"][2]
|
||||||
|
icons["cc"][1] = icons["cplusplus"][1]; icons["cc"][2] = icons["cplusplus"][2]
|
||||||
|
icons["class"][1] = icons["java"][1]; icons["class"][2] = icons["java"][2]
|
||||||
|
icons["clj"][1] = icons["clojure"][1]; icons["clj"][2] = icons["clojure"][2]
|
||||||
|
icons["cljc"][1] = icons["clojure"][1]; icons["cljc"][2] = icons["clojure"][2]
|
||||||
|
icons["cljs"][1] = icons["clojure"][1]; icons["cljs"][2] = icons["clojure"][2]
|
||||||
|
icons["cmake"][1] = icons["makefile"][1]; icons["cmake"][2] = icons["makefile"][2]
|
||||||
|
icons["coffee"][1] = ""; icons["coffee"][2] = color_default
|
||||||
|
icons["conf"][1] = icons["configure"][1]; icons["conf"][2] = icons["configure"][2]
|
||||||
|
icons["cpio"][1] = icons["archive"][1]; icons["cpio"][2] = icons["archive"][2]
|
||||||
|
icons["cpp"][1] = icons["cplusplus"][1]; icons["cpp"][2] = icons["cplusplus"][2]
|
||||||
|
icons["css"][1] = ""; icons["css"][2] = color_css
|
||||||
|
icons["cue"][1] = icons["playlist"][1]; icons["cue"][2] = icons["playlist"][2]
|
||||||
|
icons["cvs"][1] = icons["configure"][1]; icons["cvs"][2] = icons["configure"][2]
|
||||||
|
icons["cxx"][1] = icons["cplusplus"][1]; icons["cxx"][2] = icons["cplusplus"][2]
|
||||||
|
|
||||||
|
# d
|
||||||
|
icons["db"][1] = icons["database"][1]; icons["db"][2] = icons["database"][2]
|
||||||
|
icons["deb"][1] = ""; icons["deb"][2] = color_archive
|
||||||
|
icons["diff"][1] = ""; icons["diff"][2] = color_default
|
||||||
|
icons["dll"][1] = icons["script"][1]; icons["dll"][2] = icons["script"][2]
|
||||||
|
icons["doc"][1] = icons["worddoc"][1]; icons["doc"][2] = icons["worddoc"][2]
|
||||||
|
icons["docx"][1] = icons["worddoc"][1]; icons["docx"][2] = icons["worddoc"][2]
|
||||||
|
|
||||||
|
# e
|
||||||
|
icons["ejs"][1] = icons["js"][1]; icons["ejs"][2] = icons["js"][2]
|
||||||
|
icons["elf"][1] = icons["linux"][1]; icons["elf"][2] = icons["linux"][2]
|
||||||
|
icons["epub"][1] = icons["manual"][1]; icons["epub"][2] = icons["manual"][2]
|
||||||
|
icons["exe"][1] = icons["exec"][1]; icons["exe"][2] = icons["exec"][2]
|
||||||
|
|
||||||
|
# f
|
||||||
|
icons["fsharp"][1] = icons["fsharp"][1]; icons["fsharp"][2] = icons["fsharp"][2]
|
||||||
|
icons["flac"][1] = icons["musicfile"][1]; icons["flac"][2] = icons["musicfile"][2]
|
||||||
|
icons["fen"][1] = icons["chess"][1]; icons["fen"][2] = icons["chess"][2]
|
||||||
|
icons["flv"][1] = icons["videofile"][1]; icons["flv"][2] = icons["videofile"][2]
|
||||||
|
icons["fs"][1] = icons["fsharp"][1]; icons["fs"][2] = icons["fsharp"][2]
|
||||||
|
icons["fsi"][1] = icons["fsharp"][1]; icons["fsi"][2] = icons["fsharp"][2]
|
||||||
|
icons["fsscript"][1] = icons["fsharp"][1]; icons["fsscript"][2] = icons["fsharp"][2]
|
||||||
|
icons["fsx"][1] = icons["fsharp"][1]; icons["fsx"][2] = icons["fsharp"][2]
|
||||||
|
|
||||||
|
# g
|
||||||
|
icons["gem"][1] = icons["ruby"][1]; icons["gem"][2] = icons["ruby"][2]
|
||||||
|
icons["gif"][1] = icons["picturefile"][1]; icons["gif"][2] = icons["picturefile"][2]
|
||||||
|
icons["go"][1] = "ﳑ"; icons["go"][2] = color_default
|
||||||
|
icons["gz"][1] = icons["archive"][1]; icons["gz"][2] = icons["archive"][2]
|
||||||
|
icons["gzip"][1] = icons["archive"][1]; icons["gzip"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# h
|
||||||
|
icons["h"][1] = icons["c"][1]; icons["h"][2] = icons["c"][2]
|
||||||
|
icons["hh"][1] = icons["cplusplus"][1]; icons["hh"][2] = icons["cplusplus"][2]
|
||||||
|
icons["hpp"][1] = icons["cplusplus"][1]; icons["hpp"][2] = icons["cplusplus"][2]
|
||||||
|
icons["hs"][1] = icons["haskell"][1]; icons["hs"][2] = icons["haskell"][2]
|
||||||
|
icons["htaccess"][1] = icons["configure"][1]; icons["htaccess"][2] = icons["configure"][2]
|
||||||
|
icons["htpasswd"][1] = icons["configure"][1]; icons["htpasswd"][2] = icons["configure"][2]
|
||||||
|
icons["htm"][1] = icons["html"][1]; icons["htm"][2] = icons["html"][2]
|
||||||
|
icons["hxx"][1] = icons["cplusplus"][1]; icons["hxx"][2] = icons["cplusplus"][2]
|
||||||
|
|
||||||
|
# i
|
||||||
|
icons["ico"][1] = icons["picturefile"][1]; icons["ico"][2] = icons["picturefile"][2]
|
||||||
|
icons["img"][1] = icons["opticaldisk"][1]; icons["img"][2] = icons["opticaldisk"][2]
|
||||||
|
icons["ini"][1] = icons["configure"][1]; icons["ini"][2] = icons["configure"][2]
|
||||||
|
icons["iso"][1] = icons["opticaldisk"][1]; icons["iso"][2] = icons["opticaldisk"][2]
|
||||||
|
|
||||||
|
# j
|
||||||
|
icons["jar"][1] = icons["java"][1]; icons["jar"][2] = icons["java"][2]
|
||||||
|
icons["java"][1] = icons["java"][1]; icons["java"][2] = icons["java"][2]
|
||||||
|
icons["jl"][1] = icons["configure"][1]; icons["jl"][2] = icons["configure"][2]
|
||||||
|
icons["jpeg"][1] = icons["picturefile"][1]; icons["jpeg"][2] = icons["picturefile"][2]
|
||||||
|
icons["jpg"][1] = icons["picturefile"][1]; icons["jpg"][2] = icons["picturefile"][2]
|
||||||
|
icons["json"][1] = "ﬥ"; icons["json"][2] = color_js
|
||||||
|
icons["jsx"][1] = icons["react"][1]; icons["jsx"][2] = icons["react"][2]
|
||||||
|
|
||||||
|
# k
|
||||||
|
|
||||||
|
# l
|
||||||
|
icons["lha"][1] = icons["archive"][1]; icons["lha"][2] = icons["archive"][2]
|
||||||
|
icons["lhs"][1] = icons["haskell"][1]; icons["lhs"][2] = icons["haskell"][2]
|
||||||
|
icons["ilog"][1] = icons["document"][1]; icons["ilog"][2] = icons["document"][2]
|
||||||
|
icons["lua"][1] = ""; icons["lua"][2] = color_lua
|
||||||
|
icons["lzh"][1] = icons["archive"][1]; icons["lzh"][2] = icons["archive"][2]
|
||||||
|
icons["lzma"][1] = icons["archive"][1]; icons["lzma"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# m
|
||||||
|
icons["m"][1] = "ﴜ"; icons["mat"][2] = color_c
|
||||||
|
icons["m4a"][1] = icons["musicfile"][1]; icons["m4a"][2] = icons["musicfile"][2]
|
||||||
|
icons["m4v"][1] = icons["videofile"][1]; icons["m4v"][2] = icons["videofile"][2]
|
||||||
|
icons["mat"][1] = ""; icons["mat"][2] = color_c
|
||||||
|
icons["markdown"][1] = ""; icons["markdown"][2] = color_docs
|
||||||
|
icons["md"][1] = ""; icons["md"][2] = color_docs
|
||||||
|
icons["mk"][1] = icons["makefile"][1]; icons["mk"][2] = icons["makefile"][2]
|
||||||
|
icons["mkv"][1] = icons["videofile"][1]; icons["mkv"][2] = icons["videofile"][2]
|
||||||
|
icons["mov"][1] = icons["videofile"][1]; icons["mov"][2] = icons["videofile"][2]
|
||||||
|
icons["mp3"][1] = icons["musicfile"][1]; icons["mp3"][2] = icons["musicfile"][2]
|
||||||
|
icons["mp4"][1] = icons["videofile"][1]; icons["mp4"][2] = icons["videofile"][2]
|
||||||
|
icons["mpeg"][1] = icons["videofile"][1]; icons["mpeg"][2] = icons["videofile"][2]
|
||||||
|
icons["mpg"][1] = icons["videofile"][1]; icons["mpg"][2] = icons["videofile"][2]
|
||||||
|
icons["msi"][1] = ""; icons["msi"][2] = color_default
|
||||||
|
|
||||||
|
# n
|
||||||
|
icons["nix"][1] = ""; icons["nix"][2] = color_fsharp
|
||||||
|
|
||||||
|
# o
|
||||||
|
icons["o"][1] = icons["manual"][1]; icons["o"][2] = icons["manual"][2]
|
||||||
|
icons["ogg"][1] = icons["musicfile"][1]; icons["ogg"][2] = icons["musicfile"][2]
|
||||||
|
icons["odownload"][1] = icons["download"][1]; icons["odownload"][2] = icons["download"][2]
|
||||||
|
icons["out"][1] = icons["linux"][1]; icons["out"][2] = icons["linux"][2]
|
||||||
|
|
||||||
|
# p
|
||||||
|
icons["part"][1] = icons["download"][1]; icons["part"][2] = icons["download"][2]
|
||||||
|
icons["patch"][1] = icons["diff"][1]; icons["patch"][2] = icons["diff"][2]
|
||||||
|
icons["pdf"][1] = ""; icons["pdf"][2] = color_docs
|
||||||
|
icons["pgn"][1] = icons["chess"][1]; icons["pgn"][2] = icons["chess"][2]
|
||||||
|
icons["php"][1] = ""; icons["php"][2] = color_default
|
||||||
|
icons["png"][1] = icons["picturefile"][1]; icons["png"][2] = icons["picturefile"][2]
|
||||||
|
icons["ppt"][1] = ""; icons["ppt"][2] = color_default
|
||||||
|
icons["pptx"][1] = ""; icons["pptx"][2] = color_default
|
||||||
|
icons["psb"][1] = ""; icons["psb"][2] = color_default
|
||||||
|
icons["psd"][1] = ""; icons["psd"][2] = color_default
|
||||||
|
icons["py"][1] = icons["python"][1]; icons["py"][2] = icons["python"][2]
|
||||||
|
icons["pyc"][1] = icons["python"][1]; icons["pyc"][2] = icons["python"][2]
|
||||||
|
icons["pyd"][1] = icons["python"][1]; icons["pyd"][2] = icons["python"][2]
|
||||||
|
icons["pyo"][1] = icons["python"][1]; icons["pyo"][2] = icons["python"][2]
|
||||||
|
|
||||||
|
# q
|
||||||
|
|
||||||
|
# r
|
||||||
|
icons["rar"][1] = icons["archive"][1]; icons["rar"][2] = icons["archive"][2]
|
||||||
|
icons["rc"][1] = icons["configure"][1]; icons["rc"][2] = icons["configure"][2]
|
||||||
|
icons["rom"][1] = ""; icons["rom"][2] = color_default
|
||||||
|
icons["rpm"][1] = icons["archive"][1]; icons["rpm"][2] = icons["archive"][2]
|
||||||
|
icons["rss"][1] = "參"; icons["rss"][2] = color_default
|
||||||
|
icons["rtf"][1] = ""; icons["rtf"][2] = color_default
|
||||||
|
|
||||||
|
# s
|
||||||
|
icons["sass"][1] = ""; icons["sass"][2] = color_css
|
||||||
|
icons["scss"][1] = ""; icons["scss"][2] = color_css
|
||||||
|
icons["so"][1] = icons["manual"][1]; icons["so"][2] = icons["manual"][2]
|
||||||
|
icons["scala"][1] = ""; icons["scala"][2] = color_scala
|
||||||
|
icons["sh"][1] = icons["script"][1]; icons["sh"][2] = icons["script"][2]
|
||||||
|
icons["slim"][1] = icons["script"][1]; icons["slim"][2] = icons["script"][2]
|
||||||
|
icons["sln"][1] = ""; icons["sln"][2] = color_default
|
||||||
|
icons["sql"][1] = icons["database"][1]; icons["sql"][2] = icons["database"][2]
|
||||||
|
icons["srt"][1] = ""; icons["srt"][2] = color_default
|
||||||
|
icons["isub"][1] = ""; icons["isub"][2] = color_default
|
||||||
|
icons["svg"][1] = icons["picturefile"][1]; icons["svg"][2] = icons["picturefile"][2]
|
||||||
|
|
||||||
|
# t
|
||||||
|
icons["tar"][1] = icons["archive"][1]; icons["tar"][2] = icons["archive"][2]
|
||||||
|
icons["tex"][1] = ""; icons["tex"][2] = color_default
|
||||||
|
icons["tgz"][1] = icons["archive"][1]; icons["tgz"][2] = icons["archive"][2]
|
||||||
|
icons["ts"][1] = ""; icons["ts"][2] = color_js
|
||||||
|
icons["tsx"][1] = icons["react"][1]; icons["tsx"][2] = icons["react"][2]
|
||||||
|
icons["txt"][1] = icons["document"][1]; icons["txt"][2] = icons["document"][2]
|
||||||
|
icons["txz"][1] = icons["archive"][1]; icons["txz"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# u
|
||||||
|
|
||||||
|
# v
|
||||||
|
icons["vid"][1] = icons["videofile"][1]; icons["vid"][2] = icons["videofile"][2]
|
||||||
|
icons["vim"][1] = ""; icons["vim"][2] = color_vim
|
||||||
|
icons["vimrc"][1] = ""; icons["vimrc"][2] = color_vim
|
||||||
|
icons["vtt"][1] = ""; icons["vtt"][2] = color_default
|
||||||
|
# w
|
||||||
|
icons["wav"][1] = icons["musicfile"][1]; icons["wav"][2] = icons["musicfile"][2]
|
||||||
|
icons["webm"][1] = icons["videofile"][1]; icons["webm"][2] = icons["videofile"][2]
|
||||||
|
icons["wma"][1] = icons["videofile"][1]; icons["wma"][2] = icons["videofile"][2]
|
||||||
|
icons["wmv"][1] = icons["videofile"][1]; icons["wmv"][2] = icons["videofile"][2]
|
||||||
|
|
||||||
|
# x
|
||||||
|
icons["xbps"][1] = icons["archive"][1]; icons["xbps"][2] = color_archive
|
||||||
|
icons["xcf"][1] = icons["picturefile"][1]; icons["xcf"][2] = color_image
|
||||||
|
icons["xhtml"][1] = icons["html"][1]; icons["xhtml"][2] = icons["html"][2]
|
||||||
|
icons["xls"][1] = ""; icons["xls"][2] = color_default
|
||||||
|
icons["xlsx"][1] = ""; icons["xlsx"][2] = color_default
|
||||||
|
icons["xml"][1] = icons["html"][1]; icons["xml"][2] = icons["html"][2]
|
||||||
|
icons["xz"][1] = icons["archive"][1]; icons["xz"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
# y
|
||||||
|
icons["yaml"][1] = icons["configure"][1]; icons["yaml"][2] = icons["configure"][2]
|
||||||
|
icons["yml"][1] = icons["configure"][1]; icons["yml"][2] = icons["configure"][2]
|
||||||
|
# z
|
||||||
|
icons["zip"][1] = icons["archive"][1]; icons["zip"][2] = icons["archive"][2]
|
||||||
|
icons["zsh"][1] = icons["script"][1]; icons["zsh"][2] = icons["script"][2]
|
||||||
|
icons["zst"][1] = icons["archive"][1]; icons["zst"][2] = icons["archive"][2]
|
||||||
|
|
||||||
|
FS = "."
|
||||||
|
limit = ENVIRON["limit"]
|
||||||
|
switch (colordepth) {
|
||||||
|
case "4":
|
||||||
|
escape="\033["
|
||||||
|
break;
|
||||||
|
case "8":
|
||||||
|
escape="\033[38;5;"
|
||||||
|
break;
|
||||||
|
case "24":
|
||||||
|
escape="\033[38;2;"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bstr = ENVIRON["beforestr"]
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# dont print cwd . and leading ./ from tree -f
|
||||||
|
if ($0 ~/^\.$/)
|
||||||
|
next
|
||||||
|
ent = ($0 ~/^\.\//) ? substr($0, 3, length($0) - 2) : $0
|
||||||
|
ext = $NF
|
||||||
|
|
||||||
|
# Print icons, set color and bold directories by using ansi escape codes
|
||||||
|
if (ext in icons)
|
||||||
|
printcolor(icons[ext][1], icons[ext][2], color_filetxt, ent, "10")
|
||||||
|
else
|
||||||
|
switch (substr(ent, length(ent), 1)) {
|
||||||
|
case "/":
|
||||||
|
printcolor(icons["directory"][1], color_default, color_dirtxt, ent, "1")
|
||||||
|
break;
|
||||||
|
case "*":
|
||||||
|
printcolor(icons["exe"][1], color_default, color_filetxt, ent, "10")
|
||||||
|
break;
|
||||||
|
case "|":
|
||||||
|
printcolor(icons["pipe"][1], color_default, color_filetxt, ent, "10")
|
||||||
|
break;
|
||||||
|
case "=":
|
||||||
|
printcolor(icons["socket"][1], color_default, color_filetxt, ent, "10")
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
printcolor(icons["door"][1], color_default, color_filetxt, ent, "10")
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printcolor(icons["file"][1], color_default, color_filetxt, ent, "10")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function printcolor(i, c, d, n, b) {
|
||||||
|
if (limit != "" && length(n) + 2 > limit)
|
||||||
|
n = substr(n, 1, limit - 2)
|
||||||
|
printf "\033[0m"
|
||||||
|
printf "%s%s%s;%sm%s %s%sm%s\n", bstr, escape, c, b, i, escape, d, n
|
||||||
|
}'
|
||||||
|
printf '\033[0m'
|
||||||
|
}
|
||||||
|
|
||||||
|
print_begin() {
|
||||||
|
printf '%s\n' "$1" | sed 's/\\n/\n/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
print_end() {
|
||||||
|
printf '%s\n' "$1" | sed 's/\\n/\n/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
print_help() {
|
||||||
|
printf 'Icon Lookup\n
|
||||||
|
Usage:
|
||||||
|
iconlookup [options]
|
||||||
|
iconlookup [-bBe] [string]
|
||||||
|
iconlookup -l [number]
|
||||||
|
iconlookup (-h | --help)
|
||||||
|
|
||||||
|
Prepend icons to list of files based on extension or appended indicator by ls/tree "-F" flag ("/" for directory, "*" for executable etc.)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help -? Show this screen.
|
||||||
|
-b --before Prepend str before icon.
|
||||||
|
-B --begin Prepend str before output.
|
||||||
|
-e --end Append str after output.
|
||||||
|
-l --limit Limit line length to [number] characters.'
|
||||||
|
}
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
case $1 in
|
||||||
|
-h|-\?|--help)
|
||||||
|
print_help
|
||||||
|
exit ;;
|
||||||
|
-B|--begin)
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
print_begin "$2"
|
||||||
|
fi
|
||||||
|
shift ;;
|
||||||
|
-e|--end)
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
end=1
|
||||||
|
endstr="$2"
|
||||||
|
fi
|
||||||
|
shift ;;
|
||||||
|
-b|--before)
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
export beforestr="$2"
|
||||||
|
fi
|
||||||
|
shift ;;
|
||||||
|
-l|--limit)
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
export limit="$2"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf 'ERROR: "--limit" requires a non-empty option argument.\n'
|
||||||
|
exit
|
||||||
|
fi ;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break ;;
|
||||||
|
-?*)
|
||||||
|
printf 'WARNING: Unknown option ignored: %s\n' "$1" ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -t 0 ]; then
|
||||||
|
[ -n "$beforestr" ] && limit="$((limit - ${#beforestr}))"
|
||||||
|
icon_lookup
|
||||||
|
else
|
||||||
|
printf 'ERROR: no data provided...\nExpecting a directory listing in stdin\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$end" ]; then
|
||||||
|
print_end "$endstr"
|
||||||
|
fi
|
|
@ -0,0 +1,180 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Description: An almost fully POSIX compliant batch file renamer
|
||||||
|
#
|
||||||
|
# Note: nnn auto-detects and invokes this plugin if available
|
||||||
|
# Whitespace is used as delimiter for read.
|
||||||
|
# The plugin doesn't support filenames with leading or trailing whitespace
|
||||||
|
# To use NNN_LIST your shell must support readlink(1)
|
||||||
|
#
|
||||||
|
# Capabilities:
|
||||||
|
# 1. Basic file rename
|
||||||
|
# 2. Detects order change
|
||||||
|
# 3. Can move files
|
||||||
|
# 4. Can remove files
|
||||||
|
# 5. Switch number pairs to swap filenames
|
||||||
|
#
|
||||||
|
# Shell: bash
|
||||||
|
# Author: KlzXS
|
||||||
|
|
||||||
|
EDITOR="${EDITOR:-vi}"
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
NNN_INCLUDE_HIDDEN="${NNN_INCLUDE_HIDDEN:-0}"
|
||||||
|
VERBOSE="${VERBOSE:-0}"
|
||||||
|
RECURSIVE="${RECURSIVE:-0}"
|
||||||
|
|
||||||
|
case "$NNN_TRASH" in
|
||||||
|
1)
|
||||||
|
RM_UTIL="trash-put" ;;
|
||||||
|
2)
|
||||||
|
RM_UTIL="gio trash" ;;
|
||||||
|
*)
|
||||||
|
RM_UTIL="rm -ri" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
exit_status=0
|
||||||
|
|
||||||
|
dst_file=$(mktemp "$TMPDIR/.nnnXXXXXX")
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "Rename 'c'urrent / 's'election? "
|
||||||
|
read -r resp
|
||||||
|
|
||||||
|
if ! [ "$resp" = "c" ] && ! [ "$resp" = "s" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
arr=$(tr '\0' '\n' < "$selection")
|
||||||
|
else
|
||||||
|
findcmd="find . ! -name ."
|
||||||
|
|
||||||
|
if [ "$RECURSIVE" -eq 0 ]; then
|
||||||
|
findcmd="$findcmd -prune"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NNN_INCLUDE_HIDDEN" -eq 0 ]; then
|
||||||
|
findcmd="$findcmd ! -name \".*\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$NNN_LIST" ]; then
|
||||||
|
findcmd="$findcmd -print"
|
||||||
|
else
|
||||||
|
findcmd="$findcmd -printf "'"'"$NNN_LIST/%P\n"'"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
arr=$(eval "$findcmd" | sort)
|
||||||
|
fi
|
||||||
|
|
||||||
|
lines=$(printf "%s\n" "$arr" | wc -l)
|
||||||
|
width=${#lines}
|
||||||
|
|
||||||
|
printf "%s" "$arr" | awk '{printf("%'"${width}"'d %s\n", NR, $0)}' > "$dst_file"
|
||||||
|
|
||||||
|
items=("~")
|
||||||
|
while IFS='' read -r line; do
|
||||||
|
if [ -n "$NNN_LIST" ]; then
|
||||||
|
line=$(readlink "$line" || printf "%s" "$line")
|
||||||
|
fi
|
||||||
|
|
||||||
|
items+=("$line");
|
||||||
|
done < <(printf "%s\n" "$arr")
|
||||||
|
|
||||||
|
$EDITOR "$dst_file"
|
||||||
|
|
||||||
|
while read -r num name; do
|
||||||
|
if [ -z "$name" ]; then
|
||||||
|
if [ -z "$num" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s: unable to parse line, aborting\n" "$0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if $num is an integer
|
||||||
|
if [ ! "$num" -eq "$num" ] 2> /dev/null; then
|
||||||
|
printf "%s: unable to parse line, aborting\n" "$0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
src=${items[$num]}
|
||||||
|
|
||||||
|
if [ -z "$src" ]; then
|
||||||
|
printf "%s: unknown item number %s\n" "$0" "$num" > /dev/stderr
|
||||||
|
continue
|
||||||
|
elif [ "$name" != "$src" ]; then
|
||||||
|
if [ -z "$name" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$src" ] && [ ! -L "$src" ]; then
|
||||||
|
printf "%s: %s does not exit\n" "$0" "$src" > /dev/stderr
|
||||||
|
|
||||||
|
unset "items[$num]"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# handle swaps
|
||||||
|
if [ -e "$name" ] || [ -L "$name" ]; then
|
||||||
|
tmp="$name~"
|
||||||
|
c=0
|
||||||
|
|
||||||
|
while [ -e "$tmp" ] || [ -L "$tmp" ]; do
|
||||||
|
c=$((c+1))
|
||||||
|
tmp="$tmp~$c"
|
||||||
|
done
|
||||||
|
|
||||||
|
if mv "$name" "$tmp"; then
|
||||||
|
if [ "$VERBOSE" -ne 0 ]; then
|
||||||
|
printf "'%s' -> '%s'\n" "$name" "$tmp"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr
|
||||||
|
exit_status=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for key in "${!items[@]}"; do
|
||||||
|
if [ "${items[$key]}" = "$name" ]; then
|
||||||
|
items[$key]="$tmp"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir=$(dirname "$name")
|
||||||
|
if [ ! -d "$dir" ] && ! mkdir -p "$dir"; then
|
||||||
|
printf "%s: failed to create directory tree %s\n" "$0" "$dir" > /dev/stderr
|
||||||
|
exit_status=1
|
||||||
|
elif ! mv -i "$src" "$name"; then
|
||||||
|
printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr
|
||||||
|
exit_status=1
|
||||||
|
else
|
||||||
|
if [ -d "$name" ]; then
|
||||||
|
for key in "${!items[@]}"; do
|
||||||
|
items[$key]=$(printf "%s" "${items[$key]}" | sed "s|^$src\(\$\|\/\)|$name\1|")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$VERBOSE" -ne 0 ]; then
|
||||||
|
printf "'%s' => '%s'\n" "$src" "$name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
true
|
||||||
|
if [ "$VERBOSE" -ne 0 ]; then
|
||||||
|
printf "'%s' -> '%s'\n" "$src" "$name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset "items[$num]"
|
||||||
|
done <"$dst_file"
|
||||||
|
|
||||||
|
unset "items[0]"
|
||||||
|
for item in "${items[@]}"; do
|
||||||
|
$RM_UTIL "$item"
|
||||||
|
done
|
||||||
|
|
||||||
|
rm "$dst_file"
|
||||||
|
exit $exit_status
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Helper script for plugins
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
export selection
|
||||||
|
|
||||||
|
## Set CUR_CTX to 1 to open directory in current context
|
||||||
|
CUR_CTX=0
|
||||||
|
export CUR_CTX
|
||||||
|
|
||||||
|
## Ask nnn to switch to directory $1 in context $2.
|
||||||
|
## If $2 is not provided, the function asks explicitly.
|
||||||
|
nnn_cd () {
|
||||||
|
dir="$1"
|
||||||
|
|
||||||
|
if [ -z "$NNN_PIPE" ]; then
|
||||||
|
echo "No pipe file found" 1>&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
context=$2
|
||||||
|
elif [ $CUR_CTX -ne 1 ]; then
|
||||||
|
printf "Choose context 1-4 (blank for current): "
|
||||||
|
read -r context
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "${context:-0}c$dir" > "$NNN_PIPE"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_exists () {
|
||||||
|
type "$1" > /dev/null 2>&1
|
||||||
|
echo $?
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Show a notification
|
||||||
|
#
|
||||||
|
# Details: nnn invokes this plugin to show notification when a cp/mv/rm operation is complete.
|
||||||
|
#
|
||||||
|
# Dependencies: notify-send (Ubuntu)/ntfy (https://github.com/dschep/ntfy)/osascript (macOS)/notify (Haiku)
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad
|
||||||
|
|
||||||
|
OS="$(uname)"
|
||||||
|
|
||||||
|
if type notify-send >/dev/null 2>&1; then
|
||||||
|
notify-send nnn "Done!"
|
||||||
|
elif [ "$OS" = "Darwin" ]; then
|
||||||
|
osascript -e 'display notification "Done!" with title "nnn"'
|
||||||
|
elif type ntfy >/dev/null 2>&1; then
|
||||||
|
ntfy -t nnn send "Done!"
|
||||||
|
elif [ "$OS" = "Haiku" ]; then
|
||||||
|
notify --title "nnn" "Done!"
|
||||||
|
fi
|
|
@ -0,0 +1,378 @@
|
||||||
|
<h1 align="center">nnn plugins</h1>
|
||||||
|
|
||||||
|
<p align="center"><img src="https://i.imgur.com/SpT0L2W.png" /></p>
|
||||||
|
<p align="center"><i>read ebooks with plugin gutenread (Android)</i></p>
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Plugins extend the capabilities of `nnn`. They are _executable_ scripts (or binaries) `nnn` can communicate with and trigger. This mechanism fits perfectly with the fundamental design to keep the core file manager lean and fast, by delegating repetitive (but not necessarily file manager-specific) tasks to the plugins which can be run with custom hotkeys.
|
||||||
|
|
||||||
|
`nnn` is _**language-agnostic**_ when it comes to plugins. You can write a plugin in any (scripting) language you are comfortable in!
|
||||||
|
|
||||||
|
## List of plugins
|
||||||
|
|
||||||
|
| Plugin (a-z) | Description [Clears selection<sup>1</sup>] | Lang | Dependencies |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [autojump](autojump) | Navigate to dir/path | sh | [jump](https://github.com/gsamokovarov/jump)/autojump/<br>zoxide/z/[z.lua](https://github.com/skywind3000/z.lua) |
|
||||||
|
| [boom](boom) | Play random music from dir | sh | [moc](http://moc.daper.net/) |
|
||||||
|
| [bulknew](bulknew) | Create multiple files/dirs at once | bash | sed, xargs, mktemp |
|
||||||
|
| [cdpath](cdpath) | `cd` to the directory from `CDPATH` | sh | fzf |
|
||||||
|
| [chksum](chksum) | Create and verify checksums [✓] | sh | md5sum,<br>sha256sum |
|
||||||
|
| [cmusq](cmusq) | Queue/play files/dirs in cmus player [✓] | sh | cmus, pgrep |
|
||||||
|
| [diffs](diffs) | Diff for selection (limited to 2 for directories) [✓] | sh | vimdiff, mktemp |
|
||||||
|
| [dragdrop](dragdrop) | Drag/drop files from/into nnn | sh | [dragon](https://github.com/mwh/dragon) |
|
||||||
|
| [dups](dups) | List non-empty duplicate files in current dir | bash | find, md5sum,<br>sort uniq xargs |
|
||||||
|
| [finder](finder) | Run custom find command (**stored in histfile**) and list | sh | - |
|
||||||
|
| [fixname](fixname) | Clean filename to be more shell-friendly [✓] | bash | sed |
|
||||||
|
| [fzcd](fzcd) | Fuzzy search multiple dirs (or `$PWD`) and visit file | sh | fzf, (find) |
|
||||||
|
| [fzhist](fzhist) | Fuzzy-select a cmd from history, edit in `$EDITOR` and run | sh | fzf, mktemp |
|
||||||
|
| [fzopen](fzopen) | Fuzzy find file(s) in subtree to edit/open/pick | sh | fzf, xdg-open/open |
|
||||||
|
| [fzplug](fzplug) | Fuzzy find, preview and run other plugins | sh | fzf |
|
||||||
|
| [getplugs](getplugs) | Update plugins to installed `nnn` version | sh | curl |
|
||||||
|
| [gitroot](gitroot) | Cd to the root of current git repo | sh | git |
|
||||||
|
| [gpge](gpge) | Encrypt/decrypt files using GPG [✓] | sh | gpg |
|
||||||
|
| [gutenread](gutenread) | Browse, download, read from Project Gutenberg | sh | curl, unzip, w3m<br>[epr](https://github.com/wustho/epr) (optional) |
|
||||||
|
| [gsconnect](gsconnect) | GNOME's implementation of kdeconnect [✓] | sh | gsconnect |
|
||||||
|
| [imgresize](imgresize) | Batch resize images in dir to screen resolution | sh | [imgp](https://github.com/jarun/imgp) |
|
||||||
|
| [imgur](imgur) | Upload an image to imgur (from [imgur-screenshot](https://github.com/jomo/imgur-screenshot)) | bash | - |
|
||||||
|
| [imgview](imgview) | View (thumbnail)images, set wallpaper, [rename](https://github.com/jarun/nnn/wiki/Basic-use-cases#browse-rename-images) and [more](https://wiki.archlinux.org/index.php/Sxiv#Assigning_keyboard_shortcuts)| sh | _see in-file docs_ |
|
||||||
|
| [ipinfo](ipinfo) | Fetch external IP address and whois information | sh | curl, whois |
|
||||||
|
| [kdeconnect](kdeconnect) | Send selected files to an Android device [✓] | sh | kdeconnect-cli |
|
||||||
|
| [launch](launch) | GUI application launcher | sh | fzf |
|
||||||
|
| [mimelist](mimelist) | List files by mime in subtree | sh | - |
|
||||||
|
| [moclyrics](moclyrics) | Show lyrics of the track playing in moc | sh | [ddgr](https://github.com/jarun/ddgr), [moc](http://moc.daper.net/) |
|
||||||
|
| [mocq](mocq) | Queue/play selection/dir/file in moc [✓] | sh | [moc](http://moc.daper.net/) |
|
||||||
|
| [mp3conv](mp3conv) | Extract audio from multimedia as mp3 | sh | ffmpeg |
|
||||||
|
| [mtpmount](mtpmount) | Toggle mount of MTP device (eg. Android) | sh | gvfs-mtp |
|
||||||
|
| [nbak](nbak) | Backs up `nnn` config | sh | tar, awk, mktemp |
|
||||||
|
| [nmount](nmount) | Toggle mount status of a device as normal user | sh | pmount, udisks2 |
|
||||||
|
| [nuke](nuke) | Sample file opener (CLI-only by default) | sh | _see in-file docs_ |
|
||||||
|
| [oldbigfile](oldbigfile) | List large files by access time | sh | find, sort |
|
||||||
|
| [openall](openall) | Open selected files together or one by one [✓] | bash | - |
|
||||||
|
| [organize](organize) | Auto-organize files in directories by file type [✓] | sh | file |
|
||||||
|
| [pdfread](pdfread) | Read a PDF or text file aloud | sh | pdftotext, mpv,<br>pico2wave |
|
||||||
|
| [preview-tabbed](preview-tabbed) | Preview files with Tabbed/xembed | bash | _see in-file docs_ |
|
||||||
|
| [preview-tui](preview-tui) | Preview with Tmux/kitty/[QuickLook](https://github.com/QL-Win/QuickLook)/xterm/`$TERMINAL` | sh | _see in-file docs_ |
|
||||||
|
| [pskill](pskill) | Fuzzy list by name and kill process or zombie | sh | fzf, ps, sudo/doas |
|
||||||
|
| [renamer](renamer) | Batch rename selection or files in dir [✓] | sh | [qmv](https://www.nongnu.org/renameutils/)/[vidir](https://joeyh.name/code/moreutils/) |
|
||||||
|
| [ringtone](ringtone) | Create a variable bitrate mp3 ringtone from file | sh | date, ffmpeg |
|
||||||
|
| [rsynccp](rsynccp) | Gives copy-paste verbose progress percentage [✓] | sh | rsync |
|
||||||
|
| [splitjoin](splitjoin) | Split file or join selection [✓] | sh | split, cat |
|
||||||
|
| [suedit](suedit) | Edit file using superuser permissions | sh | sudoedit/sudo/doas |
|
||||||
|
| [togglex](togglex) | Toggle executable mode for selection [✓] | sh | chmod |
|
||||||
|
| [umounttree](umounttree) | Unmount a remote mountpoint from within | sh | fusermount |
|
||||||
|
| [upload](upload) | Upload to Firefox Send or ix.io (text) or file.io (bin) | sh | [ffsend](https://github.com/timvisee/ffsend), curl, jq, tr |
|
||||||
|
| [wallpaper](wall) | Set wallpaper or change colorscheme | sh | nitrogen/pywal |
|
||||||
|
| [x2sel](x2sel) | Copy file list from system clipboard to selection | sh | _see in-file docs_ |
|
||||||
|
| [xdgdefault](xdgdefault) | Set the default app for the hovered file type | sh | xdg-utils, fzf/dmenu |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. A plugin has to explicitly request `nnn` to clear the selection e.g. after operating on the selected files.
|
||||||
|
2. Files starting with a dot in the `plugins` directory are internal files and should not be used as plugins.
|
||||||
|
|
||||||
|
### Table of contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Skip directory refresh after running a plugin](#skip-directory-refresh-after-running-a-plugin)
|
||||||
|
- [Running commands as plugin](#running-commands-as-plugin)
|
||||||
|
- [Skip user confirmation after command execution](#skip-user-confirmation-after-command-execution)
|
||||||
|
- [Run a GUI app as plugin](#run-a-gui-app-as-plugin)
|
||||||
|
- [Page non-interactive command output](#page-non-interactive-command-output)
|
||||||
|
- [Some useful key-command examples](#some-useful-key-command-examples)
|
||||||
|
- [Access level of plugins](#access-level-of-plugins)
|
||||||
|
- [Create your own plugins](#create-your-own-plugins)
|
||||||
|
- [Send data to `nnn`](#send-data-to-nnn)
|
||||||
|
- [Get notified on file hover](#get-notified-on-file-hover)
|
||||||
|
- [Examples](#examples)
|
||||||
|
- [Contributing plugins](#contributing-plugins)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The following command installs or updates (after backup) all plugins:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -Ls https://raw.githubusercontent.com/jarun/nnn/master/plugins/getplugs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Plugins are installed to `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set environment variable `NNN_PLUG` to assign keybinds and invoke plugins directly using the plugin shortcut (<kbd>;</kbd>) followed by the assigned key character. E.g., with the below config:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='f:finder;o:fzopen;p:mocq;d:diffs;t:nmount;v:imgview'
|
||||||
|
```
|
||||||
|
|
||||||
|
plugin `finder` can be invoked with the keybind <kbd>;f</kbd>, `fzopen` can be run with <kbd>;o</kbd> and so on... The key vs. plugin pairs are shown in the help and config screen.
|
||||||
|
|
||||||
|
Alternatively, combine with <kbd>Alt</kbd> (i.e. <kbd>Alt+key</kbd>).
|
||||||
|
|
||||||
|
To pick and run an unassigned plugin, press <kbd>Enter</kbd> (to _enter_ the plugin dir) at the plugin prompt.
|
||||||
|
|
||||||
|
To run a plugin at startup, use the option `-P` followed by the plugin key.
|
||||||
|
|
||||||
|
If the plugins list gets too long, try breaking them up into sections:
|
||||||
|
|
||||||
|
```
|
||||||
|
NNN_PLUG_PERSONAL='g:personal/convert2zoom;p:personal/echo'
|
||||||
|
NNN_PLUG_WORK='j:work/prettyjson;d:work/foobar'
|
||||||
|
NNN_PLUG_INLINE='e:!go run $nnn*'
|
||||||
|
NNN_PLUG_DEFAULT='1:ipinfo;p:preview-tui;o:fzz;b:nbak'
|
||||||
|
NNN_PLUG="$NNN_PLUG_PERSONAL;$NNN_PLUG_WORK;$NNN_PLUG_DEFAULT;$NNN_PLUG_INLINE"
|
||||||
|
export NNN_PLUG
|
||||||
|
```
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- `'g:personal/convert2zoom'` will look in the personal sub-folder inside the plugin folder.
|
||||||
|
- `'b:boom;b:bulknew` will result in only the first definition of *b* (`b:boom`) being used.
|
||||||
|
- A keybinding definition of more than 1 character will prevent nnn from starting.
|
||||||
|
|
||||||
|
|
||||||
|
#### Skip directory refresh after running a plugin [`-`]
|
||||||
|
|
||||||
|
`nnn` refreshes the directory after running a plugin to reflect any changes by the plugin. To disable this add a `-` before the plugin name:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='p:-plugin'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running commands as plugin [`!`]
|
||||||
|
|
||||||
|
To assign keys to arbitrary non-background cli commands and invoke like plugins, add `!` (underscore) before the command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='x:!chmod +x $nnn;g:!git log;s:!smplayer $nnn'
|
||||||
|
```
|
||||||
|
|
||||||
|
Now <kbd>;x</kbd> can be used to make a file executable, <kbd>;g</kbd> can be used to the git log of a git project directory, <kbd>;s</kbd> can be used to preview a partially downloaded media file.
|
||||||
|
|
||||||
|
#### Skip user confirmation after command execution [`*`]
|
||||||
|
|
||||||
|
`nnn` waits for user confirmation (the prompt `Press Enter to continue`) after it executes a command as plugin (unlike plugins which can add a `read` to wait). To skip this, add a `*` after the command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='s:!smplayer $nnn*;n:-!vim /home/vaio/Dropbox/Public/synced_note*'
|
||||||
|
```
|
||||||
|
|
||||||
|
Now there will be no prompt after <kbd>;s</kbd> and <kbd>;n</kbd>.
|
||||||
|
|
||||||
|
Note: Do not use `*` with programs those run and exit e.g. cat.
|
||||||
|
|
||||||
|
#### Run a GUI app as plugin [`&`]
|
||||||
|
|
||||||
|
To run a GUI app as plugin, add a `&` after `!`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='m:-!&mousepad $nnn'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `$nnn` must be the last argument in this case.
|
||||||
|
|
||||||
|
#### Page non-interactive command output [`|`]
|
||||||
|
|
||||||
|
To show the output of run-and-exit commands which do not need user input, add `|` (pipe) after `!`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export NNN_PLUG='m:-!|mediainfo $nnn;t:-!|tree -ps;l:-!|ls -lah --group-directories-first'
|
||||||
|
```
|
||||||
|
|
||||||
|
This option is incompatible with `&` (terminal output is masked for GUI programs) and ignores `*` (output is already paged for user).
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. Use single quotes for `$NNN_PLUG` so `$nnn` is not interpreted
|
||||||
|
2. `$nnn` must be the last argument (if used) to run a _command as plugin_
|
||||||
|
3. (_Again_) add `!` before the command
|
||||||
|
4. To disable directory refresh after running a _command as plugin_, prefix with `-!`
|
||||||
|
|
||||||
|
#### Some useful key-command examples
|
||||||
|
|
||||||
|
| Key:Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `c:!convert $nnn png:- \| xclip -sel clipboard -t image/png*` | Copy image to clipboard |
|
||||||
|
| `e:-!sudo -E vim $nnn*` | Edit file as root in vim |
|
||||||
|
| `g:-!git diff` | Show git diff |
|
||||||
|
| `h:-!hx $nnn*` | Open hovered file in [hx](https://github.com/krpors/hx) hex editor |
|
||||||
|
| `k:-!fuser -kiv $nnn*` | Interactively kill process(es) using hovered file |
|
||||||
|
| `l:-!git log` | Show git log |
|
||||||
|
| `n:-!vi /home/user/Dropbox/dir/note*` | Take quick notes in a synced file/dir of notes |
|
||||||
|
| `p:-!less -iR $nnn*` | Page through hovered file in less |
|
||||||
|
| `s:-!&smplayer -minigui $nnn` | Play hovered media file, even unfinished download |
|
||||||
|
| `x:!chmod +x $nnn` | Make the hovered file executable |
|
||||||
|
| `y:-!sync*` | Flush cached writes |
|
||||||
|
|
||||||
|
## Access level of plugins
|
||||||
|
|
||||||
|
When `nnn` executes a plugin, it does the following:
|
||||||
|
- Changes to the directory where the plugin is to be run (`$PWD` pointing to the active directory)
|
||||||
|
- Passes three arguments to the script:
|
||||||
|
1. `$1`: The hovered file's name.
|
||||||
|
2. `$2`: The working directory (might differ from `$PWD` in case of symlinked paths; non-canonical).
|
||||||
|
3. `$3`: The picker mode output file (`-` for stdout) if `nnn` is executed as a file picker.
|
||||||
|
- Sets the environment variable `NNN_PIPE` used to control `nnn` active directory.
|
||||||
|
- Sets the environment variable `NNN_INCLUDE_HIDDEN` to `1` if hidden files are active, `0` otherwise.
|
||||||
|
- Exports the [special variables](https://github.com/jarun/nnn/wiki/Concepts#special-variables).
|
||||||
|
|
||||||
|
Plugins can also read the `.selection` file in the config directory.
|
||||||
|
|
||||||
|
## Create your own plugins
|
||||||
|
|
||||||
|
Plugins can be written in any scripting language. However, POSIX-compliant shell scripts runnable in `sh` are preferred.
|
||||||
|
|
||||||
|
**Make the file executable**. Drop it in the plugin directory. Optionally add a hotkey in `$NNN_PLUG` for frequent usage.
|
||||||
|
|
||||||
|
#### Send data to `nnn`
|
||||||
|
`nnn` provides a mechanism for plugins to send data to `nnn` to control its active directory or invoke the list mode.
|
||||||
|
The way to do so is by writing to the pipe pointed by the environment variable `NNN_PIPE`.
|
||||||
|
The plugin should write a single string in the format `(<->)<ctxcode><opcode><data>` without a newline at the end. For example, `1c/etc`.
|
||||||
|
|
||||||
|
The optional `-` at the **beginning of the stream** instructs `nnn` to clear the selection.
|
||||||
|
In cases where the data transfer to `nnn` has to happen while the selection file is being read (e.g. in a loop), the plugin should
|
||||||
|
create a tmp copy of the selection file, inform `nnn` to clear the selection and then do the subsequent processing with the tmp file.
|
||||||
|
A paged [`|`] or GUI [`&`] cmd run as plugin cannot clear selection.
|
||||||
|
|
||||||
|
The `ctxcode` indicates the context to change the active directory of.
|
||||||
|
|
||||||
|
| Context code | Meaning |
|
||||||
|
|:---:| --- |
|
||||||
|
| `+` | smart context (next inactive else current) |
|
||||||
|
| `0` | current context |
|
||||||
|
| `1`-`4` | context number |
|
||||||
|
|
||||||
|
The `opcode` indicates the operation type.
|
||||||
|
|
||||||
|
| Opcode | Operation |
|
||||||
|
|:---:| --- |
|
||||||
|
| `c` | change directory |
|
||||||
|
| `l` | list files in list mode |
|
||||||
|
| `p` | picker file overwritten |
|
||||||
|
|
||||||
|
For convenience, we provided a helper script named `.nnn-plugin-helper` and a function named `nnn_cd` to ease this process. `nnn_cd` receives the path to change to as the first argument, and the context as an optional second argument.
|
||||||
|
If a context is not provided, it is asked for explicitly. To skip this and choose the current context, set the `CUR_CTX` variable in `.nnn-plugin-helper` (or in the specific plugin after sourcing `.nnn-plugin-helper`) to 1.
|
||||||
|
Usage examples can be found in the Examples section below.
|
||||||
|
|
||||||
|
#### Get notified on file hover
|
||||||
|
|
||||||
|
If `NNN_FIFO` is set, `nnn` will open it and write every hovered files. This can be used in plugins and external scripts, e.g. to implement file previews.
|
||||||
|
|
||||||
|
Don't forget to fork in the background to avoid blocking `nnn`.
|
||||||
|
|
||||||
|
For more details on configuration and usage of the preview plugins, visit [Live Previews](https://github.com/jarun/nnn/wiki/Live-previews).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
There are many plugins provided by `nnn` which can be used as examples. Here are a few simple selected examples.
|
||||||
|
|
||||||
|
#### Show the git log of changes to the particular file along with the code for a quick and easy review.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
git log -p -- "$1"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Change to directory in clipboard using helper script
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
. $(dirname $0)/.nnn-plugin-helper
|
||||||
|
|
||||||
|
nnn_cd "$(xsel -ob)"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Change directory to the location of a link using helper script with specific context (current)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
. $(dirname $0)/.nnn-plugin-helper
|
||||||
|
|
||||||
|
nnn_cd "$(dirname $(readlink -fn $1))" 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Change to arbitrary directory without helper script
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
printf "cd to: "
|
||||||
|
read -r dir
|
||||||
|
|
||||||
|
printf "%s" "0c$dir" > "$NNN_PIPE"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Send every hovered file to X selection
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
if [ -z "$NNN_FIFO" ] ; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while read FILE ; do
|
||||||
|
printf "%s" "$FILE" | xsel
|
||||||
|
done < "$NNN_FIFO" &
|
||||||
|
disown
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quick `find` the first match in subtree and open in `nuke`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke"
|
||||||
|
|
||||||
|
printf "file name: "
|
||||||
|
read -r pattern
|
||||||
|
|
||||||
|
entry=$(find . -type f -iname "$pattern" -print -quit 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$entry" ]; then
|
||||||
|
"$NUKE" "$entry"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quick find (using `fd`)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
printf "pattern: "
|
||||||
|
read -r pattern
|
||||||
|
|
||||||
|
if [ -n "$pattern" ]; then
|
||||||
|
printf "%s" "+l" > "$NNN_PIPE"
|
||||||
|
eval "fd -HI $pattern -0" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quick grep (using `rg`)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
printf "pattern: "
|
||||||
|
read -r pattern
|
||||||
|
|
||||||
|
if [ -n "$pattern" ]; then
|
||||||
|
printf "%s" "+l" > "$NNN_PIPE"
|
||||||
|
eval "rg -l0 --hidden -S $pattern" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing plugins
|
||||||
|
|
||||||
|
1. Add informative sections like _Description_, _Notes_, _Dependencies_, _Shell_, _Author_ etc. in the plugin.
|
||||||
|
2. Add an entry in the table above. Note that the list is alphabetically ordered by plugin name.
|
||||||
|
3. Keep non-portable commands (like `notify-send`) commented so users from any other OS/DE aren't surprised.
|
||||||
|
4. The plugin file should be executable.
|
||||||
|
5. If your plugin stores data, use `${XDG_CACHE_HOME:-$HOME/.cache}/nnn`. Document it _in-file_.
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Navigate to directory using jump/autojump/zoxide/z
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - jump - https://github.com/gsamokovarov/jump
|
||||||
|
# - OR autojump - https://github.com/wting/autojump
|
||||||
|
# - OR zoxide - https://github.com/ajeetdsouza/zoxide
|
||||||
|
# - OR z - https://github.com/rupa/z (z requires fzf)
|
||||||
|
# - OR z (fish) - https://github.com/jethrokuan/z (z requires fzf)
|
||||||
|
# - OR z.lua - https://github.com/skywind3000/z.lua (z.lua can enhanced with fzf)
|
||||||
|
#
|
||||||
|
# Note: The dependencies STORE NAVIGATION PATTERNS
|
||||||
|
#
|
||||||
|
# to make z.lua work, you need to set $NNN_ZLUA to the path of script z.lua
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Marty Buchaus, Dave Snider, Tim Adler, Nick Waywood
|
||||||
|
|
||||||
|
if [ ! -p "$NNN_PIPE" ]; then
|
||||||
|
printf 'ERROR: NNN_PIPE is not set!'
|
||||||
|
read -r _
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if type jump >/dev/null 2>&1; then
|
||||||
|
printf "jump to : "
|
||||||
|
IFS= read -r line
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
odir="$(jump cd ${line})"
|
||||||
|
printf "%s" "0c$odir" > "$NNN_PIPE"
|
||||||
|
elif type autojump >/dev/null 2>&1; then
|
||||||
|
printf "jump to : "
|
||||||
|
read -r dir
|
||||||
|
odir="$(autojump "$dir")"
|
||||||
|
printf "%s" "0c$odir" > "$NNN_PIPE"
|
||||||
|
elif type zoxide >/dev/null 2>&1; then
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
odir="$(zoxide query -i --)"
|
||||||
|
printf "%s" "0c$odir" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
printf "jump to : "
|
||||||
|
read -r dir
|
||||||
|
odir="$(zoxide query -- "$dir")"
|
||||||
|
printf "%s" "0c$odir" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
elif type lua >/dev/null 2>&1 && [ -n "$NNN_ZLUA" ]; then
|
||||||
|
printf "jump to : "
|
||||||
|
read -r line
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
odir="$(lua "$NNN_ZLUA" -l "$line" | fzf --nth 2.. --reverse --inline-info --tac +s -e --height 35%)"
|
||||||
|
printf "%s" "0c$(echo "$odir" | awk '{print $2}')" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
odir="$(lua "$NNN_ZLUA" -e "$line")"
|
||||||
|
printf "%s" "0c$odir" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# rupa/z uses $_Z_DATA, jethrokuan/z (=port of z for fish) uses $Z_DATA
|
||||||
|
datafile="${_Z_DATA:-${Z_DATA:-$HOME/.z}}"
|
||||||
|
if type fzf >/dev/null 2>&1 && [ -f "$datafile" ]; then
|
||||||
|
# Read the data from z's file instead of calling
|
||||||
|
# z so the data doesn't need to be processed twice
|
||||||
|
sel=$(awk -F "|" '{print $1}' "$datafile" | fzf | awk '{$1=$1};1')
|
||||||
|
|
||||||
|
# NOTE: Uncomment this line and comment out the line above if
|
||||||
|
# you want to see the weightings of the dir's in the fzf pane
|
||||||
|
# sel=$(awk -F "|" '{printf "%s %s\n", $2, $1}' "$datafile" | fzf | sed 's/^[0-9,.]* *//' | awk '{$1=$1};1')
|
||||||
|
|
||||||
|
printf "%s" "0c$sel" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
printf "No supported autojump script [jump/autojump/zoxide/z (needs fzf)] found"
|
||||||
|
read -r _
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Play random music (MP3, FLAC, M4A, WEBM, WMA) from current dir.
|
||||||
|
#
|
||||||
|
# Dependencies: mocp (or custom)
|
||||||
|
#
|
||||||
|
# Note: You may want to set GUIPLAYER.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
GUIPLAYER="${GUIPLAYER:-""}"
|
||||||
|
NUMTRACKS="${NUMTRACKS:-100}"
|
||||||
|
|
||||||
|
if [ -n "$GUIPLAYER" ]; then
|
||||||
|
find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | shuf -n "$NUMTRACKS" | xargs -d "\n" "$GUIPLAYER" > /dev/null 2>&1 &
|
||||||
|
|
||||||
|
# detach the player
|
||||||
|
sleep 1
|
||||||
|
elif type mocp >/dev/null 2>&1; then
|
||||||
|
cmd=$(pgrep -x mocp 2>/dev/null)
|
||||||
|
ret=$cmd
|
||||||
|
|
||||||
|
if [ -z "$ret" ]; then
|
||||||
|
# start MOC server
|
||||||
|
mocp -S
|
||||||
|
mocp -o shuffle
|
||||||
|
else
|
||||||
|
# mocp running, check if it's playing
|
||||||
|
state=$(mocp -i | grep "State:" | cut -d' ' -f2)
|
||||||
|
if [ "$state" = 'PLAY' ]; then
|
||||||
|
# add up to 100 random audio files
|
||||||
|
find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# clear MOC playlist
|
||||||
|
mocp -c
|
||||||
|
mocp -o shuffle
|
||||||
|
|
||||||
|
# add up to 100 random audio files
|
||||||
|
find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a
|
||||||
|
|
||||||
|
# start playing
|
||||||
|
mocp -p
|
||||||
|
else
|
||||||
|
printf "moc missing"
|
||||||
|
read -r _
|
||||||
|
fi
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Allows for creation of multiple files/dirs simultaneously
|
||||||
|
# Creates a tmp file to write each entry in a separate line
|
||||||
|
#
|
||||||
|
# Note: Only relative paths are supported. Absolute paths are ignored
|
||||||
|
# Leading and trailing whitespace in path names is also ignored
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: KlzXS
|
||||||
|
|
||||||
|
EDITOR="${EDITOR:-vi}"
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
|
||||||
|
printf "'f'ile / 'd'ir? "
|
||||||
|
read -r resp
|
||||||
|
|
||||||
|
if [ "$resp" = "f" ]; then
|
||||||
|
#shellcheck disable=SC2016
|
||||||
|
cmd='mkdir -p "$(dirname "{}")" && touch "{}"'
|
||||||
|
elif [ "$resp" = "d" ]; then
|
||||||
|
cmd='mkdir -p {}'
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX")
|
||||||
|
$EDITOR "$tmpfile"
|
||||||
|
|
||||||
|
sed "/^\//d" "$tmpfile" | xargs -n1 -I{} sh -c "$cmd"
|
||||||
|
|
||||||
|
rm "$tmpfile"
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: 'cd' to the directory from CDPATH
|
||||||
|
#
|
||||||
|
# Details: If the CDPATH environmet variable is not set, the default value of
|
||||||
|
# ${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks will be used.
|
||||||
|
# You can create this directory and fill it with symbolic links to your
|
||||||
|
# favorite directories. It's a good idea to add it to CDPATH so that it
|
||||||
|
# could also be used from the command line outside of nnn.
|
||||||
|
# The fzf search is done on the directory basename (the first column).
|
||||||
|
#
|
||||||
|
# This plugin is an extended version of the bookmarks plugin.
|
||||||
|
# If you set your CDPATH to ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/bookmarks
|
||||||
|
# or to the value of BOOKMARKS_DIR, you can use it as a bookmarks replacement.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Yuri Kloubakov
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
# Get a list of (symbolic links to) directories for every element of CDPATH
|
||||||
|
get_dirs() {
|
||||||
|
IFS=':'
|
||||||
|
for path in $CDPATH; do
|
||||||
|
for entry in "$path"/*; do
|
||||||
|
if [ -d "$entry" ]; then
|
||||||
|
name=$(basename "$entry" | grep -o '^.\{1,24\}')
|
||||||
|
if [ -h "$entry" ]; then
|
||||||
|
slink=$(ls -dl -- "$entry")
|
||||||
|
entry=${slink#*" $entry -> "}
|
||||||
|
fi
|
||||||
|
printf "%-24s :%s\n" "${name}" "$entry"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
echo "$1"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$CDPATH" ]; then
|
||||||
|
CDPATH="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks"
|
||||||
|
[ -d "$CDPATH" ] || abort "CDPATH is not set and there is no \"$CDPATH\" directory"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dir_list=$(get_dirs)
|
||||||
|
[ -n "$dir_list" ] || abort "There are no directories to choose from. Check your \"$CDPATH\"."
|
||||||
|
|
||||||
|
dir=$(echo "$dir_list" | fzf --nth=1 --delimiter=':' | awk -F: 'END { print $2 }')
|
||||||
|
if [ -n "$dir" ]; then
|
||||||
|
nnn_cd "$dir" 0
|
||||||
|
fi
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Create and verify checksums
|
||||||
|
#
|
||||||
|
# Note: On macOS, install the relevant checksum packages from Homebrew with:
|
||||||
|
# brew install coreutils
|
||||||
|
#
|
||||||
|
# Details:
|
||||||
|
# - selection: it will generate one file with the checksums and filenames
|
||||||
|
# (and with paths if they are in another directory)
|
||||||
|
# output checksum filename format: checksum_timestamp.checksum_type
|
||||||
|
# - file: if the file is a checksum, the plugin does the verification
|
||||||
|
# if the file is not a checksum, checksum will be generated for it
|
||||||
|
# the output checksum filename will be filename.checksum_type
|
||||||
|
# - directory: recursively calculates checksum for all the files in the dir
|
||||||
|
# the output checksum filename will be directory.checksum_type
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: ath3, Arun Prakash Jana
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
resp=f
|
||||||
|
chsum=md5
|
||||||
|
|
||||||
|
checksum_type()
|
||||||
|
{
|
||||||
|
echo "possible checksums: md5, sha1, sha224, sha256, sha384, sha512"
|
||||||
|
printf "create md5 (m), sha256 (s), sha512 (S) (or type one of the above checksums) [default=m]: "
|
||||||
|
read -r chsum_resp
|
||||||
|
for chks in md5 sha1 sha224 sha256 sha384 sha512
|
||||||
|
do
|
||||||
|
if [ "$chsum_resp" = "$chks" ]; then
|
||||||
|
chsum=$chsum_resp
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$chsum_resp" = "s" ]; then
|
||||||
|
chsum=sha256
|
||||||
|
elif [ "$chsum_resp" = "S" ]; then
|
||||||
|
chsum=sha512
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "work with selection (s) or current file (f) [default=f]: "
|
||||||
|
read -r resp
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
checksum_type
|
||||||
|
sed 's|'"$PWD/"'||g' < "$selection" | xargs -0 -I{} ${chsum}sum {} > "checksum_$(date '+%Y%m%d%H%M').$chsum"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
elif [ -n "$1" ]; then
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
for chks in md5 sha1 sha224 sha256 sha384 sha512
|
||||||
|
do
|
||||||
|
if echo "$1" | grep -q \.${chks}$; then
|
||||||
|
${chks}sum -c < "$1"
|
||||||
|
read -r _
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
checksum_type
|
||||||
|
file=$(basename "$1").$chsum
|
||||||
|
${chsum}sum "$1" > "$file"
|
||||||
|
elif [ -d "$1" ]; then
|
||||||
|
checksum_type
|
||||||
|
file=$(basename "$1").$chsum
|
||||||
|
find "$1" -type f -exec ${chsum}sum "{}" + > "$file"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,80 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Add selection or hovered file/directory to cmus queue
|
||||||
|
#
|
||||||
|
# Dependencies: cmus, pgrep, xdotool (optional)
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. If adding selection, files/dirs are added in the same order they were selected in nnn
|
||||||
|
# 2. A new window will be opened if cmus is not running already, playback will start immediately
|
||||||
|
# 3. If cmus is already running, files will be appended to the queue with no forced playback
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# 1. Add cava and cmus-lyrics as optional dependencies
|
||||||
|
# 2. Start cava and/or cmus-lyrics in tmux or kitty panes next to cmus
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Kabouik
|
||||||
|
|
||||||
|
# (Optional) Set preferred terminal emulator for cmus if not set in your env,
|
||||||
|
# or leave commented out to use OS default
|
||||||
|
#TERMINAL="kitty"
|
||||||
|
|
||||||
|
if ! type cmus >/dev/null; then
|
||||||
|
printf "cmus missing"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
start_cmus() {
|
||||||
|
type xdotool >/dev/null && nnnwindow="$(xdotool getactivewindow)"
|
||||||
|
case "$TERMINAL" in
|
||||||
|
kitty | gnome-terminal | st)
|
||||||
|
nohup "$TERMINAL" -- cmus & ;;
|
||||||
|
havoc)
|
||||||
|
nohup "$TERMINAL" cmus & ;;
|
||||||
|
"")
|
||||||
|
nohup x-terminal-emulator -e cmus & ;;
|
||||||
|
*)
|
||||||
|
nohup "$TERMINAL" -e cmus & ;;
|
||||||
|
esac
|
||||||
|
# Give the new terminal some time to open
|
||||||
|
until cmus-remote -C; do sleep 0.1; done
|
||||||
|
[ -n "$nnnwindow" ] && xdotool windowactivate "$nnnwindow"
|
||||||
|
} >/dev/null 2>&1
|
||||||
|
|
||||||
|
fill_queue() {
|
||||||
|
if [ "$REPLY" = "s" ]; then
|
||||||
|
xargs < "$selection" -0 cmus-remote -q
|
||||||
|
elif [ -n "$1" ]; then
|
||||||
|
cmus-remote -q "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# If active selection,then ask what to do
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "Queue [s]election or [c]urrently hovered? [default=c]: "
|
||||||
|
read -r REPLY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If cmus is not running, start and play queue
|
||||||
|
if ! pgrep cmus >/dev/null; then
|
||||||
|
printf "cmus is not running, starting it in a new %s window.\n" "$TERMINAL"
|
||||||
|
start_cmus
|
||||||
|
fill_queue "$1"
|
||||||
|
cmus-remote -p
|
||||||
|
printf "Files added to cmus queue.\n"
|
||||||
|
else # Append to existing queue if cmus is already running
|
||||||
|
fill_queue "$1"
|
||||||
|
printf "Files appended to current cmus queue.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Change view
|
||||||
|
cmus-remote -C "view 4"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Show diff of 2 directories or multiple files in vimdiff
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. vim may show the warning: 'Vim: Warning: Input is not from a terminal'
|
||||||
|
# press 'Enter' to ignore and proceed.
|
||||||
|
# 2. if only one file is in selection, the hovered file is considered as the
|
||||||
|
# second file to diff with
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Arun Prakash Jana, ath3
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
if type nvim >/dev/null 2>&1; then
|
||||||
|
diffcmd="nvim -d"
|
||||||
|
else
|
||||||
|
diffcmd="vimdiff +0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirdiff() {
|
||||||
|
dir1=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$1")".XXXXXXXX)
|
||||||
|
dir2=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$2")".XXXXXXXX)
|
||||||
|
ls -A1 "$1" > "$dir1"
|
||||||
|
ls -A1 "$2" > "$dir2"
|
||||||
|
$diffcmd "$dir1" "$dir2"
|
||||||
|
rm "$dir1" "$dir2"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
arr=$(tr '\0' '\n' < "$selection")
|
||||||
|
if [ "$(echo "$arr" | wc -l)" -gt 1 ]; then
|
||||||
|
f1="$(echo "$arr" | sed -n '1p')"
|
||||||
|
f2="$(echo "$arr" | sed -n '2p')"
|
||||||
|
if [ -d "$f1" ] && [ -d "$f2" ]; then
|
||||||
|
dirdiff "$f1" "$f2"
|
||||||
|
else
|
||||||
|
# If xargs supports the -o option, use it to get rid of:
|
||||||
|
# Vim: Warning: Input is not from a terminal
|
||||||
|
# xargs -0 -o vimdiff < $selection
|
||||||
|
|
||||||
|
eval xargs -0 "$diffcmd" < "$selection"
|
||||||
|
fi
|
||||||
|
elif [ -n "$1" ]; then
|
||||||
|
f1="$(echo "$arr" | sed -n '1p')"
|
||||||
|
if [ -d "$f1" ] && [ -d "$1" ]; then
|
||||||
|
dirdiff "$f1" "$1"
|
||||||
|
elif [ -f "$f1" ] && [ -f "$1" ]; then
|
||||||
|
$diffcmd "$f1" "$1"
|
||||||
|
else
|
||||||
|
echo "cannot compare file with directory"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "needs at least 2 files or directories selected for comparison"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Open a Drag and drop window, to drop files onto other programs.
|
||||||
|
# Also provides drag and drop window for files.
|
||||||
|
#
|
||||||
|
# Dependencies: dragon - https://github.com/mwh/dragon
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. Files that are dropped will be added to nnn's selection
|
||||||
|
# Some web-based files will be downloaded to current dir
|
||||||
|
# with curl and it may overwrite some existing files
|
||||||
|
# 2. The user has to mm to clear nnn's selection first
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: 0xACE
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
resp=f
|
||||||
|
all=
|
||||||
|
if type dragon-drag-and-drop >/dev/null 2>&1; then
|
||||||
|
dnd="dragon-drag-and-drop"
|
||||||
|
elif type dragon-drop >/dev/null 2>&1; then
|
||||||
|
dnd="dragon-drop"
|
||||||
|
else
|
||||||
|
dnd="dragon"
|
||||||
|
fi
|
||||||
|
|
||||||
|
add_file ()
|
||||||
|
{
|
||||||
|
printf '%s\0' "$@" >> "$selection"
|
||||||
|
}
|
||||||
|
|
||||||
|
use_all ()
|
||||||
|
{
|
||||||
|
printf "mark --all (a) [default=none]: "
|
||||||
|
read -r resp
|
||||||
|
if [ "$resp" = "a" ]; then
|
||||||
|
all="--all"
|
||||||
|
else
|
||||||
|
all=""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "Drop file (r). Drag selection (s), Drag current directory (d) or drag current file (f) [default=f]: "
|
||||||
|
read -r resp
|
||||||
|
else
|
||||||
|
printf "Drop file (r). Drag current directory (d) or drag current file (f) [default=f]: "
|
||||||
|
read -r resp
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
resp=f
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
use_all
|
||||||
|
sed -z 's|'"$PWD/"'||g' < "$selection" | xargs -0 "$dnd" "$all" &
|
||||||
|
elif [ "$resp" = "d" ]; then
|
||||||
|
use_all
|
||||||
|
"$dnd" "$all" "$PWD/"* &
|
||||||
|
elif [ "$resp" = "r" ]; then
|
||||||
|
true > "$selection"
|
||||||
|
"$dnd" --print-path --target | while read -r f
|
||||||
|
do
|
||||||
|
if printf "%s" "$f" | grep '^\(https\?\|ftps\?\|s\?ftp\):\/\/' ; then
|
||||||
|
curl -LJO "$f"
|
||||||
|
add_file "$PWD/$(basename "$f")"
|
||||||
|
elif [ -e "$f" ]; then
|
||||||
|
add_file "$f"
|
||||||
|
fi
|
||||||
|
done &
|
||||||
|
else
|
||||||
|
if [ -n "$1" ] && [ -e "$1" ]; then
|
||||||
|
"$dnd" "$1" &
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: List non-empty duplicates in the current dir (based on size followed by MD5)
|
||||||
|
#
|
||||||
|
# Source: https://www.commandlinefu.com/commands/view/3555/find-duplicate-files-based-on-size-first-then-md5-hash
|
||||||
|
#
|
||||||
|
# Dependencies: find md5sum sort uniq xargs gsed
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. If the file size exceeds $size_digits digits the file will be misplaced
|
||||||
|
# 12 digits fit files up to 931GiB
|
||||||
|
# 2. Bash compatible required for mktemp
|
||||||
|
#
|
||||||
|
# Shell: Bash
|
||||||
|
# Authors: syssyphus, KlzXS
|
||||||
|
|
||||||
|
EDITOR="${EDITOR:-vi}"
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
|
||||||
|
size_digits=12
|
||||||
|
tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX")
|
||||||
|
|
||||||
|
printf "\
|
||||||
|
## This is an overview of all duplicate files found.
|
||||||
|
## Comment out the files you wish to remove. You will be given an option to cancel.
|
||||||
|
## Lines with double comments (##) are ignored.
|
||||||
|
## You will have the option to remove the files with force or interactively.\n
|
||||||
|
" > "$tmpfile"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
find . -size +0 -type f -printf "%${size_digits}s %p\n" | sort -rn | uniq -w"${size_digits}" -D | sed -e '
|
||||||
|
s/^ \{0,12\}\([0-9]\{0,12\}\) \(.*\)$/printf "%s %s\\n" "$(md5sum "\2")" "d\1"/
|
||||||
|
' | tr '\n' '\0' | xargs -0 -n1 sh -c | sort | { uniq -w32 --all-repeated=separate; echo; } | sed -ne '
|
||||||
|
h
|
||||||
|
s/^\(.\{32\}\).* d\([0-9]*\)$/## md5sum: \1 size: \2 bytes/p
|
||||||
|
g
|
||||||
|
|
||||||
|
:loop
|
||||||
|
N
|
||||||
|
/.*\n$/!b loop
|
||||||
|
p' | sed -e 's/^.\{32\} \(.*\) d[0-9]*$/\1/' >> "$tmpfile"
|
||||||
|
|
||||||
|
"$EDITOR" "$tmpfile"
|
||||||
|
|
||||||
|
printf "Remove commented files? (yes/no) [default=n]: "
|
||||||
|
read -r commented
|
||||||
|
|
||||||
|
if [ "$commented" = "y" ]; then
|
||||||
|
sedcmd="/^##.*/d; /^[^#].*/d; /^$/d; s/^# *\(.*\)$/\1/"
|
||||||
|
else
|
||||||
|
printf "Press any key to exit"
|
||||||
|
read -r _
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "Remove with force or interactive? (f/i) [default=i]: "
|
||||||
|
read -r force
|
||||||
|
|
||||||
|
if [ "$force" = "f" ]; then
|
||||||
|
#shellcheck disable=SC2016
|
||||||
|
sed -e "$sedcmd" "$tmpfile" | tr '\n' '\0' | xargs -0 -r sh -c 'rm -f "$0" "$@" </dev/tty'
|
||||||
|
else
|
||||||
|
#shellcheck disable=SC2016
|
||||||
|
sed -e "$sedcmd" "$tmpfile" | tr '\n' '\0' | xargs -0 -r sh -c 'rm -i "$0" "$@" </dev/tty'
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm "$tmpfile"
|
||||||
|
|
||||||
|
printf "Press any key to exit"
|
||||||
|
read -r _
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Description: Run custom search and list results in smart context
|
||||||
|
#
|
||||||
|
# Note: This plugin retains search history
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# Run plugin and enter e.g. "-size +10M" to list files in current
|
||||||
|
# directory larger than 10M. By default entered expressions are
|
||||||
|
# interpreted as arguments to find. Results have to be NUL
|
||||||
|
# terminated which is done by default for find. Alternatively one
|
||||||
|
# can prepend a '$' to run a custom search program such as fd or
|
||||||
|
# ripgrep. Entered expressions will be saved in history file to
|
||||||
|
# be listed as bookmarks and and can be entered by index and edited.
|
||||||
|
#
|
||||||
|
# Shell: Bash
|
||||||
|
# Author: Arun Prakash Jana, Luuk van Baal
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
NNN_FINDHIST="${NNN_FINDHIST:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/finderbms}"
|
||||||
|
NNN_FINDHISTLEN="${NNN_FINDHISTLEN:-10000}"
|
||||||
|
|
||||||
|
printexamples() {
|
||||||
|
printf -- "-maxdepth 1 -name pattern
|
||||||
|
-maxdepth 1 -size +100M
|
||||||
|
\$fd -0 pattern
|
||||||
|
\$fd -0 -d 2 -S +100M
|
||||||
|
\$grep -rlZ pattern
|
||||||
|
\$rg -l0 pattern
|
||||||
|
\$fzf -m | tr '\\\n' '\\\0'\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
printexprs() {
|
||||||
|
for ((i = "$1"; i < ${#fexprs[@]}; i++)); do
|
||||||
|
printf '%s\t%s\n' "$((i + 1))" "${fexprs[$i]}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
mapexpr() {
|
||||||
|
if [ "$fexpr" -eq "$fexpr" ] 2>/dev/null; then
|
||||||
|
fexpr=${fexprs[$((fexpr - 1))]}
|
||||||
|
read -r -e -p "Search expression: " -i "$fexpr" fexpr
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
readexpr() {
|
||||||
|
case "$fexpr" in
|
||||||
|
h) clear
|
||||||
|
printf "Examples:\n"
|
||||||
|
mapfile -t fexprs < <(printexamples)
|
||||||
|
printexprs 0
|
||||||
|
read -r -p "Search expression or index: " fexpr
|
||||||
|
mapexpr
|
||||||
|
[ -n "$fexpr" ] && readexpr ;;
|
||||||
|
\$*) cmd="${fexpr:1}" ;;
|
||||||
|
*) mapexpr && readexpr
|
||||||
|
cmd="find $fexpr -print0" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
clear
|
||||||
|
[ -f "$NNN_FINDHIST" ] || printexamples > "$NNN_FINDHIST"
|
||||||
|
|
||||||
|
mapfile -t fexprs < <(sort "$NNN_FINDHIST" | uniq -c | sort -nr | head -n5 |\
|
||||||
|
awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}')
|
||||||
|
printf "Most used search expressions:\n"
|
||||||
|
printexprs 0
|
||||||
|
|
||||||
|
mapfile -t -O"$i" fexprs < <(tac "$NNN_FINDHIST" | awk '!a[$0]++' | head -n5)
|
||||||
|
printf "Most recently used search expressions:\n"
|
||||||
|
printexprs "$i"
|
||||||
|
read -r -p "Search expression or index (h for help): " fexpr
|
||||||
|
|
||||||
|
mapexpr
|
||||||
|
|
||||||
|
if [ -n "$fexpr" ]; then
|
||||||
|
printf "+l" > "$NNN_PIPE"
|
||||||
|
while :; do
|
||||||
|
readexpr
|
||||||
|
eval "$cmd" > "$NNN_PIPE" && break
|
||||||
|
read -r -e -p "Search expression: " -i "$fexpr" fexpr
|
||||||
|
done
|
||||||
|
if [ -n "$fexpr" ]; then
|
||||||
|
tail -n"$NNN_FINDHISTLEN" "$NNN_FINDHIST" > "$TMPDIR/finderbms"
|
||||||
|
printf "%s\n" "$fexpr" >> "$TMPDIR/finderbms"
|
||||||
|
mv "$TMPDIR/finderbms" "$NNN_FINDHIST"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Description: Clean filename or dirname (either hovered or selections)
|
||||||
|
# to be more shell-friendly. This script cleans
|
||||||
|
# non A-Za-z0-9._- characters.
|
||||||
|
# and replaces it with underscore (_).
|
||||||
|
#
|
||||||
|
# It supports cleaning single/double quote, newline,
|
||||||
|
# leading, trailing spaces.
|
||||||
|
#
|
||||||
|
# eg.
|
||||||
|
# to be continued (つづく).mp4 -> to_be_continued______.mp4
|
||||||
|
# [work] stuff.txt -> _work__stuff.txt
|
||||||
|
# home's server -> home_s_server
|
||||||
|
# qwe\trty -> __qwe_rty
|
||||||
|
#
|
||||||
|
# And if there are two almost similar filenames
|
||||||
|
# like: 'asd]f' and 'asd f' both will be renamed to 'asd_f',
|
||||||
|
# to avoid overwriting, the last file will be prepended by _.
|
||||||
|
# So they will be: 'asd_f' and '_asd_f'
|
||||||
|
#
|
||||||
|
# Dependencies: sed
|
||||||
|
#
|
||||||
|
# Shell: Bash
|
||||||
|
# Author: Benawi Adha
|
||||||
|
|
||||||
|
prompt=true
|
||||||
|
sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
# printf "%s" "$1" | sed -e 's/[^A-Za-z0-9._-]/_/g'
|
||||||
|
printf "%s" "$1" | sed 's/[^A-Za-z0-9._-]/_/g' | sed ':a;N;$!ba;s/\n/_/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -s "$sel" ]; then
|
||||||
|
targets=()
|
||||||
|
while IFS= read -r -d '' i || [ -n "$i" ]; do
|
||||||
|
targets+=( "$(basename "$i")" )
|
||||||
|
done < "$sel"
|
||||||
|
else
|
||||||
|
targets=("$1")
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in "${targets[@]}"; do
|
||||||
|
printf "%s -> %s\n" "$i" "$(cleanup "$i")";
|
||||||
|
done
|
||||||
|
|
||||||
|
if $prompt; then
|
||||||
|
echo
|
||||||
|
printf "Proceed [Yn]? "
|
||||||
|
read -r input
|
||||||
|
case "$input" in
|
||||||
|
y|Y|'')
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Canceled"
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in "${targets[@]}"; do
|
||||||
|
if [ "$i" != "$(cleanup "$i")" ]; then
|
||||||
|
tmp=''
|
||||||
|
if [ -e "$(cleanup "$i")" ]; then
|
||||||
|
tmp='_'
|
||||||
|
fi
|
||||||
|
mv "$i" "$tmp$(cleanup "$i")";
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Fuzzy search multiple locations read-in from a path-list file
|
||||||
|
# (or $PWD) and open the selected file's dir in a smart context.
|
||||||
|
# Dependencies: fzf, find (only for multi-location search)
|
||||||
|
#
|
||||||
|
# Details: Paths in list file should be newline-separated absolute paths.
|
||||||
|
# Paths can be file paths; the script will scan the parent dirs.
|
||||||
|
#
|
||||||
|
# The path-list file precedence is:
|
||||||
|
# - "$1" (the hovered file) if it exists, is plain-text and the
|
||||||
|
# first line points to an existing file
|
||||||
|
# - "$LIST" if set below
|
||||||
|
# - "$2" (the current directory) [mimics plugin fzcd behaviour]
|
||||||
|
#
|
||||||
|
# The path-list file can be generated easily:
|
||||||
|
# - pick the (file)paths in picker mode to path-list file
|
||||||
|
# - OR, edit selection in nnn and save as path-list file
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad, Arun Prakash Jana, KlzXS
|
||||||
|
|
||||||
|
IFS="$(printf '\n\r')"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
CTX=+
|
||||||
|
LIST="${LIST:-""}"
|
||||||
|
|
||||||
|
if ! type fzf >/dev/null 2>&1; then
|
||||||
|
printf "fzf missing"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$1" ] && [ "$(file -b --mime-type "$1")" = 'text/plain' ] && [ -e "$(head -1 "$1")" ]; then
|
||||||
|
LIST="$1"
|
||||||
|
elif ! [ -s "$LIST" ]; then
|
||||||
|
sel=$(fzf)
|
||||||
|
# Show only the file and parent dir
|
||||||
|
# sel=$(fzf --delimiter / --with-nth=-2,-1 --tiebreak=begin --info=hidden)
|
||||||
|
|
||||||
|
LIST=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$LIST" ]; then
|
||||||
|
if type find >/dev/null 2>&1; then
|
||||||
|
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
|
||||||
|
|
||||||
|
while IFS= read -r path; do
|
||||||
|
if [ -d "$path" ]; then
|
||||||
|
printf "%s\n" "$path" >> "$tmpfile"
|
||||||
|
elif [ -f "$path" ]; then
|
||||||
|
printf "%s\n" "$(dirname "$path")" >> "$tmpfile"
|
||||||
|
fi
|
||||||
|
done < "$LIST"
|
||||||
|
|
||||||
|
sel=$(xargs -d '\n' < "$tmpfile" -I{} find {} -type f -printf "%H//%P\n" | sed '/.*\/\/\(\..*\|.*\/\..*\)/d; s:/\+:/:g' | fzf --delimiter / --tiebreak=begin --info=hidden)
|
||||||
|
# Alternative for 'fd'
|
||||||
|
# sel=$(xargs -d '\n' < "$tmpfile" fd . | fzf --delimiter / --tiebreak=begin --info=hidden)
|
||||||
|
|
||||||
|
rm "$tmpfile"
|
||||||
|
else
|
||||||
|
printf "find missing"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$sel" ]; then
|
||||||
|
if [ "$sel" = "." ] || { ! [ -d "$sel" ] && ! [ -f "$sel" ]; }; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the selected path returned by fzf command is absolute
|
||||||
|
case $sel in
|
||||||
|
/*) nnn_cd "$sel" "$CTX" ;;
|
||||||
|
*)
|
||||||
|
# Remove "./" prefix if it exists
|
||||||
|
sel="${sel#./}"
|
||||||
|
|
||||||
|
if [ "$PWD" = "/" ]; then
|
||||||
|
nnn_cd "/$sel" "$CTX"
|
||||||
|
else
|
||||||
|
nnn_cd "$PWD/$sel" "$CTX"
|
||||||
|
fi;;
|
||||||
|
esac
|
||||||
|
fi
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Fuzzy find a command from history,
|
||||||
|
# edit in $EDITOR and run as a command
|
||||||
|
#
|
||||||
|
# Note: Supports only bash and fish history
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
fuzzy=fzf
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
shellname="$(basename "$SHELL")"
|
||||||
|
|
||||||
|
if [ "$shellname" = "bash" ]; then
|
||||||
|
hist_file="$HOME/.bash_history"
|
||||||
|
entry="$("$fuzzy" < "$hist_file")"
|
||||||
|
elif [ "$shellname" = "fish" ]; then
|
||||||
|
hist_file="$HOME/.local/share/fish/fish_history"
|
||||||
|
entry="$(grep "\- cmd: " "$hist_file" | cut -c 8- | "$fuzzy")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$entry" ]; then
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
echo "$entry" >> "$tmpfile"
|
||||||
|
$EDITOR "$tmpfile"
|
||||||
|
|
||||||
|
if [ -s "$tmpfile" ]; then
|
||||||
|
$SHELL -c "$(cat "$tmpfile")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm "$tmpfile"
|
||||||
|
|
||||||
|
printf "Press any key to exit"
|
||||||
|
read -r _
|
||||||
|
fi
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Regular mode:
|
||||||
|
# Fuzzy find a file in directory subtree.
|
||||||
|
# Opens in $VISUAL or $EDITOR if text.
|
||||||
|
# Opens other type of files with xdg-open.
|
||||||
|
# Work only with a single file selected.
|
||||||
|
#
|
||||||
|
# Picker mode:
|
||||||
|
# If picker mode output file is passed, it
|
||||||
|
# will be overwritten with any picked files.
|
||||||
|
# Leaves untouched if no file is picked.
|
||||||
|
# Works with single/multiple files selected.
|
||||||
|
#
|
||||||
|
# Dependencies: fd/find, fzf/skim, xdg-open/open (on macOS)
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke"
|
||||||
|
USE_NUKE=0
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
cmd="$FZF_DEFAULT_COMMAND"
|
||||||
|
if type fd >/dev/null 2>&1; then
|
||||||
|
[ -z "$cmd" ] && cmd="fd -t f 2>/dev/null"
|
||||||
|
else
|
||||||
|
[ -z "$cmd" ] && cmd="find . -type f 2>/dev/null"
|
||||||
|
fi
|
||||||
|
entry="$(eval "$cmd" | fzf -m)"
|
||||||
|
# To show only the file name
|
||||||
|
# entry=$(find . -type f 2>/dev/null | fzf --delimiter / --with-nth=-1 --tiebreak=begin --info=hidden)
|
||||||
|
elif type sk >/dev/null 2>&1; then
|
||||||
|
entry=$(find . -type f 2>/dev/null | sk)
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for picker mode
|
||||||
|
if [ "$3" ]; then
|
||||||
|
if [ "$entry" ]; then
|
||||||
|
case "$entry" in
|
||||||
|
/*) fullpath="$entry" ;;
|
||||||
|
*) fullpath="$PWD/$entry" ;;
|
||||||
|
esac
|
||||||
|
if [ "-" = "$3" ]; then
|
||||||
|
printf "%s\n" "$fullpath"
|
||||||
|
else
|
||||||
|
printf "%s\n" "$fullpath" > "$3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tell `nnn` to clear its internal selection
|
||||||
|
printf "%s" "0p" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$USE_NUKE" -ne 0 ]; then
|
||||||
|
"$NUKE" "$entry"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Open the file (works for a single file only)
|
||||||
|
cmd_file=""
|
||||||
|
cmd_open=""
|
||||||
|
if uname | grep -q "Darwin"; then
|
||||||
|
cmd_file="file -bIL"
|
||||||
|
cmd_open="open"
|
||||||
|
else
|
||||||
|
cmd_file="file -biL"
|
||||||
|
cmd_open="xdg-open"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$($cmd_file "$entry")" in
|
||||||
|
*text*)
|
||||||
|
"${VISUAL:-$EDITOR}" "$entry" ;;
|
||||||
|
*)
|
||||||
|
$cmd_open "$entry" >/dev/null 2>&1 ;;
|
||||||
|
esac
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Fuzzy find and execute nnn plugins (and optionally,
|
||||||
|
# custom scripts located elsewhere).
|
||||||
|
# Description and details of plugins can be previewed
|
||||||
|
# from the fzf interface. Use `?` to toggle preview
|
||||||
|
# pane on and off, ^Up/^Dn to scroll.
|
||||||
|
#
|
||||||
|
# Dependencies: find, fzf, cat (or bat, if installed)
|
||||||
|
#
|
||||||
|
# Note: For better compatibility with as many nnn plugins as possible,
|
||||||
|
# fzplug will first execute the chosen script on the file hovered
|
||||||
|
# in nnn, and upon failure, try to run it with no target (i.e on
|
||||||
|
# an active selection, if present).
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Kabouik
|
||||||
|
|
||||||
|
# Optional scripts sources
|
||||||
|
|
||||||
|
# Leave blank or fill with the absolute path of a folder containing executable
|
||||||
|
# scripts other than nnn plugins (e.g., "$HOME/.local/share/nautilus/scripts",
|
||||||
|
# since there are numerous Nautilus script git repositories).
|
||||||
|
# Add extra variables if needed, make sure you call them in the find command.
|
||||||
|
|
||||||
|
#CUSTOMDIR1="$HOME/.local/share/nautilus/scripts"
|
||||||
|
CUSTOMDIR1=""
|
||||||
|
CUSTOMDIR2=""
|
||||||
|
|
||||||
|
nnnpluginsdir="$HOME/.config/nnn/plugins"
|
||||||
|
|
||||||
|
# Preview with bat if installed
|
||||||
|
if type bat >/dev/null; then
|
||||||
|
BAT="bat --terminal-width='$(tput cols)' --decorations=always --color=always --style='${BAT_STYLE:-header,numbers}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
plugin=$(find "$nnnpluginsdir" "$CUSTOMDIR1" "$CUSTOMDIR2" \
|
||||||
|
-maxdepth 3 -perm -111 -type f 2>/dev/null | fzf --ansi --preview \
|
||||||
|
"${BAT:-cat} {}" --preview-window="right:66%:wrap" --delimiter / \
|
||||||
|
--with-nth -1 --bind="?:toggle-preview")
|
||||||
|
|
||||||
|
# Try running the script on the hovered file, and abort
|
||||||
|
# abort if no plugin was selected (ESC or ^C pressed).
|
||||||
|
err=0
|
||||||
|
if ! [ "$plugin" = "" ]; then
|
||||||
|
"$plugin" "$1" || err=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If attempt with hovered file fails, try without any target
|
||||||
|
# (nnn selections should still be passed to the script in that case)
|
||||||
|
if [ "$err" -eq "1" ]; then
|
||||||
|
clear && "$plugin" || err=2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Abort and show error if both fail
|
||||||
|
if [ "$err" -eq "2" ]; then
|
||||||
|
sep="\n---\n"
|
||||||
|
printf "$sep""Failed to execute '%s'. See error above or try without fzfplug. Press return to continue. " "$plugin" && read -r _ && clear
|
||||||
|
fi
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Update nnn plugins to installed nnn version
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Arun Prakash Jana, KlzXS
|
||||||
|
|
||||||
|
CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/
|
||||||
|
PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins
|
||||||
|
|
||||||
|
merge () {
|
||||||
|
if type nvim >/dev/null 2>&1; then
|
||||||
|
nvim -d "$1" "$2"
|
||||||
|
else
|
||||||
|
vimdiff +0 "$1" "$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt () {
|
||||||
|
printf "%s\n" "Plugin $1 already exists and is different."
|
||||||
|
printf "Keep (k), merge (m), overwrite (o) [default: k]? "
|
||||||
|
read -r operation
|
||||||
|
|
||||||
|
if [ "$operation" = "m" ]; then
|
||||||
|
op="merge"
|
||||||
|
elif [ "$operation" = "o" ]; then
|
||||||
|
op="cp -vRf"
|
||||||
|
else
|
||||||
|
op="true"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$1" = "master" ] ; then
|
||||||
|
VER="master"
|
||||||
|
ARCHIVE_URL=https://github.com/jarun/nnn/archive/master.tar.gz
|
||||||
|
elif type nnn >/dev/null 2>&1; then
|
||||||
|
VER=$(nnn -V)
|
||||||
|
ARCHIVE_URL=https://github.com/jarun/nnn/releases/download/v"$VER"/nnn-v"$VER".tar.gz
|
||||||
|
else
|
||||||
|
echo "nnn is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# backup any earlier plugins
|
||||||
|
if [ -d "$PLUGIN_DIR" ]; then
|
||||||
|
tar -C "$CONFIG_DIR" -czf "$CONFIG_DIR""plugins-$(date '+%Y%m%d%H%M').tar.gz" plugins/
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$PLUGIN_DIR"
|
||||||
|
cd "$CONFIG_DIR" || exit 1
|
||||||
|
curl -Ls "$ARCHIVE_URL" -o nnn-"$VER".tar.gz
|
||||||
|
tar -zxf nnn-"$VER".tar.gz
|
||||||
|
|
||||||
|
cd nnn-"$VER"/plugins || exit 1
|
||||||
|
|
||||||
|
# shellcheck disable=SC2044
|
||||||
|
# We do not use obnoxious names for plugins
|
||||||
|
for f in $(find . -maxdepth 1 \( ! -iname "." ! -iname "*.md" \)); do
|
||||||
|
if [ -f ../../plugins/"$f" ]; then
|
||||||
|
if [ "$(diff --brief "$f" ../../plugins/"$f")" ]; then
|
||||||
|
prompt "$f"
|
||||||
|
$op "$f" ../../plugins/
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cp -vRf "$f" ../../plugins/
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
cd ../.. || exit 1
|
||||||
|
|
||||||
|
rm -rf nnn-"$VER"/ nnn-"$VER".tar.gz
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: cd to the top level of the current git repository in the current context
|
||||||
|
# Dependencies: git
|
||||||
|
# Shell: sh
|
||||||
|
# Author: https://github.com/PatrickF1
|
||||||
|
|
||||||
|
root="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||||
|
if [ -n "$root" ]; then
|
||||||
|
printf "%s" "0c$root" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
printf "Not in a git repository"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Decrypts selected files using gpg. The contents of the
|
||||||
|
# decrypted file are stored in a file with extension .dec
|
||||||
|
#
|
||||||
|
# Note: If an appropriate private key cannot be found gpg silently
|
||||||
|
# prints a message in the background and no files are written.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: KlzXS
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
printf "(s)election/(c)urrent? [default=c] "
|
||||||
|
read -r resp
|
||||||
|
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
files=$(tr '\0' '\n' < "$selection")
|
||||||
|
else
|
||||||
|
files=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$files" | xargs -n1 -I{} gpg --decrypt --output "{}.dec" {}
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Encrypts selected files using gpg. Can encrypt
|
||||||
|
# asymmetrically (key) or symmetrically (passphrase).
|
||||||
|
# If asymmetric encryption is chosen a key can be
|
||||||
|
# chosen from the list of capable public keys using fzf.
|
||||||
|
#
|
||||||
|
# Note: Symmetric encryption only works for a single (current) file as per gpg limitations
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: KlzXS
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
printf "(s)ymmetric, (a)symmetric? [default=a] "
|
||||||
|
read -r symmetry
|
||||||
|
|
||||||
|
if [ "$symmetry" = "s" ]; then
|
||||||
|
gpg --symmetric "$1"
|
||||||
|
else
|
||||||
|
printf "(s)election/(c)urrent? [default=c] "
|
||||||
|
read -r resp
|
||||||
|
|
||||||
|
if [ "$resp" = "s" ]; then
|
||||||
|
files=$(tr '\0' '\n' < "$selection")
|
||||||
|
else
|
||||||
|
files=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
keyids=$(gpg --list-public-keys --with-colons | grep -E "pub:(.*:){10}.*[eE].*:" | awk -F ":" '{print $5}')
|
||||||
|
|
||||||
|
#awk needs literal $10
|
||||||
|
#shellcheck disable=SC2016
|
||||||
|
keyuids=$(printf "%s" "$keyids" | xargs -n1 -I{} sh -c 'gpg --list-key --with-colons "{}" | grep "uid" | awk -F ":" '\''{printf "%s %s\n", "{}", $10}'\''')
|
||||||
|
|
||||||
|
recipient=$(printf "%s" "$keyuids" | fzf | awk '{print $1}')
|
||||||
|
|
||||||
|
printf "%s" "$files" | xargs -n1 gpg --encrypt --recipient "$recipient"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#set -x
|
||||||
|
# Description: Send the selected (or hovered) files to your Android device using gsconnect daemon.js.
|
||||||
|
# GSConnect must be configured on the Android device and the PC.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Darukutsu
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
gsconnect=$HOME/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/daemon.js
|
||||||
|
ids=$($gsconnect -l)
|
||||||
|
|
||||||
|
for id in $ids; do
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
xargs -0 < "$selection" -I{} "$gsconnect" -d "$id" --share-file="{}"
|
||||||
|
# Clear selection
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
"$gsconnect" -d "$id" --share-file="$2/$1"
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Browse Project Gutenberg catalogue by popularity, then download
|
||||||
|
# and read a book of your choice.
|
||||||
|
#
|
||||||
|
# Details: Set the variable EBOOK_ID to download in html format and read in w3m.
|
||||||
|
# Clear EBOOK_ID to browse available ebooks by popularity and set it to
|
||||||
|
# the ID once you find an interesting one.
|
||||||
|
# To download and read in epub format set READER to an epub reader like
|
||||||
|
# epr: https://github.com/wustho/epr
|
||||||
|
#
|
||||||
|
# More on EBOOK_ID:
|
||||||
|
# Wuthering Heights by Emily Brontë is at https://www.gutenberg.org/ebooks/768
|
||||||
|
# So EBOOK_ID would be 768
|
||||||
|
#
|
||||||
|
# Downloaded ebooks are at ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
EBOOK_ID="${EBOOK_ID:-""}"
|
||||||
|
DIR="${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/$EBOOK_ID"
|
||||||
|
BROWSE_LINK="https://www.gutenberg.org/ebooks/search/?sort_order=downloads"
|
||||||
|
BROWSER="${BROWSER:-w3m}"
|
||||||
|
READER="${READER:-""}"
|
||||||
|
|
||||||
|
if [ -n "$EBOOK_ID" ]; then
|
||||||
|
if [ ! -e "$DIR" ]; then
|
||||||
|
mkdir -p "$DIR"
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
|
||||||
|
if [ -z "$READER" ]; then
|
||||||
|
curl -L -O "https://www.gutenberg.org/files/$EBOOK_ID/$EBOOK_ID-h.zip"
|
||||||
|
unzip "$EBOOK_ID"-h.zip
|
||||||
|
else
|
||||||
|
curl -L -o "$EBOOK_ID".epub "https://www.gutenberg.org/ebooks/$EBOOK_ID.epub.noimages"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
if [ -z "$READER" ]; then
|
||||||
|
"$BROWSER" "$DIR/$EBOOK_ID-h/$EBOOK_ID-h.htm"
|
||||||
|
else
|
||||||
|
"$READER" "$DIR/$EBOOK_ID.epub"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
"$BROWSER" "$BROWSE_LINK"
|
||||||
|
fi
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Resize images in a directory to screen resolution with imgp
|
||||||
|
#
|
||||||
|
# Dependencipes: imgp - https://github.com/jarun/imgp
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. Set res to avoid the desktop resolution prompt each time
|
||||||
|
# 2. MINSIZE is set to 1MB by default, adjust it if you want
|
||||||
|
# 3. imgp options used:
|
||||||
|
# a - adaptive mode
|
||||||
|
# c - convert PNG to JPG
|
||||||
|
# k - skip images matching specified hres/vres
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
# set resolution (e.g. 1920x1080)
|
||||||
|
res="${RESOLUTION}"
|
||||||
|
|
||||||
|
# set minimum image size (in bytes) to resize (default: 1MB)
|
||||||
|
MINSIZE="${MINSIZE:-1048576}"
|
||||||
|
|
||||||
|
if [ -z "$res" ]; then
|
||||||
|
printf "desktop resolution (hxv): "
|
||||||
|
read -r res
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$res" ] && [ -n "$MINSIZE" ]; then
|
||||||
|
imgp -ackx "$res" -s "$MINSIZE"
|
||||||
|
fi
|
|
@ -0,0 +1,597 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# The MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) jomo
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
# https://github.com/jomo/imgur-screenshot
|
||||||
|
# https://help.imgur.com/hc/en-us/articles/209592766-Tools-for-Imgur
|
||||||
|
#
|
||||||
|
# Slightly modified for `nnn` integration
|
||||||
|
#
|
||||||
|
# Shell: Bash
|
||||||
|
# Description: Upload an image file to imgur
|
||||||
|
|
||||||
|
if [ "${1}" = "--debug" ]; then
|
||||||
|
echo "########################################"
|
||||||
|
echo "Enabling debug mode"
|
||||||
|
echo "Please remove credentials before pasting"
|
||||||
|
echo "########################################"
|
||||||
|
echo ""
|
||||||
|
uname -a
|
||||||
|
for arg in ${0} "${@}"; do
|
||||||
|
echo -n "'${arg}' "
|
||||||
|
done
|
||||||
|
echo -e "\n"
|
||||||
|
shift
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
current_version="v1.7.4"
|
||||||
|
|
||||||
|
function is_mac() {
|
||||||
|
uname | grep -q "Darwin"
|
||||||
|
}
|
||||||
|
|
||||||
|
### IMGUR-SCREENSHOT DEFAULT CONFIG ####
|
||||||
|
|
||||||
|
# You can override the config in ~/.config/imgur-screenshot/settings.conf
|
||||||
|
|
||||||
|
imgur_anon_id="ea6c0ef2987808e"
|
||||||
|
imgur_icon_path="${HOME}/Pictures/imgur.png"
|
||||||
|
|
||||||
|
imgur_acct_key=""
|
||||||
|
imgur_secret=""
|
||||||
|
login="false"
|
||||||
|
album_title=""
|
||||||
|
album_id=""
|
||||||
|
credentials_file="${HOME}/.config/imgur-screenshot/credentials.conf"
|
||||||
|
|
||||||
|
file_name_format="imgur-%Y_%m_%d-%H:%M:%S.png" # when using scrot, must end with .png!
|
||||||
|
file_dir="${HOME}/Pictures"
|
||||||
|
|
||||||
|
upload_connect_timeout="5"
|
||||||
|
upload_timeout="120"
|
||||||
|
upload_retries="1"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
if is_mac; then
|
||||||
|
screenshot_select_command="screencapture -i %img"
|
||||||
|
screenshot_window_command="screencapture -iWa %img"
|
||||||
|
screenshot_full_command="screencapture %img"
|
||||||
|
open_command="open %url"
|
||||||
|
else
|
||||||
|
screenshot_select_command="scrot -s %img"
|
||||||
|
screenshot_window_command="scrot %img"
|
||||||
|
screenshot_full_command="scrot %img"
|
||||||
|
open_command="xdg-open %url"
|
||||||
|
fi
|
||||||
|
open="true"
|
||||||
|
|
||||||
|
mode="select"
|
||||||
|
edit_command="gimp %img"
|
||||||
|
edit="false"
|
||||||
|
exit_on_album_creation_fail="true"
|
||||||
|
|
||||||
|
log_file="${HOME}/.imgur-screenshot.log"
|
||||||
|
|
||||||
|
auto_delete=""
|
||||||
|
copy_url="true"
|
||||||
|
keep_file="true"
|
||||||
|
check_update="true"
|
||||||
|
|
||||||
|
# NOTICE: if you make changes here, also edit the docs at
|
||||||
|
# https://github.com/jomo/imgur-screenshot/wiki/Config
|
||||||
|
|
||||||
|
# You can override the config in ~/.config/imgur-screenshot/settings.conf
|
||||||
|
|
||||||
|
############## END CONFIG ##############
|
||||||
|
|
||||||
|
settings_path="${HOME}/.config/imgur-screenshot/settings.conf"
|
||||||
|
if [ -f "${settings_path}" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${settings_path}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# dependency check
|
||||||
|
if [ "${1}" = "--check" ]; then
|
||||||
|
(type grep &>/dev/null && echo "OK: found grep") || echo "ERROR: grep not found"
|
||||||
|
if is_mac; then
|
||||||
|
if type growlnotify &>/dev/null; then
|
||||||
|
echo "OK: found growlnotify"
|
||||||
|
elif type terminal-notifier &>/dev/null; then
|
||||||
|
echo "OK: found terminal-notifier"
|
||||||
|
else
|
||||||
|
echo "ERROR: growlnotify nor terminal-notifier found"
|
||||||
|
fi
|
||||||
|
(type screencapture &>/dev/null && echo "OK: found screencapture") || echo "ERROR: screencapture not found"
|
||||||
|
(type pbcopy &>/dev/null && echo "OK: found pbcopy") || echo "ERROR: pbcopy not found"
|
||||||
|
else
|
||||||
|
(type notify-send &>/dev/null && echo "OK: found notify-send") || echo "ERROR: notify-send (from libnotify-bin) not found"
|
||||||
|
(type scrot &>/dev/null && echo "OK: found scrot") || echo "ERROR: scrot not found"
|
||||||
|
(type xclip &>/dev/null && echo "OK: found xclip") || echo "ERROR: xclip not found"
|
||||||
|
fi
|
||||||
|
(type curl &>/dev/null && echo "OK: found curl") || echo "ERROR: curl not found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# notify <'ok'|'error'> <title> <text>
|
||||||
|
function notify() {
|
||||||
|
if is_mac; then
|
||||||
|
if type growlnotify &>/dev/null; then
|
||||||
|
growlnotify --icon "${imgur_icon_path}" --iconpath "${imgur_icon_path}" --title "${2}" --message "${3}"
|
||||||
|
else
|
||||||
|
terminal-notifier -appIcon "${imgur_icon_path}" -contentImage "${imgur_icon_path}" -title "imgur: ${2}" -message "${3}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "${1}" = "error" ]; then
|
||||||
|
notify-send -a ImgurScreenshot -u critical -c "im.error" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}"
|
||||||
|
else
|
||||||
|
notify-send -a ImgurScreenshot -u low -c "transfer.complete" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function take_screenshot() {
|
||||||
|
echo "Please select area"
|
||||||
|
is_mac || sleep 0.1 # https://bbs.archlinux.org/viewtopic.php?pid=1246173#p1246173
|
||||||
|
|
||||||
|
cmd="screenshot_${mode}_command"
|
||||||
|
cmd=${!cmd//\%img/${1}}
|
||||||
|
|
||||||
|
if ! shot_err="$(${cmd} &>/dev/null)"; then #takes a screenshot with selection
|
||||||
|
echo "Failed to take screenshot '${1}': '${shot_err}'. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}"
|
||||||
|
notify error "Something went wrong :(" "Information has been logged"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_for_update() {
|
||||||
|
# exit non-zero on HTTP error, output only the body (no stats) but output errors, follow redirects, output everything to stdout
|
||||||
|
remote_version="$(curl --compressed -fsSL --stderr - "https://api.github.com/repos/jomo/imgur-screenshot/releases" | grep -Em 1 --color 'tag_name":\s*".*"' | cut -d '"' -f 4)"
|
||||||
|
if [ -n "$remote_version" ]; then
|
||||||
|
if [ ! "${current_version}" = "${remote_version}" ] && [ -n "${current_version}" ] && [ -n "${remote_version}" ]; then
|
||||||
|
echo "Update found!"
|
||||||
|
echo "Version ${remote_version} is available (You have ${current_version})"
|
||||||
|
notify ok "Update found" "Version ${remote_version} is available (You have ${current_version}). https://github.com/jomo/imgur-screenshot"
|
||||||
|
echo "Check https://github.com/jomo/imgur-screenshot/releases/${remote_version} for more info."
|
||||||
|
elif [ -z "${current_version}" ] || [ -z "${remote_version}" ]; then
|
||||||
|
echo "Invalid empty version string"
|
||||||
|
echo "Current (local) version: '${current_version}'"
|
||||||
|
echo "Latest (remote) version: '${remote_version}'"
|
||||||
|
else
|
||||||
|
echo "Version ${current_version} is up to date."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to check for latest version: ${remote_version}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_oauth2_client_secrets() {
|
||||||
|
if [ -z "${imgur_acct_key}" ] || [ -z "${imgur_secret}" ]; then
|
||||||
|
echo "In order to upload to your account, register a new application at:"
|
||||||
|
echo "https://api.imgur.com/oauth2/addclient"
|
||||||
|
echo "Select 'OAuth 2 authorization without a callback URL'"
|
||||||
|
echo "Then, set the imgur_acct_key (Client ID) and imgur_secret in your config."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_access_token() {
|
||||||
|
token_expire_time=0
|
||||||
|
# check for saved access_token and its expiration date
|
||||||
|
if [ -f "${credentials_file}" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${credentials_file}"
|
||||||
|
fi
|
||||||
|
current_time="$(date +%s)"
|
||||||
|
preemptive_refresh_time="$((10*60))"
|
||||||
|
expired="$((current_time > (token_expire_time - preemptive_refresh_time)))"
|
||||||
|
if [ -n "${refresh_token}" ]; then
|
||||||
|
# token already set
|
||||||
|
if [ "${expired}" -eq "0" ]; then
|
||||||
|
# token expired
|
||||||
|
refresh_access_token "${credentials_file}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
acquire_access_token "${credentials_file}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function acquire_access_token() {
|
||||||
|
check_oauth2_client_secrets
|
||||||
|
# prompt for a PIN
|
||||||
|
authorize_url="https://api.imgur.com/oauth2/authorize?client_id=${imgur_acct_key}&response_type=pin"
|
||||||
|
echo "Go to"
|
||||||
|
echo "${authorize_url}"
|
||||||
|
echo "and grant access to this application."
|
||||||
|
read -rp "Enter the PIN: " imgur_pin
|
||||||
|
|
||||||
|
if [ -z "${imgur_pin}" ]; then
|
||||||
|
echo "PIN not entered, exiting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# exchange the PIN for access token and refresh token
|
||||||
|
response="$(curl --compressed -fsSL --stderr - \
|
||||||
|
-F "client_id=${imgur_acct_key}" \
|
||||||
|
-F "client_secret=${imgur_secret}" \
|
||||||
|
-F "grant_type=pin" \
|
||||||
|
-F "pin=${imgur_pin}" \
|
||||||
|
https://api.imgur.com/oauth2/token)"
|
||||||
|
save_access_token "${response}" "${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_access_token() {
|
||||||
|
check_oauth2_client_secrets
|
||||||
|
token_url="https://api.imgur.com/oauth2/token"
|
||||||
|
# exchange the refresh token for access_token and refresh_token
|
||||||
|
if ! response="$(curl --compressed -fsSL --stderr - \
|
||||||
|
-F "client_id=${imgur_acct_key}" \
|
||||||
|
-F "client_secret=${imgur_secret}" \
|
||||||
|
-F "grant_type=refresh_token" \
|
||||||
|
-F "refresh_token=${refresh_token}" \
|
||||||
|
"${token_url}"
|
||||||
|
)"; then
|
||||||
|
# curl failed
|
||||||
|
handle_upload_error "${response}" "${token_url}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
save_access_token "${response}" "${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_access_token() {
|
||||||
|
if ! grep -q "access_token" <<<"${1}"; then
|
||||||
|
# server did not send access_token
|
||||||
|
echo "Error: Something is wrong with your credentials:"
|
||||||
|
echo "${1}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
access_token="$(grep -Eo 'access_token":".*"' <<<"${1}" | cut -d '"' -f 3)"
|
||||||
|
refresh_token="$(grep -Eo 'refresh_token":".*"' <<<"${1}" | cut -d '"' -f 3)"
|
||||||
|
expires_in="$(grep -Eo 'expires_in":[0-9]*' <<<"${1}" | cut -d ':' -f 2)"
|
||||||
|
token_expire_time="$(( $(date +%s) + expires_in ))"
|
||||||
|
|
||||||
|
# create dir if not exist
|
||||||
|
mkdir -p "$(dirname "${2}")" 2>/dev/null
|
||||||
|
touch "${2}" && chmod 600 "${2}"
|
||||||
|
cat <<EOF > "${2}"
|
||||||
|
access_token="${access_token}"
|
||||||
|
refresh_token="${refresh_token}"
|
||||||
|
token_expire_time="${token_expire_time}"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_account_info() {
|
||||||
|
response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/account/me)"
|
||||||
|
if grep -Eq '"success":\s*true' <<<"${response}"; then
|
||||||
|
username="$(grep -Eo '"url":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
echo "Logged in as ${username}."
|
||||||
|
echo "https://${username}.imgur.com"
|
||||||
|
else
|
||||||
|
echo "Failed to fetch info: ${response}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_image() {
|
||||||
|
response="$(curl --compressed -X DELETE -fsSL --stderr - -H "Authorization: Client-ID ${1}" "https://api.imgur.com/3/image/${2}")"
|
||||||
|
if grep -Eq '"success":\s*true' <<<"${response}"; then
|
||||||
|
echo "Image successfully deleted (delete hash: ${2})." >> "${3}"
|
||||||
|
else
|
||||||
|
echo "The Image could not be deleted: ${response}." >> "${3}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload_authenticated_image() {
|
||||||
|
echo "Uploading '${1}'..."
|
||||||
|
title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)"
|
||||||
|
if [ -n "${album_id}" ]; then
|
||||||
|
response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)"
|
||||||
|
else
|
||||||
|
response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JSON parser premium edition (not really)
|
||||||
|
if grep -Eq '"success":\s*true' <<<"${response}"; then
|
||||||
|
img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https!
|
||||||
|
del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
|
||||||
|
if [ -n "${auto_delete}" ]; then
|
||||||
|
export -f delete_image
|
||||||
|
echo "Deleting image in ${auto_delete} seconds."
|
||||||
|
nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" &
|
||||||
|
fi
|
||||||
|
|
||||||
|
handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}"
|
||||||
|
else # upload failed
|
||||||
|
err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
test -z "${err_msg}" && err_msg="${response}"
|
||||||
|
handle_upload_error "${err_msg}" "${1}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload_anonymous_image() {
|
||||||
|
echo "Uploading '${1}'..."
|
||||||
|
title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)"
|
||||||
|
if [ -n "${album_id}" ]; then
|
||||||
|
response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" https://api.imgur.com/3/image)"
|
||||||
|
else
|
||||||
|
response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" https://api.imgur.com/3/image)"
|
||||||
|
fi
|
||||||
|
# JSON parser premium edition (not really)
|
||||||
|
if grep -Eq '"success":\s*true' <<<"${response}"; then
|
||||||
|
img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https!
|
||||||
|
del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
|
||||||
|
if [ -n "${auto_delete}" ]; then
|
||||||
|
export -f delete_image
|
||||||
|
echo "Deleting image in ${auto_delete} seconds."
|
||||||
|
nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" &
|
||||||
|
fi
|
||||||
|
|
||||||
|
handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}"
|
||||||
|
else # upload failed
|
||||||
|
err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
test -z "${err_msg}" && err_msg="${response}"
|
||||||
|
handle_upload_error "${err_msg}" "${1}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_upload_success() {
|
||||||
|
echo ""
|
||||||
|
echo "image link: ${1}"
|
||||||
|
echo "delete link: ${2}"
|
||||||
|
|
||||||
|
if [ "${copy_url}" = "true" ] && [ -z "${album_title}" ]; then
|
||||||
|
if is_mac; then
|
||||||
|
echo -n "${1}" | pbcopy
|
||||||
|
else
|
||||||
|
echo -n "${1}" | xclip -selection clipboard
|
||||||
|
fi
|
||||||
|
echo "URL copied to clipboard"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# print to log file: image link, image location, delete link
|
||||||
|
echo -e "${1}\t${3}\t${2}" >> "${log_file}"
|
||||||
|
|
||||||
|
notify ok "Upload done!" "${1}"
|
||||||
|
|
||||||
|
# if [ ! -z "${open_command}" ] && [ "${open}" = "true" ]; then
|
||||||
|
# open_cmd=${open_command//\%url/${1}}
|
||||||
|
# open_cmd=${open_cmd//\%img/${2}}
|
||||||
|
# echo "Opening '${open_cmd}'"
|
||||||
|
# eval "${open_cmd}"
|
||||||
|
# fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_upload_error() {
|
||||||
|
error="Upload failed: \"${1}\""
|
||||||
|
echo "${error}"
|
||||||
|
echo -e "Error\t${2}\t${error}" >> "${log_file}"
|
||||||
|
notify error "Upload failed :(" "${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_album_creation_success() {
|
||||||
|
echo ""
|
||||||
|
echo "Album link: ${1}"
|
||||||
|
echo "Delete hash: ${2}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
notify ok "Album created!" "${1}"
|
||||||
|
|
||||||
|
if [ "${copy_url}" = "true" ]; then
|
||||||
|
if is_mac; then
|
||||||
|
echo -n "${1}" | pbcopy
|
||||||
|
else
|
||||||
|
echo -n "${1}" | xclip -selection clipboard
|
||||||
|
fi
|
||||||
|
echo "URL copied to clipboard"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# print to log file: album link, album title, delete hash
|
||||||
|
echo -e "${1}\t\"${3}\"\t${2}" >> "${log_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_album_creation_error() {
|
||||||
|
error="Album creation failed: \"${1}\""
|
||||||
|
echo -e "Error\t${2}\t${error}" >> "${log_file}"
|
||||||
|
notify error "Album creation failed :(" "${1}"
|
||||||
|
if [ ${exit_on_album_creation_fail} ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ ${#} != 0 ]; do
|
||||||
|
case "${1}" in
|
||||||
|
-h | --help)
|
||||||
|
echo "usage: ${0} [--debug] [-c | --check | -v | -h | -u]"
|
||||||
|
echo " ${0} [--debug] [option]... [file]..."
|
||||||
|
echo ""
|
||||||
|
echo " --debug Enable debugging, must be first option"
|
||||||
|
echo " -h, --help Show this help, exit"
|
||||||
|
echo " -v, --version Show current version, exit"
|
||||||
|
echo " --check Check if all dependencies are installed, exit"
|
||||||
|
echo " -c, --connect Show connected imgur account, exit"
|
||||||
|
echo " -o, --open <true|false> Override 'open' config"
|
||||||
|
echo " -e, --edit <true|false> Override 'edit' config"
|
||||||
|
echo " -i, --edit-command <command> Override 'edit_command' config (include '%img'), sets --edit 'true'"
|
||||||
|
echo " -l, --login <true|false> Override 'login' config"
|
||||||
|
echo " -a, --album <album_title> Create new album and upload there"
|
||||||
|
echo " -A, --album-id <album_id> Override 'album_id' config"
|
||||||
|
echo " -k, --keep-file <true|false> Override 'keep_file' config"
|
||||||
|
echo " -d, --auto-delete <s> Automatically delete image after <s> seconds"
|
||||||
|
echo " -u, --update Check for updates, exit"
|
||||||
|
echo " file Upload file instead of taking a screenshot"
|
||||||
|
exit 0;;
|
||||||
|
-v | --version)
|
||||||
|
echo "${current_version}"
|
||||||
|
exit 0;;
|
||||||
|
-s | --select)
|
||||||
|
mode="select"
|
||||||
|
shift;;
|
||||||
|
-w | --window)
|
||||||
|
mode="window"
|
||||||
|
shift;;
|
||||||
|
-f | --full)
|
||||||
|
mode="full"
|
||||||
|
shift;;
|
||||||
|
-o | --open)
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
open="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-e | --edit)
|
||||||
|
edit="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-i | --edit-command)
|
||||||
|
edit_command="${2}"
|
||||||
|
edit="true"
|
||||||
|
shift 2;;
|
||||||
|
-l | --login)
|
||||||
|
login="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-c | --connect)
|
||||||
|
load_access_token
|
||||||
|
fetch_account_info
|
||||||
|
exit 0;;
|
||||||
|
-a | --album)
|
||||||
|
album_title="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-A | --album-id)
|
||||||
|
album_id="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-k | --keep-file)
|
||||||
|
keep_file="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-d | --auto-delete)
|
||||||
|
auto_delete="${2}"
|
||||||
|
shift 2;;
|
||||||
|
-u | --update)
|
||||||
|
check_for_update
|
||||||
|
exit 0;;
|
||||||
|
*)
|
||||||
|
upload_files=("${@}")
|
||||||
|
break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${login}" = "true" ]; then
|
||||||
|
# load before changing directory
|
||||||
|
load_access_token
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -n "${album_title}" ]; then
|
||||||
|
if [ "${login}" = "true" ]; then
|
||||||
|
response="$(curl -fsSL --stderr - \
|
||||||
|
-F "title=${album_title}" \
|
||||||
|
-H "Authorization: Bearer ${access_token}" \
|
||||||
|
https://api.imgur.com/3/album)"
|
||||||
|
else
|
||||||
|
response="$(curl -fsSL --stderr - \
|
||||||
|
-F "title=${album_title}" \
|
||||||
|
-H "Authorization: Client-ID ${imgur_anon_id}" \
|
||||||
|
https://api.imgur.com/3/album)"
|
||||||
|
fi
|
||||||
|
if grep -Eq '"success":\s*true' <<<"${response}"; then # Album creation successful
|
||||||
|
echo "Album '${album_title}' successfully created"
|
||||||
|
album_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
handle_album_creation_success "https://imgur.com/a/${album_id}" "${del_id}" "${album_title}"
|
||||||
|
|
||||||
|
if [ "${login}" = "false" ]; then
|
||||||
|
album_id="${del_id}"
|
||||||
|
fi
|
||||||
|
else # Album creation failed
|
||||||
|
err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)"
|
||||||
|
test -z "${err_msg}" && err_msg="${response}"
|
||||||
|
handle_album_creation_error "${err_msg}" "${album_title}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${upload_files[*]}" ]; then
|
||||||
|
upload_files[0]=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
for upload_file in "${upload_files[@]}"; do
|
||||||
|
|
||||||
|
if [ -z "${upload_file}" ]; then
|
||||||
|
cd "${file_dir}" || exit 1
|
||||||
|
|
||||||
|
# new filename with date
|
||||||
|
img_file="$(date +"${file_name_format}")"
|
||||||
|
take_screenshot "${img_file}"
|
||||||
|
else
|
||||||
|
# upload file instead of screenshot
|
||||||
|
img_file="${upload_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get full path
|
||||||
|
#cd "$(dirname "$(realpath "${img_file}")")"
|
||||||
|
#img_file="$(realpath "${img_file}")"
|
||||||
|
|
||||||
|
# check if file exists
|
||||||
|
if ! [ -f "${img_file}" ]; then
|
||||||
|
echo "file '${img_file}' doesn't exist !"
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# open image in editor if configured
|
||||||
|
if [ "${edit}" = "true" ]; then
|
||||||
|
edit_cmd=${edit_command//\%img/${img_file}}
|
||||||
|
echo "Opening editor '${edit_cmd}'"
|
||||||
|
if ! (eval "${edit_cmd}"); then
|
||||||
|
echo "Error for image '${img_file}': command '${edit_cmd}' failed, not uploading. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}"
|
||||||
|
notify error "Something went wrong :(" "Information has been logged"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${login}" = "true" ]; then
|
||||||
|
upload_authenticated_image "${img_file}"
|
||||||
|
else
|
||||||
|
upload_anonymous_image "${img_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# delete file if configured
|
||||||
|
if [ "${keep_file}" = "false" ] && [ -z "${1}" ]; then
|
||||||
|
echo "Deleting temp file ${file_dir}/${img_file}"
|
||||||
|
rm -rf "${img_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
if [ "${check_update}" = "true" ]; then
|
||||||
|
check_for_update
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r _
|
|
@ -0,0 +1,113 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Open hovered or current directory in image viewer.
|
||||||
|
# Generates media thumbnails with optional dependencies.
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - imv (https://github.com/eXeC64/imv) or,
|
||||||
|
# - sxiv (https://github.com/muennich/sxiv) or,
|
||||||
|
# - nsxiv (https://codeberg.org/nsxiv/nsxiv) or,
|
||||||
|
# - ucollage (https://github.com/ckardaris/ucollage) or,
|
||||||
|
# - lsix (https://github.com/hackerb9/lsix), or
|
||||||
|
# - viu (https://github.com/atanunq/viu), or
|
||||||
|
# - catimg (https://github.com/posva/catimg), or
|
||||||
|
# - optional: ffmpeg for audio thumbnails (album art)
|
||||||
|
# - optional: ffmpegthumbnailer for video thumbnails
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana, Luuk van Baal
|
||||||
|
#
|
||||||
|
# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews
|
||||||
|
# if you want to keep media thumbnails on disk between reboots.
|
||||||
|
NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-${TMPDIR:-/tmp}/nnn/previews}"
|
||||||
|
|
||||||
|
exit_prompt() {
|
||||||
|
[ -n "$1" ] && printf "%s\n" "$1"
|
||||||
|
printf "%s" "Press any key to exit..."
|
||||||
|
cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg"
|
||||||
|
clear
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
make_thumbs() {
|
||||||
|
mkdir -p "$NNN_PREVIEWDIR$dir" || return
|
||||||
|
if [ "$1" = "viu" ] || [ "$1" = "catimg" ]; then
|
||||||
|
[ -d "$target" ] && exit_prompt "$1 can only display a single image"
|
||||||
|
mime="$(file -bL --mime-type -- "$target")"
|
||||||
|
case "$mime" in
|
||||||
|
audio/*) ffmpeg -i "$target" "$NNN_PREVIEWDIR$target.jpg" -y >/dev/null 2>&1
|
||||||
|
ret="$NNN_PREVIEWDIR/$target.jpg" ;;
|
||||||
|
video/*) ffmpegthumbnailer -i "$target" -o "$NNN_PREVIEWDIR$target.jpg" 2> /dev/null
|
||||||
|
ret="$NNN_PREVIEWDIR/$target.jpg" ;;
|
||||||
|
*) ret="$target" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
for file in "$dir"/*; do
|
||||||
|
if [ ! -f "$NNN_PREVIEWDIR$file.jpg" ]; then
|
||||||
|
case "$(file -bL --mime-type -- "$file")" in
|
||||||
|
audio/*) [ "$1" != "sxiv" ] &&
|
||||||
|
ffmpeg -i "$file" "$NNN_PREVIEWDIR$file.jpg" -y >/dev/null 2>&1 ;;
|
||||||
|
video/*) [ "$1" != "ucollage" ] &&
|
||||||
|
ffmpegthumbnailer -i "$file" -o "$NNN_PREVIEWDIR$file.jpg" 2> /dev/null ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for file in "$NNN_PREVIEWDIR$dir"/*; do
|
||||||
|
filename="$(basename "$file" .jpg)"
|
||||||
|
[ ! -e "$dir/$filename" ] && rm "$file" 2>/dev/null
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
listimages() {
|
||||||
|
find -L "$dir" "$NNN_PREVIEWDIR$dir" -maxdepth 1 -type f -print0 2>/dev/null | sort -z
|
||||||
|
}
|
||||||
|
|
||||||
|
view_files() {
|
||||||
|
[ -f "$target" ] && count="-n $(listimages | grep -a -m 1 -ZznF "$target" | cut -d: -f1)"
|
||||||
|
case "$1" in
|
||||||
|
nsxiv) listimages | xargs -0 nsxiv -a "${count:--t}" -- ;;
|
||||||
|
sxiv) listimages | xargs -0 sxiv -a "${count:--t}" -- ;;
|
||||||
|
imv*) listimages | xargs -0 "$1" "${count:-}" -- ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
target="$(readlink -f "$1")"
|
||||||
|
[ -d "$target" ] && dir="$target" || dir="${target%/*}"
|
||||||
|
if uname | grep -q "Darwin"; then
|
||||||
|
[ -f "$1" ] && open "$1" >/dev/null 2>&1 &
|
||||||
|
elif type lsix >/dev/null 2>&1; then
|
||||||
|
if [ -d "$target" ]; then
|
||||||
|
cd "$target" || exit_prompt
|
||||||
|
fi
|
||||||
|
make_thumbs lsix
|
||||||
|
clear
|
||||||
|
lsix
|
||||||
|
cd "$NNN_PREVIEWDIR$dir" && lsix
|
||||||
|
exit_prompt
|
||||||
|
elif type ucollage >/dev/null 2>&1; then
|
||||||
|
type ffmpeg >/dev/null 2>&1 && make_thumbs ucollage
|
||||||
|
UCOLLAGE_EXPAND_DIRS=1 ucollage "$dir" "$NNN_PREVIEWDIR$dir" || exit_prompt
|
||||||
|
elif type sxiv >/dev/null 2>&1; then
|
||||||
|
type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs sxiv
|
||||||
|
view_files sxiv >/dev/null 2>&1 &
|
||||||
|
elif type nsxiv >/dev/null 2>&1; then
|
||||||
|
type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs sxiv
|
||||||
|
view_files nsxiv >/dev/null 2>&1 &
|
||||||
|
elif type imv >/dev/null 2>&1; then
|
||||||
|
make_thumbs imv
|
||||||
|
view_files imv >/dev/null 2>&1 &
|
||||||
|
elif type imvr >/dev/null 2>&1; then
|
||||||
|
make_thumbs imv
|
||||||
|
view_files imvr >/dev/null 2>&1 &
|
||||||
|
elif type viu >/dev/null 2>&1; then
|
||||||
|
clear
|
||||||
|
make_thumbs viu
|
||||||
|
viu -n "$ret"
|
||||||
|
exit_prompt
|
||||||
|
elif type catimg >/dev/null 2>&1; then
|
||||||
|
make_thumbs catimg
|
||||||
|
catimg "$ret"
|
||||||
|
exit_prompt
|
||||||
|
else
|
||||||
|
exit_prompt "Please install sxiv/nsxiv/imv/viu/catimg/lsix."
|
||||||
|
fi
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Shows the external IP address and whois information. Useful over VPNs.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
IP=$(curl -s ifconfig.me)
|
||||||
|
|
||||||
|
whois "$IP"
|
||||||
|
echo your external IP address is "$IP"
|
||||||
|
|
||||||
|
read -r _
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Send the selected files to your Android device using kdeconnect-cli.
|
||||||
|
# kdeconnect must be configured on the Android device and the PC.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: juacq97
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
id=$(kdeconnect-cli -a --id-only | awk '{print $1}')
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
kdeconnect-cli -d "$id" --share "$(cat "$selection")"
|
||||||
|
|
||||||
|
# If you want a system notification, uncomment the next 3 lines.
|
||||||
|
#notify-send -a "Kdeconnect" "Sending $(cat "$selection")"
|
||||||
|
#else
|
||||||
|
#notify-send -a "Kdeconnect" "No file selected"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Independent POSIX-compliant GUI application launcher.
|
||||||
|
# Fuzzy find executables in $PATH and launch an application.
|
||||||
|
# stdin, stdout, stderr are suppressed so CLI tools exit silently.
|
||||||
|
#
|
||||||
|
# To configure launch as an independent app launcher add a keybind
|
||||||
|
# to open launch in a terminal e.g.,
|
||||||
|
#
|
||||||
|
# xfce4-terminal -e "${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/launch
|
||||||
|
#
|
||||||
|
# Dependencies: fzf
|
||||||
|
#
|
||||||
|
# Usage: launch [delay]
|
||||||
|
# delay is in seconds, if omitted launch waits for 1 sec
|
||||||
|
#
|
||||||
|
# Integration with nnn: launch is installed with other plugins, nnn picks it up.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
|
||||||
|
IFS=':'
|
||||||
|
|
||||||
|
get_selection() {
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
{ IFS=':'; ls -H $PATH; } | sort | fzf
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection=$( get_selection ); then
|
||||||
|
setsid "$selection" 2>/dev/null 1>/dev/null &
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
sleep "$1"
|
||||||
|
else
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Find and list files by mime type in smart context
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
. "$(dirname "$0")"/.nnn-plugin-helper
|
||||||
|
|
||||||
|
printf "mime (e.g., video/audio/image): "
|
||||||
|
read -r mime
|
||||||
|
|
||||||
|
printf "%s" "+l" > "$NNN_PIPE"
|
||||||
|
find . | file -if- | grep "$mime" | awk -F: '{printf "%s\0", $1}' > "$NNN_PIPE"
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Fetches the lyrics of the track currently playing in MOC
|
||||||
|
#
|
||||||
|
# Dependencies: ddgr (https://github.com/jarun/ddgr)
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
# Check if MOC server is running
|
||||||
|
cmd=$(pgrep -x mocp 2>/dev/null)
|
||||||
|
ret=$cmd
|
||||||
|
if [ -z "$ret" ]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Grab the output
|
||||||
|
out="$(mocp -i)"
|
||||||
|
|
||||||
|
# Check if anything is playing
|
||||||
|
state=$(echo "$out" | grep "State:" | cut -d' ' -f2)
|
||||||
|
if ! [ "$state" = 'PLAY' ]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try by Artist and Song Title first
|
||||||
|
ARTIST="$(echo "$out" | grep 'Artist:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')"
|
||||||
|
TITLE="$(echo "$out" | grep 'SongTitle:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')"
|
||||||
|
|
||||||
|
if [ -n "$ARTIST" ] && [ -n "$TITLE" ]; then
|
||||||
|
ddgr -w azlyrics.com --ducky "$ARTIST" "$TITLE"
|
||||||
|
else
|
||||||
|
# Try by file name
|
||||||
|
FILENAME="$(basename "$(echo "$out" | grep 'File:' | cut -d':' -f2)")"
|
||||||
|
FILENAME="$(echo "${FILENAME%%.*}" | tr -d -)"
|
||||||
|
|
||||||
|
if [ -n "$FILENAME" ]; then
|
||||||
|
ddgr -w azlyrics.com --ducky "$FILENAME"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Appends and optionally plays music in MOC
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - if selection is available, plays it, else plays the current file or directory
|
||||||
|
# - appends tracks and exits is MOC is running, else clears playlist and adds tracks
|
||||||
|
# - to let mocp shuffle tracks, set SHUFFLE=1
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Arun Prakash Jana, ath3
|
||||||
|
|
||||||
|
IFS="$(printf '\n\r')"
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
cmd=$(pgrep -x mocp 2>/dev/null)
|
||||||
|
ret=$cmd
|
||||||
|
|
||||||
|
SHUFFLE="${SHUFFLE:-0}"
|
||||||
|
|
||||||
|
mocp_add ()
|
||||||
|
{
|
||||||
|
if [ "$SHUFFLE" = 1 ]; then
|
||||||
|
if [ "$resp" = "y" ]; then
|
||||||
|
arr=$(tr '\0' '\n' < "$selection")
|
||||||
|
elif [ -n "$1" ]; then
|
||||||
|
arr="$1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for entry in $arr
|
||||||
|
do
|
||||||
|
if [ -d "$entry" ]; then
|
||||||
|
arr2=$arr2$(find "$entry" -type f \( ! -iname "*.m3u" ! -iname "*.pls" \))
|
||||||
|
elif echo "$entry" | grep -qv '\.m3u$\|\.pls$' ; then
|
||||||
|
arr2=$(printf "%s\n%s" "$entry" "$arr2")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
mocp -o shuffle
|
||||||
|
echo "$arr2" | xargs -d "\n" mocp -a
|
||||||
|
else
|
||||||
|
if [ "$resp" = "y" ]; then
|
||||||
|
xargs < "$selection" -0 mocp -a
|
||||||
|
else
|
||||||
|
mocp -a "$1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -s "$selection" ] && [ -z "$1" ]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$2" = "opener" ]; then
|
||||||
|
:
|
||||||
|
elif [ -s "$selection" ]; then
|
||||||
|
printf "Work with selection? Enter 'y' to confirm: "
|
||||||
|
read -r resp
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ret" ]; then
|
||||||
|
# mocp not running
|
||||||
|
mocp -S
|
||||||
|
else
|
||||||
|
# mocp running, check if it's playing
|
||||||
|
state=$(mocp -i | grep "State:" | cut -d' ' -f2)
|
||||||
|
|
||||||
|
if [ "$state" = 'PLAY' ]; then
|
||||||
|
# add to playlist and exit
|
||||||
|
mocp_add "$1"
|
||||||
|
|
||||||
|
# uncomment the line below to show mocp interface after appending
|
||||||
|
# mocp
|
||||||
|
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# clear selection and play
|
||||||
|
mocp -c
|
||||||
|
mocp_add "$1" "$resp"
|
||||||
|
mocp -p
|
||||||
|
|
||||||
|
# uncomment the line below to show mocp interface after appending
|
||||||
|
# mocp
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ "$resp" = "y" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Extract audio from multimedia files and convert to mp3
|
||||||
|
#
|
||||||
|
# Dependencies: ffmpeg compiled with libmp3lame audio codec support
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
outdir=_mp3files
|
||||||
|
|
||||||
|
handle_multimedia() {
|
||||||
|
mime="${1}"
|
||||||
|
file="${2}"
|
||||||
|
|
||||||
|
case "${mime}" in
|
||||||
|
audio/* | video/*)
|
||||||
|
ffmpeg -i "${file}" -vn -codec:a libmp3lame -q:a 2 "${outdir}/${file%.*}.mp3"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "Process 'a'll in directory or 'c'urrent? "
|
||||||
|
read -r resp
|
||||||
|
|
||||||
|
if [ "$resp" = "a" ]; then
|
||||||
|
if ! [ -e "${outdir}" ]; then
|
||||||
|
mkdir "${outdir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for f in *; do
|
||||||
|
if [ -f "${f}" ]; then
|
||||||
|
mimestr="$( file --dereference --brief --mime-type -- "${f}" )"
|
||||||
|
handle_multimedia "${mimestr}" "${f}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif [ "$resp" = "c" ] && [ -f "$1" ]; then
|
||||||
|
ffmpeg -i "${1}" -vn -codec:a libmp3lame -q:a 2 "${1%.*}.mp3"
|
||||||
|
fi
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Toggle mount of MTP device (eg. Android device)
|
||||||
|
# 'l' to list mountable devices
|
||||||
|
# 'n' integer associated to device to mount
|
||||||
|
# 'q'/'Return' exit
|
||||||
|
#
|
||||||
|
# Dependencies: gvfs-mtp
|
||||||
|
#
|
||||||
|
# Notes: The MTP device should be mounted at /run/user/$UID/gvfs.
|
||||||
|
# Put /run/user/$UID/gvfs to bookmark entries (NNN_BMS) for faster access.
|
||||||
|
# Make sure the device is unlocked when mounting.
|
||||||
|
#
|
||||||
|
# When doing copy-paste into MTP device, you will get an error like this:
|
||||||
|
# cp: preserving times for './gambar1.png': Operation not supported
|
||||||
|
# That just means the file is copied but timestamp won't be preserved.
|
||||||
|
# It's like doing `cp -p localfile.txt file-to-SMB.txt`.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Benawi Adha
|
||||||
|
|
||||||
|
prompt="Device number ('l' to list): "
|
||||||
|
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
|
||||||
|
lsmtp () {
|
||||||
|
devs=$(gio mount -li | grep -e 'activation_root' | sed 's/\s*activation_root=//g')
|
||||||
|
c=1
|
||||||
|
printf "Devices list:\n"
|
||||||
|
for i in $devs; do
|
||||||
|
printf "%s %s\\n" "$c" "$i"
|
||||||
|
c=$(( c + 1 ))
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
lsmtp
|
||||||
|
printf "%s" "$prompt"
|
||||||
|
read -r input
|
||||||
|
|
||||||
|
while [ -n "$input" ]
|
||||||
|
do
|
||||||
|
if [ "$input" = "l" ]; then
|
||||||
|
lsmtp
|
||||||
|
elif [ "$input" = "q" ] || [ "$input" -eq 0 ]; then
|
||||||
|
exit
|
||||||
|
elif [ "$input" -le "$(printf '%s\n' "${devs}" | grep -c '^')" ]; then
|
||||||
|
# dev=$(printf "%s\n" "$devs" | cut -d$'\n' -f${input})
|
||||||
|
c=1
|
||||||
|
for i in $devs; do
|
||||||
|
dev=$i
|
||||||
|
if [ "$input" -eq $c ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
c=$(( c + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
if (gio mount -l | grep '^Mount([1-9]).*'"$dev" ) 1>/dev/null; then
|
||||||
|
if gio mount -u "${dev}"; then
|
||||||
|
printf "%s unmounted\n" "$dev"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if gio mount "${dev}"; then
|
||||||
|
printf "%s mounted to /run/user/\$UID/gvfs\n" "$dev"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
printf "Invalid input\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$prompt"
|
||||||
|
read -r input
|
||||||
|
done
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Backup nnn configuration
|
||||||
|
# - config dir content
|
||||||
|
# - environment config
|
||||||
|
# - shell functions and aliases
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Léo Villeveygoux
|
||||||
|
|
||||||
|
nnn_aliases="n nnn"
|
||||||
|
|
||||||
|
outdir="nnn-$(whoami)@$(hostname)"
|
||||||
|
|
||||||
|
outfile="${outdir}.tar.bz2"
|
||||||
|
|
||||||
|
shellname="$(basename "$SHELL")"
|
||||||
|
|
||||||
|
conffile="config.txt"
|
||||||
|
|
||||||
|
configdir="${XDG_CONFIG_HOME:-$HOME/.config}/nnn"
|
||||||
|
|
||||||
|
workdir="$PWD"
|
||||||
|
|
||||||
|
tempdir="$(mktemp -d)"
|
||||||
|
|
||||||
|
mkdir "$tempdir/$outdir"
|
||||||
|
|
||||||
|
if [ ! -d "$tempdir" ]; then
|
||||||
|
echo "Can't create work directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$tempdir/$outdir" || exit 1
|
||||||
|
|
||||||
|
# Backing up config dir content
|
||||||
|
cp -r "$configdir" . || exit 1
|
||||||
|
|
||||||
|
# Environment config
|
||||||
|
env | sed "s/'/'\\\\''/" |\
|
||||||
|
awk '/^NNN_/{print "export '\''"$0"'\''"}' > "$conffile"
|
||||||
|
|
||||||
|
# Shell functions/aliases
|
||||||
|
case "$shellname" in
|
||||||
|
bash)
|
||||||
|
for name in $nnn_aliases ; do
|
||||||
|
if [ "$(bash -ic "type -t $name")" = "function" ] ; then
|
||||||
|
bash -ic "type $name" | tail -n+2 >> "$conffile"
|
||||||
|
elif bash -ic "alias $name" >/dev/null 2>&1 ; then
|
||||||
|
bash -ic "alias $name" >> "$conffile"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
zsh)
|
||||||
|
for name in $nnn_aliases ; do
|
||||||
|
if zsh -ic "functions $name" ; then
|
||||||
|
zsh -ic "functions $name" >> "$conffile"
|
||||||
|
elif zsh -ic "alias $name" ; then
|
||||||
|
echo alias "$(zsh -ic "alias $name")" >> "$conffile"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Unknown shell, skipping alias/function checking." >&2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cd .. || exit 1
|
||||||
|
|
||||||
|
printf "Saving as '%s' ... " "$workdir/$outfile"
|
||||||
|
|
||||||
|
tar caf "$workdir/$outfile" "$outdir" && echo "Done" || echo "Failed"
|
||||||
|
|
||||||
|
cd "$workdir" && rm -rf "$tempdir"
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Toggle mount status of a device using pmount
|
||||||
|
# If the device is not mounted, it will be mounted.
|
||||||
|
# If the device is mounted, it will be unmounted and powered down.
|
||||||
|
#
|
||||||
|
# Dependencies: lsblk, pmount
|
||||||
|
#
|
||||||
|
# Usage: Runs `lsblk` on 'l', exits on 'Return`.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - The script uses Linux-specific lsblk to list block devices. Alternatives:
|
||||||
|
# macOS: "diskutil list"
|
||||||
|
# BSD: "geom disk list"
|
||||||
|
# - The script uses udisksctl (from udisks2) to power down devices. This is also Linux-specific.
|
||||||
|
# Users on non-Linux platforms can comment it and use an alterntive to power-down disks.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
prompt="device name [e.g. sdXn] ('l'ist, 'q'uit): "
|
||||||
|
|
||||||
|
lsblk
|
||||||
|
|
||||||
|
printf "\nEnsure you aren't still in the mounted device.\n"
|
||||||
|
printf "%s" "$prompt"
|
||||||
|
read -r dev
|
||||||
|
|
||||||
|
while [ -n "$dev" ]
|
||||||
|
do
|
||||||
|
if [ "$dev" = "l" ]; then
|
||||||
|
lsblk
|
||||||
|
elif [ "$dev" = "q" ]; then
|
||||||
|
exit
|
||||||
|
else
|
||||||
|
if grep -qs "$dev " /proc/mounts; then
|
||||||
|
sync
|
||||||
|
if pumount "$dev"
|
||||||
|
then
|
||||||
|
echo "$dev" unmounted.
|
||||||
|
if udisksctl power-off -b /dev/"$dev"
|
||||||
|
then
|
||||||
|
echo "$dev" ejected.
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pmount "$dev"
|
||||||
|
echo "$dev" mounted to "$(lsblk -n /dev/"$dev" | rev | cut -d' ' -f1 | rev)".
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
printf "%s" "$prompt"
|
||||||
|
read -r dev
|
||||||
|
done
|
|
@ -0,0 +1,555 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Sample script to play files in apps by file type or mime
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Usage: nuke filepath
|
||||||
|
#
|
||||||
|
# Integration with nnn:
|
||||||
|
# 1. Export the required config:
|
||||||
|
# export NNN_OPENER=/absolute/path/to/nuke
|
||||||
|
# # Otherwise, if nuke is in $PATH
|
||||||
|
# # export NNN_OPENER=nuke
|
||||||
|
# 2. Run nnn with the program option to indicate a CLI opener
|
||||||
|
# nnn -c
|
||||||
|
# # The -c program option overrides option -e
|
||||||
|
# 3. nuke can use nnn plugins (e.g. mocq is used for audio), $PATH is updated.
|
||||||
|
#
|
||||||
|
# Details:
|
||||||
|
# Inspired by ranger's scope.sh, modified for usage with nnn.
|
||||||
|
#
|
||||||
|
# Guards against accidentally opening mime types like executables, shared libs etc.
|
||||||
|
#
|
||||||
|
# Tries to play 'file' (1st argument) in the following order:
|
||||||
|
# 1. by extension
|
||||||
|
# 2. by mime (image, video, audio, pdf)
|
||||||
|
# 3. by mime (other file types)
|
||||||
|
# 4. by mime (prompt and run executables)
|
||||||
|
#
|
||||||
|
# Modification tips:
|
||||||
|
# 1. Invokes CLI utilities by default. Set GUI to 1 to enable GUI apps.
|
||||||
|
# 2. PAGER is "less -R".
|
||||||
|
# 3. Start GUI apps in bg to unblock. Redirect stdout and strerr if required.
|
||||||
|
# 4. Some CLI utilities are piped to the $PAGER, to wait and quit uniformly.
|
||||||
|
# 5. If the output cannot be paged use "read -r _" to wait for user input.
|
||||||
|
# 6. On a DE, try 'xdg-open' or 'open' in handle_fallback() as last resort.
|
||||||
|
#
|
||||||
|
# Feel free to change the utilities to your favourites and add more mimes.
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# By extension (only the enabled ones):
|
||||||
|
# most archives: list with atool, bsdtar
|
||||||
|
# rar: list with unrar
|
||||||
|
# 7-zip: list with 7z
|
||||||
|
# pdf: zathura (GUI), pdftotext, mutool, exiftool
|
||||||
|
# audio: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool
|
||||||
|
# avi|mkv|mp4: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool
|
||||||
|
# log: vi
|
||||||
|
# torrent: rtorrent, transmission-show
|
||||||
|
# odt|ods|odp|sxw: odt2txt
|
||||||
|
# md: glow (https://github.com/charmbracelet/glow), lowdown (https://kristaps.bsd.lv/lowdown)
|
||||||
|
# htm|html|xhtml: w3m, lynx, elinks
|
||||||
|
# json: jq, python (json.tool module)
|
||||||
|
# Multimedia by mime:
|
||||||
|
# image/*: imv/sxiv/nsxiv (GUI), viu (https://github.com/atanunq/viu), img2txt, exiftool
|
||||||
|
# video/*: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool
|
||||||
|
# audio/*: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool
|
||||||
|
# application/pdf: zathura (GUI), pdftotext, mutool, exiftool
|
||||||
|
# Other mimes:
|
||||||
|
# text/troff: man -l
|
||||||
|
# text/* | */xml: vi
|
||||||
|
# image/vnd.djvu): djvutxt, exiftool
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# 1. Adapt, test and enable all mimes
|
||||||
|
# 2. Clean-up the unnecessary exit codes
|
||||||
|
|
||||||
|
# set to 1 to enable GUI apps and/or BIN execution
|
||||||
|
GUI="${GUI:-0}"
|
||||||
|
BIN="${BIN:-0}"
|
||||||
|
|
||||||
|
set -euf -o noclobber -o noglob -o nounset
|
||||||
|
IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n
|
||||||
|
|
||||||
|
PATH=$PATH:"${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins"
|
||||||
|
IMAGE_CACHE_PATH="$(dirname "$1")"/.thumbs
|
||||||
|
|
||||||
|
FPATH="$1"
|
||||||
|
FNAME=$(basename "$1")
|
||||||
|
EDITOR="${VISUAL:-${EDITOR:-vi}}"
|
||||||
|
PAGER="${PAGER:-less -R}"
|
||||||
|
ext="${FNAME##*.}"
|
||||||
|
if [ -n "$ext" ]; then
|
||||||
|
ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_mac() {
|
||||||
|
uname | grep -q "Darwin"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_pdf() {
|
||||||
|
if [ "$GUI" -ne 0 ]; then
|
||||||
|
if is_mac; then
|
||||||
|
nohup open "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
elif type zathura >/dev/null 2>&1; then
|
||||||
|
nohup zathura "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
elif type pdftotext >/dev/null 2>&1; then
|
||||||
|
## Preview as text conversion
|
||||||
|
pdftotext -l 10 -nopgbrk -q -- "${FPATH}" - | eval "$PAGER"
|
||||||
|
elif type mutool >/dev/null 2>&1; then
|
||||||
|
mutool draw -F txt -i -- "${FPATH}" 1-10 | eval "$PAGER"
|
||||||
|
elif type exiftool >/dev/null 2>&1; then
|
||||||
|
exiftool "${FPATH}" | eval "$PAGER"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_audio() {
|
||||||
|
if type mocp >/dev/null 2>&1 && type mocq >/dev/null 2>&1; then
|
||||||
|
mocq "${FPATH}" "opener" >/dev/null 2>&1
|
||||||
|
elif type mpv >/dev/null 2>&1; then
|
||||||
|
mpv "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
elif type media_client >/dev/null 2>&1; then
|
||||||
|
media_client play "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
elif type mediainfo >/dev/null 2>&1; then
|
||||||
|
mediainfo "${FPATH}" | eval "$PAGER"
|
||||||
|
elif type exiftool >/dev/null 2>&1; then
|
||||||
|
exiftool "${FPATH}"| eval "$PAGER"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_video() {
|
||||||
|
if [ "$GUI" -ne 0 ]; then
|
||||||
|
if is_mac; then
|
||||||
|
nohup open "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
elif type smplayer >/dev/null 2>&1; then
|
||||||
|
nohup smplayer "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
elif type mpv >/dev/null 2>&1; then
|
||||||
|
nohup mpv "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
elif type ffmpegthumbnailer >/dev/null 2>&1; then
|
||||||
|
# Thumbnail
|
||||||
|
[ -d "${IMAGE_CACHE_PATH}" ] || mkdir "${IMAGE_CACHE_PATH}"
|
||||||
|
ffmpegthumbnailer -i "${FPATH}" -o "${IMAGE_CACHE_PATH}/${FNAME}.jpg" -s 0
|
||||||
|
viu -n "${IMAGE_CACHE_PATH}/${FNAME}.jpg" | eval "$PAGER"
|
||||||
|
elif type mediainfo >/dev/null 2>&1; then
|
||||||
|
mediainfo "${FPATH}" | eval "$PAGER"
|
||||||
|
elif type exiftool >/dev/null 2>&1; then
|
||||||
|
exiftool "${FPATH}"| eval "$PAGER"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# handle this extension and exit
|
||||||
|
handle_extension() {
|
||||||
|
case "${ext}" in
|
||||||
|
## Archive
|
||||||
|
a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
|
||||||
|
rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)
|
||||||
|
if type atool >/dev/null 2>&1; then
|
||||||
|
atool --list -- "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type bsdtar >/dev/null 2>&1; then
|
||||||
|
bsdtar --list --file "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
rar)
|
||||||
|
if type unrar >/dev/null 2>&1; then
|
||||||
|
## Avoid password prompt by providing empty password
|
||||||
|
unrar lt -p- -- "${FPATH}" | eval "$PAGER"
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
7z)
|
||||||
|
if type 7z >/dev/null 2>&1; then
|
||||||
|
## Avoid password prompt by providing empty password
|
||||||
|
7z l -p -- "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## PDF
|
||||||
|
pdf)
|
||||||
|
handle_pdf
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
aac|flac|m4a|mid|midi|mpa|mp2|mp3|ogg|wav|wma)
|
||||||
|
handle_audio
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Video
|
||||||
|
avi|mkv|mp4)
|
||||||
|
handle_video
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Log files
|
||||||
|
log)
|
||||||
|
"$EDITOR" "${FPATH}"
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
## BitTorrent
|
||||||
|
torrent)
|
||||||
|
if type rtorrent >/dev/null 2>&1; then
|
||||||
|
rtorrent "${FPATH}"
|
||||||
|
exit 0
|
||||||
|
elif type transmission-show >/dev/null 2>&1; then
|
||||||
|
transmission-show -- "${FPATH}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## OpenDocument
|
||||||
|
odt|ods|odp|sxw)
|
||||||
|
if type odt2txt >/dev/null 2>&1; then
|
||||||
|
## Preview as text conversion
|
||||||
|
odt2txt "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Markdown
|
||||||
|
md)
|
||||||
|
if type glow >/dev/null 2>&1; then
|
||||||
|
glow -sdark "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type lowdown >/dev/null 2>&1; then
|
||||||
|
lowdown -Tterm "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
## HTML
|
||||||
|
htm|html|xhtml)
|
||||||
|
## Preview as text conversion
|
||||||
|
if type w3m >/dev/null 2>&1; then
|
||||||
|
w3m -dump "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type lynx >/dev/null 2>&1; then
|
||||||
|
lynx -dump -- "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type elinks >/dev/null 2>&1; then
|
||||||
|
elinks -dump "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
## JSON
|
||||||
|
json)
|
||||||
|
if type jq >/dev/null 2>&1; then
|
||||||
|
jq --color-output . "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type python >/dev/null 2>&1; then
|
||||||
|
python -m json.tool -- "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# sets the variable abs_target, this should be faster than calling printf
|
||||||
|
abspath() {
|
||||||
|
case "$1" in
|
||||||
|
/*) abs_target="$1";;
|
||||||
|
*) abs_target="$PWD/$1";;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# storing the result to a tmp file is faster than calling listimages twice
|
||||||
|
listimages() {
|
||||||
|
find -L "///${1%/*}" -maxdepth 1 -type f -print0 |
|
||||||
|
grep -izZE '\.(jpe?g|png|gif|webp|tiff|bmp|ico|svg)$' |
|
||||||
|
sort -z | tee "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_dir() {
|
||||||
|
abspath "$2"
|
||||||
|
tmp="${TMPDIR:-/tmp}/nuke_$$"
|
||||||
|
trap 'rm -f $tmp' EXIT
|
||||||
|
count="$(listimages "$abs_target" | grep -a -m 1 -ZznF "$abs_target" | cut -d: -f1)"
|
||||||
|
|
||||||
|
if [ -n "$count" ]; then
|
||||||
|
if [ "$GUI" -ne 0 ]; then
|
||||||
|
xargs -0 nohup "$1" -n "$count" -- < "$tmp"
|
||||||
|
else
|
||||||
|
xargs -0 "$1" -n "$count" -- < "$tmp"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
shift
|
||||||
|
"$1" -- "$@" # fallback
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_multimedia() {
|
||||||
|
## Size of the preview if there are multiple options or it has to be
|
||||||
|
## rendered from vector graphics. If the conversion program allows
|
||||||
|
## specifying only one dimension while keeping the aspect ratio, the width
|
||||||
|
## will be used.
|
||||||
|
# local DEFAULT_SIZE="1920x1080"
|
||||||
|
|
||||||
|
mimetype="${1}"
|
||||||
|
case "${mimetype}" in
|
||||||
|
## SVG
|
||||||
|
# image/svg+xml|image/svg)
|
||||||
|
# convert -- "${FPATH}" "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# exit 1;;
|
||||||
|
|
||||||
|
## DjVu
|
||||||
|
# image/vnd.djvu)
|
||||||
|
# ddjvu -format=tiff -quality=90 -page=1 -size="${DEFAULT_SIZE}" \
|
||||||
|
# - "${IMAGE_CACHE_PATH}" < "${FPATH}" \
|
||||||
|
# && exit 6 || exit 1;;
|
||||||
|
|
||||||
|
## Image
|
||||||
|
image/*)
|
||||||
|
if [ "$GUI" -ne 0 ]; then
|
||||||
|
if is_mac; then
|
||||||
|
nohup open "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
elif type imv >/dev/null 2>&1; then
|
||||||
|
load_dir imv "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
elif type imvr >/dev/null 2>&1; then
|
||||||
|
load_dir imvr "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
elif type sxiv >/dev/null 2>&1; then
|
||||||
|
load_dir sxiv "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
elif type nsxiv >/dev/null 2>&1; then
|
||||||
|
load_dir nsxiv "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
elif type viu >/dev/null 2>&1; then
|
||||||
|
viu -n "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type img2txt >/dev/null 2>&1; then
|
||||||
|
img2txt --gamma=0.6 -- "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type exiftool >/dev/null 2>&1; then
|
||||||
|
exiftool "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# local orientation
|
||||||
|
# orientation="$( identify -format '%[EXIF:Orientation]\n' -- "${FPATH}" )"
|
||||||
|
## If orientation data is present and the image actually
|
||||||
|
## needs rotating ("1" means no rotation)...
|
||||||
|
# if [[ -n "$orientation" && "$orientation" != 1 ]]; then
|
||||||
|
## ...auto-rotate the image according to the EXIF data.
|
||||||
|
# convert -- "${FPATH}" -auto-orient "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# fi
|
||||||
|
|
||||||
|
## `w3mimgdisplay` will be called for all images (unless overridden
|
||||||
|
## as above), but might fail for unsupported types.
|
||||||
|
exit 7;;
|
||||||
|
|
||||||
|
## PDF
|
||||||
|
application/pdf)
|
||||||
|
handle_pdf
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
audio/*)
|
||||||
|
handle_audio
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
## Video
|
||||||
|
video/*)
|
||||||
|
handle_video
|
||||||
|
exit 1;;
|
||||||
|
|
||||||
|
# pdftoppm -f 1 -l 1 \
|
||||||
|
# -scale-to-x "${DEFAULT_SIZE%x*}" \
|
||||||
|
# -scale-to-y -1 \
|
||||||
|
# -singlefile \
|
||||||
|
# -jpeg -tiffcompression jpeg \
|
||||||
|
# -- "${FPATH}" "${IMAGE_CACHE_PATH%.*}" \
|
||||||
|
# && exit 6 || exit 1;;
|
||||||
|
|
||||||
|
|
||||||
|
## ePub, MOBI, FB2 (using Calibre)
|
||||||
|
# application/epub+zip|application/x-mobipocket-ebook|\
|
||||||
|
# application/x-fictionbook+xml)
|
||||||
|
# # ePub (using https://github.com/marianosimone/epub-thumbnailer)
|
||||||
|
# epub-thumbnailer "${FPATH}" "${IMAGE_CACHE_PATH}" \
|
||||||
|
# "${DEFAULT_SIZE%x*}" && exit 6
|
||||||
|
# ebook-meta --get-cover="${IMAGE_CACHE_PATH}" -- "${FPATH}" \
|
||||||
|
# >/dev/null && exit 6
|
||||||
|
# exit 1;;
|
||||||
|
|
||||||
|
## Font
|
||||||
|
# application/font*|application/*opentype)
|
||||||
|
# preview_png="/tmp/$(basename "${IMAGE_CACHE_PATH%.*}").png"
|
||||||
|
# if fontimage -o "${preview_png}" \
|
||||||
|
# --pixelsize "120" \
|
||||||
|
# --fontname \
|
||||||
|
# --pixelsize "80" \
|
||||||
|
# --text " ABCDEFGHIJKLMNOPQRSTUVWXYZ " \
|
||||||
|
# --text " abcdefghijklmnopqrstuvwxyz " \
|
||||||
|
# --text " 0123456789.:,;(*!?') ff fl fi ffi ffl " \
|
||||||
|
# --text " The quick brown fox jumps over the lazy dog. " \
|
||||||
|
# "${FPATH}";
|
||||||
|
# then
|
||||||
|
# convert -- "${preview_png}" "${IMAGE_CACHE_PATH}" \
|
||||||
|
# && rm "${preview_png}" \
|
||||||
|
# && exit 6
|
||||||
|
# else
|
||||||
|
# exit 1
|
||||||
|
# fi
|
||||||
|
# ;;
|
||||||
|
|
||||||
|
## Preview archives using the first image inside.
|
||||||
|
## (Very useful for comic book collections for example.)
|
||||||
|
# application/zip|application/x-rar|application/x-7z-compressed|\
|
||||||
|
# application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar)
|
||||||
|
# local fn=""; local fe=""
|
||||||
|
# local zip=""; local rar=""; local tar=""; local bsd=""
|
||||||
|
# case "${mimetype}" in
|
||||||
|
# application/zip) zip=1 ;;
|
||||||
|
# application/x-rar) rar=1 ;;
|
||||||
|
# application/x-7z-compressed) ;;
|
||||||
|
# *) tar=1 ;;
|
||||||
|
# esac
|
||||||
|
# { [ "$tar" ] && fn=$(tar --list --file "${FPATH}"); } || \
|
||||||
|
# { fn=$(bsdtar --list --file "${FPATH}") && bsd=1 && tar=""; } || \
|
||||||
|
# { [ "$rar" ] && fn=$(unrar lb -p- -- "${FPATH}"); } || \
|
||||||
|
# { [ "$zip" ] && fn=$(zipinfo -1 -- "${FPATH}"); } || return
|
||||||
|
#
|
||||||
|
# fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \
|
||||||
|
# [ print(l, end='') for l in sys.stdin if \
|
||||||
|
# (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\
|
||||||
|
# sort -V | head -n 1)
|
||||||
|
# [ "$fn" = "" ] && return
|
||||||
|
# [ "$bsd" ] && fn=$(printf '%b' "$fn")
|
||||||
|
#
|
||||||
|
# [ "$tar" ] && tar --extract --to-stdout \
|
||||||
|
# --file "${FPATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g')
|
||||||
|
# [ "$bsd" ] && bsdtar --extract --to-stdout \
|
||||||
|
# --file "${FPATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}"
|
||||||
|
# [ "$rar" ] && unrar p -p- -inul -- "${FPATH}" "$fn" > \
|
||||||
|
# "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# [ "$zip" ] && unzip -pP "" -- "${FPATH}" "$fe" > \
|
||||||
|
# "${IMAGE_CACHE_PATH}" && exit 6
|
||||||
|
# [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}"
|
||||||
|
# ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_mime() {
|
||||||
|
mimetype="${1}"
|
||||||
|
case "${mimetype}" in
|
||||||
|
## Manpages
|
||||||
|
text/troff)
|
||||||
|
man -l "${FPATH}"
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
## Text
|
||||||
|
text/* | */xml)
|
||||||
|
"$EDITOR" "${FPATH}"
|
||||||
|
exit 0;;
|
||||||
|
## Syntax highlight
|
||||||
|
# if [[ "$( stat --printf='%s' -- "${FPATH}" )" -gt "${HIGHLIGHT_SIZE_MAX}" ]]; then
|
||||||
|
# exit 2
|
||||||
|
# fi
|
||||||
|
# if [[ "$( tput colors )" -ge 256 ]]; then
|
||||||
|
# local pygmentize_format='terminal256'
|
||||||
|
# local highlight_format='xterm256'
|
||||||
|
# else
|
||||||
|
# local pygmentize_format='terminal'
|
||||||
|
# local highlight_format='ansi'
|
||||||
|
# fi
|
||||||
|
# env HIGHLIGHT_OPTIONS="${HIGHLIGHT_OPTIONS}" highlight \
|
||||||
|
# --out-format="${highlight_format}" \
|
||||||
|
# --force -- "${FPATH}" && exit 5
|
||||||
|
# pygmentize -f "${pygmentize_format}" -O "style=${PYGMENTIZE_STYLE}"\
|
||||||
|
# -- "${FPATH}" && exit 5
|
||||||
|
# exit 2;;
|
||||||
|
|
||||||
|
## DjVu
|
||||||
|
image/vnd.djvu)
|
||||||
|
if type djvutxt >/dev/null 2>&1; then
|
||||||
|
## Preview as text conversion (requires djvulibre)
|
||||||
|
djvutxt "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
elif type exiftool >/dev/null 2>&1; then
|
||||||
|
exiftool "${FPATH}" | eval "$PAGER"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_fallback() {
|
||||||
|
if [ "$GUI" -ne 0 ]; then
|
||||||
|
if type xdg-open >/dev/null 2>&1; then
|
||||||
|
nohup xdg-open "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
elif type open >/dev/null 2>&1; then
|
||||||
|
nohup open "${FPATH}" >/dev/null 2>&1 &
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '----- File details -----' && file --dereference --brief -- "${FPATH}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_blocked() {
|
||||||
|
case "${MIMETYPE}" in
|
||||||
|
application/x-sharedlib)
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
application/x-shared-library-la)
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
application/x-executable)
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
application/x-shellscript)
|
||||||
|
exit 0;;
|
||||||
|
|
||||||
|
application/octet-stream)
|
||||||
|
exit 0;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_bin() {
|
||||||
|
case "${MIMETYPE}" in
|
||||||
|
application/x-executable|application/x-shellscript)
|
||||||
|
clear
|
||||||
|
echo '-------- Executable File --------' && file --dereference --brief -- "${FPATH}"
|
||||||
|
printf "Run executable (y/N/'a'rgs)? "
|
||||||
|
read -r answer
|
||||||
|
case "$answer" in
|
||||||
|
[Yy]* ) exec "${FPATH}";;
|
||||||
|
[Aa]* )
|
||||||
|
printf "args: "
|
||||||
|
read -r args
|
||||||
|
exec "${FPATH}" "$args";;
|
||||||
|
[Nn]* ) exit;;
|
||||||
|
esac
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
MIMETYPE="$( file -bL --mime-type -- "${FPATH}" )"
|
||||||
|
handle_extension
|
||||||
|
handle_multimedia "${MIMETYPE}"
|
||||||
|
handle_mime "${MIMETYPE}"
|
||||||
|
[ "$BIN" -ne 0 ] && [ -x "${FPATH}" ] && handle_bin
|
||||||
|
handle_blocked "${MIMETYPE}"
|
||||||
|
handle_fallback
|
||||||
|
|
||||||
|
exit 1
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: List files bigger than input size by ascending access date.
|
||||||
|
#
|
||||||
|
# Dependencies: find sort
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
printf "Min file size (MB): "
|
||||||
|
read -r size
|
||||||
|
|
||||||
|
find . -size +"$size"M -type f -printf '%A+ %s %p\n' | sort
|
||||||
|
|
||||||
|
echo "Press any key to exit"
|
||||||
|
read -r _
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Description: Open selected files in nuke one by one or in oneshot
|
||||||
|
#
|
||||||
|
# Notes: 1. Opens the hovered file if the selection is empty
|
||||||
|
# 2. nuke is the default, set OPENER below for custom
|
||||||
|
# 3. Opener is invoked once for each file in a loop
|
||||||
|
# 4. Keep pressing "Enter" to open files one by one
|
||||||
|
#
|
||||||
|
# Shell: bash
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
OPENER="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke"
|
||||||
|
|
||||||
|
if [ -s "$sel" ]; then
|
||||||
|
targets=()
|
||||||
|
while IFS= read -r -d '' entry || [ -n "$entry" ]; do
|
||||||
|
targets+=( "$entry" )
|
||||||
|
done < "$sel"
|
||||||
|
|
||||||
|
elements=${#targets[@]}
|
||||||
|
|
||||||
|
if (( elements == 1 )); then
|
||||||
|
# If there's only one file selected, open without prompts
|
||||||
|
"$OPENER" "${targets[0]}"
|
||||||
|
else
|
||||||
|
printf "open [A]ll? "
|
||||||
|
read -r all
|
||||||
|
|
||||||
|
for ((index=0; index <= ${#targets[@]}; index++)); do
|
||||||
|
"$OPENER" "${targets[index]}"
|
||||||
|
if [ "$all" != "A" ] && (( index+1 < elements )); then
|
||||||
|
printf "press Enter to open '%s'\n" "${targets[index+1]}"
|
||||||
|
read -r -s -n 1 key
|
||||||
|
if [[ $key != "" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
elif [ -n "$1" ]; then
|
||||||
|
"$OPENER" "$1"
|
||||||
|
fi
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Organize files in directories by category
|
||||||
|
#
|
||||||
|
# Note: This plugin clears the selection as it changes the contents of the current dir
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: th3lusive
|
||||||
|
|
||||||
|
sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
organize() {
|
||||||
|
case "$(file -biL "$1")" in
|
||||||
|
*video*)
|
||||||
|
[ ! -d "Videos" ] && mkdir "Videos"
|
||||||
|
mv "$1" "Videos/$1"
|
||||||
|
printf "Moved %s to Videos\n" "$1" ;;
|
||||||
|
|
||||||
|
*audio*) [ ! -d "Audio" ] && mkdir "Audio"
|
||||||
|
mv "$1" "Audio/$1"
|
||||||
|
printf "Moved %s to Audio\n" "$1" ;;
|
||||||
|
|
||||||
|
*image*)
|
||||||
|
[ ! -d "Images" ] && mkdir "Images"
|
||||||
|
mv "$1" "Images/$1"
|
||||||
|
printf "Moved %s to Images\n" "$1" ;;
|
||||||
|
|
||||||
|
*pdf*|*document*|*epub*|*djvu*|*cb*)
|
||||||
|
[ ! -d "Documents" ] && mkdir "Documents"
|
||||||
|
mv "$1" "Documents/$1"
|
||||||
|
printf "Moved %s to Documents\n" "$1" ;;
|
||||||
|
|
||||||
|
*text*)
|
||||||
|
[ ! -d "Plaintext" ] && mkdir "Plaintext"
|
||||||
|
mv "$1" "Plaintext/$1"
|
||||||
|
printf "Moved %s to Plaintext\n" "$1" ;;
|
||||||
|
|
||||||
|
*tar*|*xz*|*compress*|*7z*|*rar*|*zip*)
|
||||||
|
[ ! -d "Archives" ] && mkdir "Archives"
|
||||||
|
mv "$1" "Archives/$1"
|
||||||
|
printf "Moved %s to Archives\n" "$1" ;;
|
||||||
|
|
||||||
|
*binary*)
|
||||||
|
[ ! -d "Binaries" ] && mkdir "Binaries"
|
||||||
|
mv "$1" "Binaries/$1"
|
||||||
|
printf "Moved %s to Binaries\n" "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
for file in *
|
||||||
|
do
|
||||||
|
[ -f "$file" ] && organize "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Read a text or PDF file in British English
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
tmpf="$(basename "$1")"
|
||||||
|
tmpf="${TMPDIR:-/tmp}"/"${tmpf%.*}"
|
||||||
|
|
||||||
|
if [ "$(head -c 4 "$1")" = "%PDF" ]; then
|
||||||
|
# Convert using pdftotext
|
||||||
|
pdftotext -nopgbrk -layout "$1" - | sed 's/\xe2\x80\x8b//g' > "$tmpf".txt
|
||||||
|
|
||||||
|
pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$tmpf".txt)"
|
||||||
|
|
||||||
|
rm "$tmpf".txt
|
||||||
|
else
|
||||||
|
pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$1")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# to jump around and note the time
|
||||||
|
mpv "$tmpf".wav
|
||||||
|
|
||||||
|
# flat read but better quality
|
||||||
|
# play -qV0 "$tmpf".wav treble 2 gain -l 2
|
||||||
|
|
||||||
|
rm "$tmpf".wav
|
||||||
|
fi
|
|
@ -0,0 +1,211 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Description: tabbed/xembed based file previewer
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - tabbed (https://tools.suckless.org/tabbed): xembed host
|
||||||
|
# - xterm (or urxvt or st) : xembed client for text-based preview
|
||||||
|
# - mpv (https://mpv.io): xembed client for video/audio
|
||||||
|
# - sxiv (https://github.com/muennich/sxiv) or,
|
||||||
|
# - nsxiv (https://codeberg.org/nsxiv/nsxiv) : xembed client for images
|
||||||
|
# - zathura (https://pwmt.org/projects/zathura): xembed client for PDF
|
||||||
|
# - nnn's nuke plugin for text preview and fallback
|
||||||
|
# nuke is a fallback for 'mpv', 'sxiv'/'nsxiv', and 'zathura', but has its
|
||||||
|
# own dependencies, see the script for more information
|
||||||
|
# - vim (or any editor/pager really)
|
||||||
|
# - file
|
||||||
|
# - mktemp
|
||||||
|
# - xdotool (optional, to keep main window focused)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# - Install the dependencies. Then set a NNN_FIFO
|
||||||
|
# and set a key for the plugin, then start `nnn`:
|
||||||
|
# $ NNN_FIFO=/tmp/nnn.fifo nnn
|
||||||
|
# - Launch the plugin with the designated key from nnn
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. This plugin needs a "NNN_FIFO" to work. See man.
|
||||||
|
# 2. If the same NNN_FIFO is used in multiple nnn instances, there will be one
|
||||||
|
# common preview window. With different FIFO paths, they will be independent.
|
||||||
|
#
|
||||||
|
# How it works:
|
||||||
|
# We use `tabbed` [1] as a xembed [2] host, to have a single window
|
||||||
|
# owning each previewer window. So each previewer must be a xembed client.
|
||||||
|
# For text previewers, this is not an issue, as there are a lot of
|
||||||
|
# xembed-able terminal emulator (we default to `xterm`, but examples are
|
||||||
|
# provided for `urxvt` and `st`). For graphic preview this can be trickier,
|
||||||
|
# but a few popular viewers are xembed-able, we use:
|
||||||
|
# - `mpv`: multimedia player, for video/audio preview
|
||||||
|
# - `sxiv`/`nsxiv`: image viewer
|
||||||
|
# - `zathura`: PDF viewer
|
||||||
|
# - but we always fallback to `nuke` plugin
|
||||||
|
#
|
||||||
|
# [1]: https://tools.suckless.org/tabbed/
|
||||||
|
# [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
|
||||||
|
#
|
||||||
|
# Shell: Bash (job control is weakly specified in POSIX)
|
||||||
|
# Author: Léo Villeveygoux
|
||||||
|
|
||||||
|
|
||||||
|
XDOTOOL_TIMEOUT=2
|
||||||
|
PAGER=${PAGER:-"vim -R"}
|
||||||
|
NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke"
|
||||||
|
|
||||||
|
|
||||||
|
if type xterm >/dev/null 2>&1 ; then
|
||||||
|
TERMINAL="xterm -into"
|
||||||
|
elif type urxvt >/dev/null 2>&1 ; then
|
||||||
|
TERMINAL="urxvt -embed"
|
||||||
|
elif type st >/dev/null 2>&1 ; then
|
||||||
|
TERMINAL="st -w"
|
||||||
|
else
|
||||||
|
echo "No xembed term found" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
term_nuke () {
|
||||||
|
# $1 -> $XID, $2 -> $FILE
|
||||||
|
$TERMINAL "$1" -e "$NUKE" "$2" &
|
||||||
|
}
|
||||||
|
|
||||||
|
start_tabbed () {
|
||||||
|
FIFO="$(mktemp -u)"
|
||||||
|
mkfifo "$FIFO"
|
||||||
|
|
||||||
|
tabbed > "$FIFO" &
|
||||||
|
|
||||||
|
jobs # Get rid of the "Completed" entries
|
||||||
|
|
||||||
|
TABBEDPID="$(jobs -p %%)"
|
||||||
|
|
||||||
|
if [ -z "$TABBEDPID" ] ; then
|
||||||
|
echo "Can't start tabbed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r XID < "$FIFO"
|
||||||
|
|
||||||
|
rm "$FIFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_viewer_pid () {
|
||||||
|
VIEWERPID="$(jobs -p %%)"
|
||||||
|
}
|
||||||
|
|
||||||
|
kill_viewer () {
|
||||||
|
if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then
|
||||||
|
kill "$VIEWERPID"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sigint_kill () {
|
||||||
|
kill_viewer
|
||||||
|
kill "$TABBEDPID"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
previewer_loop () {
|
||||||
|
unset -v NNN_FIFO
|
||||||
|
# mute from now
|
||||||
|
exec >/dev/null 2>&1
|
||||||
|
|
||||||
|
MAINWINDOW="$(xdotool getactivewindow)"
|
||||||
|
|
||||||
|
start_tabbed
|
||||||
|
trap sigint_kill SIGINT
|
||||||
|
|
||||||
|
xdotool windowactivate "$MAINWINDOW"
|
||||||
|
|
||||||
|
# Bruteforce focus stealing prevention method,
|
||||||
|
# works well in floating window managers like XFCE
|
||||||
|
# but make interaction with the preview window harder
|
||||||
|
# (uncomment to use):
|
||||||
|
#xdotool behave "$XID" focus windowactivate "$MAINWINDOW" &
|
||||||
|
|
||||||
|
while read -r FILE ; do
|
||||||
|
|
||||||
|
jobs # Get rid of the "Completed" entries
|
||||||
|
|
||||||
|
if ! jobs | grep tabbed ; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$FILE" ] ; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill_viewer
|
||||||
|
|
||||||
|
MIME="$(file -bL --mime-type "$FILE")"
|
||||||
|
|
||||||
|
case "$MIME" in
|
||||||
|
video/*)
|
||||||
|
if type mpv >/dev/null 2>&1 ; then
|
||||||
|
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
|
||||||
|
else
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
audio/*)
|
||||||
|
if type mpv >/dev/null 2>&1 ; then
|
||||||
|
mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" &
|
||||||
|
else
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
image/*)
|
||||||
|
if type sxiv >/dev/null 2>&1 ; then
|
||||||
|
sxiv -ae "$XID" "$FILE" &
|
||||||
|
elif type nsxiv >/dev/null 2>&1 ; then
|
||||||
|
nsxiv -ae "$XID" "$FILE" &
|
||||||
|
else
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
application/pdf)
|
||||||
|
if type zathura >/dev/null 2>&1 ; then
|
||||||
|
zathura -e "$XID" "$FILE" &
|
||||||
|
else
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
inode/directory)
|
||||||
|
$TERMINAL "$XID" -e nnn "$FILE" &
|
||||||
|
;;
|
||||||
|
text/*)
|
||||||
|
if [ -x "$NUKE" ] ; then
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
else
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$TERMINAL "$XID" -e $PAGER "$FILE" &
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -x "$NUKE" ] ; then
|
||||||
|
term_nuke "$XID" "$FILE"
|
||||||
|
else
|
||||||
|
$TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" &
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
get_viewer_pid
|
||||||
|
|
||||||
|
# following lines are not needed with the bruteforce xdotool method
|
||||||
|
ACTIVE_XID="$(xdotool getactivewindow)"
|
||||||
|
if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then
|
||||||
|
xdotool windowactivate "$MAINWINDOW"
|
||||||
|
else
|
||||||
|
timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" &
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
kill "$TABBEDPID"
|
||||||
|
kill_viewer
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -r "$NNN_FIFO" ] ; then
|
||||||
|
echo "Can't read \$NNN_FIFO ('$NNN_FIFO')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
previewer_loop < "$NNN_FIFO" &
|
||||||
|
disown
|
|
@ -0,0 +1,481 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Terminal based file previewer
|
||||||
|
#
|
||||||
|
# Note: This plugin needs a "NNN_FIFO" to work. See man.
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - Supports 5 independent methods to preview with:
|
||||||
|
# - tmux (>=3.0), or
|
||||||
|
# - kitty with allow_remote_control and listen_on set in kitty.conf, or
|
||||||
|
# - QuickLook on WSL (https://github.com/QL-Win/QuickLook), or
|
||||||
|
# - Windows Terminal (https://github.com/Microsoft/Terminal | https://aka.ms/terminal) with WSL, or
|
||||||
|
# - $TERMINAL set to a terminal (it's xterm by default).
|
||||||
|
# - less or $PAGER
|
||||||
|
# - tree or exa or ls
|
||||||
|
# - mediainfo or file
|
||||||
|
# - mktemp
|
||||||
|
# - unzip
|
||||||
|
# - tar
|
||||||
|
# - man
|
||||||
|
# - optional: bsdtar or atool for additional archive preview
|
||||||
|
# - optional: bat for code syntax highlighting
|
||||||
|
# - optional: ueberzug, kitty terminal, viu or catimg for images
|
||||||
|
# - optional: convert(ImageMagick) for playing gif preview (required for kitty image previews)
|
||||||
|
# - optional: ffmpegthumbnailer for video thumbnails (https://github.com/dirkvdb/ffmpegthumbnailer)
|
||||||
|
# - optional: ffmpeg for audio thumbnails
|
||||||
|
# - optional: libreoffce for opendocument/officedocument preview
|
||||||
|
# - optional: pdftoppm(poppler) for pdf thumbnails
|
||||||
|
# - optional: gnome-epub-thumbnailer for epub thumbnails (https://gitlab.gnome.org/GNOME/gnome-epub-thumbnailer)
|
||||||
|
# - optional: fontpreview for font preview (https://github.com/sdushantha/fontpreview)
|
||||||
|
# - optional: djvulibre for djvu
|
||||||
|
# - optional: glow or lowdown for markdown
|
||||||
|
# - optional: w3m or lynx or elinks for html
|
||||||
|
# - optional: set/export ICONLOOKUP as 1 to enable file icons in front of directory previews with .iconlookup
|
||||||
|
# Icons and colors are configureable in .iconlookup
|
||||||
|
# - optional: scope.sh file viewer from ranger.
|
||||||
|
# 1. drop scope.sh executable in $PATH
|
||||||
|
# 2. set/export $USE_SCOPE as 1
|
||||||
|
# - optional: pistol file viewer (https://github.com/doronbehar/pistol).
|
||||||
|
# 1. install pistol
|
||||||
|
# 2. set/export $USE_PISTOL as 1
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# You need to set a NNN_FIFO path and a key for the plugin with NNN_PLUG,
|
||||||
|
# then start `nnn`:
|
||||||
|
#
|
||||||
|
# $ nnn -a
|
||||||
|
#
|
||||||
|
# or
|
||||||
|
#
|
||||||
|
# $ NNN_FIFO=/tmp/nnn.fifo nnn
|
||||||
|
#
|
||||||
|
# Then launch the `preview-tui` plugin in `nnn`.
|
||||||
|
#
|
||||||
|
# If you provide the same NNN_FIFO to all nnn instances, there will be a
|
||||||
|
# single common preview window. If you provide different FIFO path (e.g.
|
||||||
|
# with -a), they will be independent.
|
||||||
|
#
|
||||||
|
# The previews will be shown in a tmux split. If that isn't possible, it
|
||||||
|
# will try to use a kitty terminal split. And as a final fallback, a
|
||||||
|
# different terminal window will be used ($TERMINAL).
|
||||||
|
#
|
||||||
|
# Tmux and kitty users can configure $SPLIT to either "h" or "v" to set a
|
||||||
|
# 'h'orizontal split or a 'v'ertical split (as in, the line that splits the
|
||||||
|
# windows will be horizontal or vertical).
|
||||||
|
#
|
||||||
|
# Kitty users need something similar to the following in their kitty.conf:
|
||||||
|
# - `allow_remote_control yes`
|
||||||
|
# - `listen_on unix:$TMPDIR/kitty`
|
||||||
|
# - `enabled_layouts splits` (optional)
|
||||||
|
# With ImageMagick installed, this terminal can use the icat kitten to display images.
|
||||||
|
# Refer to kitty documentation for further details.
|
||||||
|
#
|
||||||
|
# Iterm2 users are recommended to use viu to view images without getting pixelated.
|
||||||
|
#
|
||||||
|
# Windows Terminal users can set "Profile termination behavior" under "Profile > Advanced" settings
|
||||||
|
# to automaticaly close pane on quit when exit code is 0.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Todd Yamakawa, Léo Villeveygoux, @Recidiviste, Mario Ortiz Manero, Luuk van Baal, @WanderLanz
|
||||||
|
|
||||||
|
#SPLIT="$SPLIT" # you can set a permanent split here
|
||||||
|
#TERMINAL="$TERMINAL" # same goes for the terminal
|
||||||
|
SPLIT_SIZE="${SPLIT_SIZE:-50}" # split size in percentage for supported previewers
|
||||||
|
USE_SCOPE="${USE_SCOPE:-0}"
|
||||||
|
USE_PISTOL="${USE_PISTOL:-0}"
|
||||||
|
ICONLOOKUP="${ICONLOOKUP:-0}"
|
||||||
|
PAGER="${PAGER:-less -P?n -R}"
|
||||||
|
TMPDIR="${TMPDIR:-/tmp}"
|
||||||
|
BAT_STYLE="${BAT_STYLE:-numbers}"
|
||||||
|
BAT_THEME="${BAT_THEME:-ansi}"
|
||||||
|
# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews if you want to keep previews on disk between reboots
|
||||||
|
NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-$TMPDIR/nnn/previews}"
|
||||||
|
NNN_PREVIEWWIDTH="${NNN_PREVIEWWIDTH:-1920}"
|
||||||
|
NNN_PREVIEWHEIGHT="${NNN_PREVIEWHEIGHT:-1080}"
|
||||||
|
NNN_PARENT="${NNN_FIFO#*.}"
|
||||||
|
[ "$NNN_PARENT" -eq "$NNN_PARENT" ] 2>/dev/null || NNN_PARENT=""
|
||||||
|
FIFOPID="$TMPDIR/nnn-preview-tui-fifopid.$NNN_PARENT"
|
||||||
|
PREVIEWPID="$TMPDIR/nnn-preview-tui-pagerpid.$NNN_PARENT"
|
||||||
|
CURSEL="$TMPDIR/nnn-preview-tui-selection.$NNN_PARENT"
|
||||||
|
FIFO_UEBERZUG="$TMPDIR/nnn-preview-tui-ueberzug-fifo.$NNN_PARENT"
|
||||||
|
POSOFFSET="$TMPDIR/nnn-preview-tui-posoffset"
|
||||||
|
|
||||||
|
exists() { type "$1" >/dev/null 2>&1 ;}
|
||||||
|
pkill() { command pkill "$@" >/dev/null 2>&1 ;}
|
||||||
|
pidkill() { [ -f "$1" ] && kill "$(cat "$1")" >/dev/null 2>&1 ;}
|
||||||
|
prompt() { printf "%b" "$@"; cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" ;}
|
||||||
|
|
||||||
|
start_preview() {
|
||||||
|
[ "$PAGER" = "most" ] && PAGER="less -R"
|
||||||
|
|
||||||
|
if [ -e "${TMUX%%,*}" ] && tmux -V | grep -q '[ -][3456789]\.'; then
|
||||||
|
TERMINAL=tmux
|
||||||
|
elif [ -n "$KITTY_LISTEN_ON" ]; then
|
||||||
|
TERMINAL=kitty
|
||||||
|
elif [ -z "$TERMINAL" ] && [ "$TERM_PROGRAM" = "iTerm.app" ]; then
|
||||||
|
TERMINAL=iterm
|
||||||
|
elif [ -n "$WT_SESSION" ]; then
|
||||||
|
TERMINAL=winterm
|
||||||
|
else
|
||||||
|
TERMINAL="${TERMINAL:-xterm}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$SPLIT" ] && [ $(($(tput lines) * 2)) -gt "$(tput cols)" ]; then
|
||||||
|
SPLIT='h'
|
||||||
|
elif [ "$SPLIT" != 'h' ]; then
|
||||||
|
SPLIT='v'
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$TERMINAL" in
|
||||||
|
tmux) # tmux splits are inverted
|
||||||
|
if [ "$SPLIT" = "v" ]; then DSPLIT="h"; else DSPLIT="v"; fi
|
||||||
|
tmux split-window -e "NNN_FIFO=$NNN_FIFO" -e "PREVIEW_MODE=1" -e "CURSEL=$CURSEL" \
|
||||||
|
-e "TMPDIR=$TMPDIR" -e "FIFOPID=$FIFOPID" -e "POSOFFSET=$POSOFFSET" \
|
||||||
|
-e "BAT_STYLE=$BAT_STYLE" -e "BAT_THEME=$BAT_THEME" -e "PREVIEWPID=$PREVIEWPID" \
|
||||||
|
-e "PAGER=$PAGER" -e "ICONLOOKUP=$ICONLOOKUP" -e "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" \
|
||||||
|
-e "USE_SCOPE=$USE_SCOPE" -e "SPLIT=$SPLIT" -e "USE_PISTOL=$USE_PISTOL" \
|
||||||
|
-e "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" -e "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \
|
||||||
|
-e "FIFO_UEBERZUG=$FIFO_UEBERZUG" -e "QLPATH=$2" -d"$DSPLIT" -p"$SPLIT_SIZE" "$0" "$1" ;;
|
||||||
|
kitty) # Setting the layout for the new window. It will be restored after the script ends.
|
||||||
|
kitty @ goto-layout splits
|
||||||
|
# Trying to use kitty's integrated window management as the split window. All
|
||||||
|
# environmental variables that will be used in the new window must be explicitly passed.
|
||||||
|
kitty @ launch --no-response --title "nnn preview" --keep-focus \
|
||||||
|
--cwd "$PWD" --env "PATH=$PATH" --env "NNN_FIFO=$NNN_FIFO" \
|
||||||
|
--env "PREVIEW_MODE=1" --env "PAGER=$PAGER" --env "TMPDIR=$TMPDIR" \
|
||||||
|
--env "USE_SCOPE=$USE_SCOPE" --env "SPLIT=$SPLIT" --env "TERMINAL=$TERMINAL"\
|
||||||
|
--env "PREVIEWPID=$PREVIEWPID" --env "FIFO_UEBERZUG=$FIFO_UEBERZUG" \
|
||||||
|
--env "ICONLOOKUP=$ICONLOOKUP" --env "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \
|
||||||
|
--env "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" --env "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" \
|
||||||
|
--env "USE_PISTOL=$USE_PISTOL" --env "BAT_STYLE=$BAT_STYLE" \
|
||||||
|
--env "BAT_THEME=$BAT_THEME" --env "FIFOPID=$FIFOPID" \
|
||||||
|
--env "CURSEL=$CURSEL" --location "${SPLIT}split" "$0" "$1" ;;
|
||||||
|
iterm)
|
||||||
|
command="$SHELL -c 'cd $PWD; \
|
||||||
|
PATH=\\\"$PATH\\\" NNN_FIFO=\\\"$NNN_FIFO\\\" PREVIEW_MODE=1 PAGER=\\\"$PAGER\\\" \
|
||||||
|
USE_SCOPE=\\\"$USE_SCOPE\\\" SPLIT=\\\"$SPLIT\\\" TERMINAL=\\\"$TERMINAL\\\" \
|
||||||
|
PREVIEWPID=\\\"$PREVIEWPID\\\" CURSEL=\\\"$CURSEL\\\" TMPDIR=\\\"$TMPDIR\\\" \
|
||||||
|
ICONLOOKUP=\\\"$ICONLOOKUP\\\" NNN_PREVIEWHEIGHT=\\\"$NNN_PREVIEWHEIGHT\\\" \
|
||||||
|
NNN_PREVIEWWIDTH=\\\"$NNN_PREVIEWWIDTH\\\" NNN_PREVIEWDIR=\\\"$NNN_PREVIEWDIR\\\" \
|
||||||
|
USE_PISTOL=\\\"$USE_PISTOL\\\" BAT_STYLE=\\\"$BAT_STYLE\\\" \
|
||||||
|
BAT_THEME=\\\"$BAT_THEME\\\" FIFOPID=\\\"$FIFOPID\\\" \\\"$0\\\" \\\"$1\\\"'"
|
||||||
|
if [ "$SPLIT" = "h" ]; then split="horizontally"; else split="vertically"; fi
|
||||||
|
osascript <<-EOF
|
||||||
|
tell application "iTerm"
|
||||||
|
tell current session of current window
|
||||||
|
split $split with default profile command "$command"
|
||||||
|
end tell
|
||||||
|
end tell
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
winterm)
|
||||||
|
if [ "$SPLIT" = "h" ]; then split="H"; else split="V"; fi
|
||||||
|
cmd.exe /c wt -w 0 sp -$split -s$((SPLIT_SIZE / 100)) bash -c "cd $PWD \; \
|
||||||
|
PATH='$PATH' NNN_FIFO=$NNN_FIFO PREVIEW_MODE=1 CURSEL=$CURSEL TMPDIR=$TMPDIR \
|
||||||
|
FIFOPID=$FIFOPID BAT_STYLE=$BAT_STYLE BAT_THEME=$BAT_THEME PREVIEWPID=$PREVIEWPID \
|
||||||
|
PAGER='$PAGER' ICONLOOKUP=$ICONLOOKUP NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH \
|
||||||
|
USE_SCOPE=$USE_SCOPE SPLIT=$SPLIT USE_PISTOL=$USE_PISTOL \
|
||||||
|
NNN_PREVIEWDIR=$NNN_PREVIEWDIR NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT \
|
||||||
|
FIFO_UEBERZUG=$FIFO_UEBERZUG QLPATH=$2 $0 $1" \; -w 0 mf previous
|
||||||
|
;;
|
||||||
|
*) if [ -n "$2" ]; then
|
||||||
|
QUICKLOOK=1 QLPATH="$2" PREVIEW_MODE=1 "$0" "$1" &
|
||||||
|
else
|
||||||
|
PREVIEWPID="$PREVIEWPID" CURSEL="$CURSEL" PREVIEW_MODE=1 \
|
||||||
|
FIFOPID="$FIFOPID" FIFO_UEBERZUG="$FIFO_UEBERZUG" $TERMINAL -e "$0" "$1" &
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle_preview() {
|
||||||
|
if exists QuickLook.exe; then
|
||||||
|
QLPATH="QuickLook.exe"
|
||||||
|
elif exists Bridge.exe; then
|
||||||
|
QLPATH="Bridge.exe"
|
||||||
|
fi
|
||||||
|
if pidkill "$FIFOPID"; then
|
||||||
|
[ -p "$NNN_PPIPE" ] && printf "0" > "$NNN_PPIPE"
|
||||||
|
pidkill "$PREVIEWPID"
|
||||||
|
pkill -f "tail --follow $FIFO_UEBERZUG"
|
||||||
|
if [ -n "$QLPATH" ] && stat "$1"; then
|
||||||
|
f="$(wslpath -w "$1")" && "$QLPATH" "$f" &
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
[ -p "$NNN_PPIPE" ] && printf "1" > "$NNN_PPIPE"
|
||||||
|
start_preview "$1" "$QLPATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fifo_pager() {
|
||||||
|
cmd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
# We use a FIFO to access $PAGER PID in jobs control
|
||||||
|
tmpfifopath="$TMPDIR/nnn-preview-tui-fifo.$$"
|
||||||
|
mkfifo "$tmpfifopath" || return
|
||||||
|
|
||||||
|
$PAGER < "$tmpfifopath" &
|
||||||
|
printf "%s" "$!" > "$PREVIEWPID"
|
||||||
|
|
||||||
|
(
|
||||||
|
exec > "$tmpfifopath"
|
||||||
|
if [ "$cmd" = "pager" ]; then
|
||||||
|
if exists bat; then
|
||||||
|
bat --terminal-width="$cols" --decorations=always --color=always \
|
||||||
|
--paging=never --style="$BAT_STYLE" --theme="$BAT_THEME" "$@" &
|
||||||
|
else
|
||||||
|
$PAGER "$@" &
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
"$cmd" "$@" &
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
|
||||||
|
rm "$tmpfifopath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Binary file: show file info inside the pager
|
||||||
|
print_bin_info() {
|
||||||
|
printf -- "-------- \033[1;31mBinary file\033[0m --------\n"
|
||||||
|
if exists mediainfo; then
|
||||||
|
mediainfo "$1"
|
||||||
|
else
|
||||||
|
file -b "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_mime() {
|
||||||
|
case "$2" in
|
||||||
|
image/jpeg) image_preview "$cols" "$lines" "$1" ;;
|
||||||
|
image/gif) generate_preview "$cols" "$lines" "$1" "gif" ;;
|
||||||
|
image/vnd.djvu) generate_preview "$cols" "$lines" "$1" "djvu" ;;
|
||||||
|
image/*) generate_preview "$cols" "$lines" "$1" "image" ;;
|
||||||
|
video/*) generate_preview "$cols" "$lines" "$1" "video" ;;
|
||||||
|
audio/*) generate_preview "$cols" "$lines" "$1" "audio" ;;
|
||||||
|
application/font*|application/*opentype|font/*) generate_preview "$cols" "$lines" "$1" "font" ;;
|
||||||
|
*/*office*|*/*document*) generate_preview "$cols" "$lines" "$1" "office" ;;
|
||||||
|
application/zip) fifo_pager unzip -l "$1" ;;
|
||||||
|
text/troff)
|
||||||
|
if exists man; then
|
||||||
|
fifo_pager man -Pcat -l "$1"
|
||||||
|
else
|
||||||
|
fifo_pager pager "$1"
|
||||||
|
fi ;;
|
||||||
|
*) handle_ext "$1" "$3" "$4" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_ext() {
|
||||||
|
case "$2" in
|
||||||
|
epub) generate_preview "$cols" "$lines" "$1" "epub" ;;
|
||||||
|
pdf) generate_preview "$cols" "$lines" "$1" "pdf" ;;
|
||||||
|
gz|bz2) fifo_pager tar -tvf "$1" ;;
|
||||||
|
md) if exists glow; then
|
||||||
|
fifo_pager glow -s dark "$1"
|
||||||
|
elif exists lowdown; then
|
||||||
|
fifo_pager lowdown -Tterm "$1"
|
||||||
|
else
|
||||||
|
fifo_pager pager "$1"
|
||||||
|
fi ;;
|
||||||
|
htm|html|xhtml)
|
||||||
|
if exists w3m; then
|
||||||
|
fifo_pager w3m "$1"
|
||||||
|
elif exists lynx; then
|
||||||
|
fifo_pager lynx "$1"
|
||||||
|
elif exists elinks; then
|
||||||
|
fifo_pager elinks "$1"
|
||||||
|
else
|
||||||
|
fifo_pager pager "$1"
|
||||||
|
fi ;;
|
||||||
|
7z|a|ace|alz|arc|arj|bz|cab|cpio|deb|jar|lha|lz|lzh|lzma|lzo\
|
||||||
|
|rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z)
|
||||||
|
if exists atool; then
|
||||||
|
fifo_pager atool -l "$1"
|
||||||
|
elif exists bsdtar; then
|
||||||
|
fifo_pager bsdtar -tvf "$1"
|
||||||
|
fi ;;
|
||||||
|
*) if [ "$3" = "bin" ]; then
|
||||||
|
fifo_pager print_bin_info "$1"
|
||||||
|
else
|
||||||
|
fifo_pager pager "$1"
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
preview_file() {
|
||||||
|
clear
|
||||||
|
# Trying to use pistol if it's available.
|
||||||
|
if [ "$USE_PISTOL" -ne 0 ] && exists pistol; then
|
||||||
|
fifo_pager pistol "$1"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Trying to use scope.sh if it's available.
|
||||||
|
if [ "$USE_SCOPE" -ne 0 ] && exists scope.sh; then
|
||||||
|
fifo_pager scope.sh "$1" "$cols" "$lines" "$(mktemp -d)" "True"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use QuickLook if it's available.
|
||||||
|
if [ -n "$QUICKLOOK" ]; then
|
||||||
|
stat "$1" && f="$(wslpath -w "$1")" && "$QLPATH" "$f" &
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detecting the exact type of the file: the encoding, mime type, and extension in lowercase.
|
||||||
|
encoding="$(file -bL --mime-encoding -- "$1")"
|
||||||
|
mimetype="$(file -bL --mime-type -- "$1")"
|
||||||
|
ext="${1##*.}"
|
||||||
|
[ -n "$ext" ] && ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
lines=$(tput lines)
|
||||||
|
cols=$(tput cols)
|
||||||
|
|
||||||
|
# Otherwise, falling back to the defaults.
|
||||||
|
if [ -d "$1" ]; then
|
||||||
|
cd "$1" || return
|
||||||
|
if [ "$ICONLOOKUP" -ne 0 ] && [ -f "$(dirname "$0")"/.iconlookup ]; then
|
||||||
|
[ "$SPLIT" = v ] && BSTR="\n"
|
||||||
|
# shellcheck disable=SC2012
|
||||||
|
ls -F --group-directories-first | head -n "$((lines - 3))" | "$(dirname "$0")"/.iconlookup -l "$cols" -B "$BSTR" -b " "
|
||||||
|
elif exists tree; then
|
||||||
|
fifo_pager tree --filelimit "$(find . -maxdepth 1 | wc -l)" -L 3 -C -F --dirsfirst --noreport
|
||||||
|
elif exists exa; then
|
||||||
|
exa -G --group-directories-first --colour=always
|
||||||
|
else
|
||||||
|
fifo_pager ls -F --group-directories-first --color=always
|
||||||
|
fi
|
||||||
|
elif [ "${encoding#*)}" = "binary" ]; then
|
||||||
|
handle_mime "$1" "$mimetype" "$ext" "bin"
|
||||||
|
else
|
||||||
|
handle_mime "$1" "$mimetype" "$ext"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_preview() {
|
||||||
|
if [ -n "$QLPATH" ] && stat "$3"; then
|
||||||
|
f="$(wslpath -w "$3")" && "$QLPATH" "$f" &
|
||||||
|
elif [ ! -f "$NNN_PREVIEWDIR/$3.jpg" ] || [ -n "$(find -L "$3" -newer "$NNN_PREVIEWDIR/$3.jpg")" ]; then
|
||||||
|
mkdir -p "$NNN_PREVIEWDIR/${3%/*}"
|
||||||
|
case $4 in
|
||||||
|
audio) ffmpeg -i "$3" -filter_complex "scale=iw*min(1\,min($NNN_PREVIEWWIDTH/iw\,ih)):-1" "$NNN_PREVIEWDIR/$3.jpg" -y ;;
|
||||||
|
epub) gnome-epub-thumbnailer "$3" "$NNN_PREVIEWDIR/$3.jpg" ;;
|
||||||
|
font) fontpreview -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" ;;
|
||||||
|
gif) if [ -p "$FIFO_UEBERZUG" ] && exists convert; then
|
||||||
|
frameprefix="$NNN_PREVIEWDIR/$3/${3##*/}"
|
||||||
|
if [ ! -d "$NNN_PREVIEWDIR/$3" ]; then
|
||||||
|
mkdir -p "$NNN_PREVIEWDIR/$3"
|
||||||
|
convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg" ||
|
||||||
|
MAGICK_TMPDIR="/tmp" convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg"
|
||||||
|
fi
|
||||||
|
frames=$(($(find "$NNN_PREVIEWDIR/$3" | wc -l) - 2))
|
||||||
|
[ $frames -lt 0 ] && return
|
||||||
|
while true; do
|
||||||
|
for i in $(seq 0 $frames); do
|
||||||
|
image_preview "$1" "$2" "$frameprefix-$i.jpg"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
done &
|
||||||
|
printf "%s" "$!" > "$PREVIEWPID"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
image_preview "$1" "$2" "$3"
|
||||||
|
return
|
||||||
|
fi ;;
|
||||||
|
image) if exists convert; then
|
||||||
|
convert "$3" -flatten -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$NNN_PREVIEWDIR/$3.jpg"
|
||||||
|
else
|
||||||
|
image_preview "$1" "$2" "$3" && return
|
||||||
|
fi ;;
|
||||||
|
office) libreoffice --convert-to jpg "$3" --outdir "$NNN_PREVIEWDIR/${3%/*}"
|
||||||
|
filename="$(printf "%s" "${3##*/}" | cut -d. -f1)"
|
||||||
|
mv "$NNN_PREVIEWDIR/${3%/*}/$filename.jpg" "$NNN_PREVIEWDIR/$3.jpg" ;;
|
||||||
|
pdf) pdftoppm -jpeg -f 1 -singlefile "$3" "$NNN_PREVIEWDIR/$3" ;;
|
||||||
|
djvu) ddjvu -format=ppm -page=1 "$3" "$NNN_PREVIEWDIR/$3.jpg" ;;
|
||||||
|
video) ffmpegthumbnailer -m -s0 -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" || rm "$NNN_PREVIEWDIR/$3.jpg" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ -f "$NNN_PREVIEWDIR/$3.jpg" ]; then
|
||||||
|
image_preview "$1" "$2" "$NNN_PREVIEWDIR/$3.jpg"
|
||||||
|
else
|
||||||
|
fifo_pager print_bin_info "$3"
|
||||||
|
fi
|
||||||
|
} >/dev/null 2>&1
|
||||||
|
|
||||||
|
image_preview() {
|
||||||
|
clear
|
||||||
|
exec >/dev/tty
|
||||||
|
if [ "$TERMINAL" = "kitty" ]; then
|
||||||
|
# Kitty terminal users can use the native image preview method
|
||||||
|
kitty +kitten icat --silent --scale-up --place "$1"x"$2"@0x0 --transfer-mode=stream --stdin=no "$3" &
|
||||||
|
elif exists ueberzug; then
|
||||||
|
ueberzug_layer "$1" "$2" "$3" && return
|
||||||
|
elif exists catimg; then
|
||||||
|
catimg "$3" &
|
||||||
|
elif exists viu; then
|
||||||
|
viu -t "$3" &
|
||||||
|
else
|
||||||
|
fifo_pager print_bin_info "$3" && return
|
||||||
|
fi
|
||||||
|
printf "%s" "$!" > "$PREVIEWPID"
|
||||||
|
}
|
||||||
|
|
||||||
|
ueberzug_layer() {
|
||||||
|
[ -f "$POSOFFSET" ] && read -r x y < "$POSOFFSET"
|
||||||
|
printf '{"action": "add", "identifier": "nnn_ueberzug", "x": %d, "y": %d, "width": "%d", "height": "%d", "scaler": "fit_contain", "path": "%s"}\n'\
|
||||||
|
"${x:-0}" "${y:-0}" "$1" "$2" "$3" > "$FIFO_UEBERZUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
ueberzug_remove() {
|
||||||
|
printf '{"action": "remove", "identifier": "nnn_ueberzug"}\n' > "$FIFO_UEBERZUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
winch_handler() {
|
||||||
|
clear
|
||||||
|
pidkill "$PREVIEWPID"
|
||||||
|
if [ -p "$FIFO_UEBERZUG" ]; then
|
||||||
|
pkill -f "tail --follow $FIFO_UEBERZUG"
|
||||||
|
tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json &
|
||||||
|
fi
|
||||||
|
preview_file "$(cat "$CURSEL")"
|
||||||
|
}
|
||||||
|
|
||||||
|
preview_fifo() {
|
||||||
|
while read -r selection; do
|
||||||
|
if [ -n "$selection" ]; then
|
||||||
|
pidkill "$PREVIEWPID"
|
||||||
|
[ -p "$FIFO_UEBERZUG" ] && ueberzug_remove
|
||||||
|
[ "$selection" = "close" ] && break
|
||||||
|
preview_file "$selection"
|
||||||
|
printf "%s" "$selection" > "$CURSEL"
|
||||||
|
fi
|
||||||
|
done < "$NNN_FIFO"
|
||||||
|
sleep 0.1 # make sure potential preview by winch_handler is killed
|
||||||
|
pkill -P "$$"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$PREVIEW_MODE" ]; then
|
||||||
|
if [ "$TERMINAL" != "kitty" ] && exists ueberzug; then
|
||||||
|
mkfifo "$FIFO_UEBERZUG"
|
||||||
|
tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json &
|
||||||
|
fi
|
||||||
|
|
||||||
|
preview_file "$PWD/$1"
|
||||||
|
preview_fifo &
|
||||||
|
printf "%s" "$!" > "$FIFOPID"
|
||||||
|
printf "%s" "$PWD/$1" > "$CURSEL"
|
||||||
|
trap 'winch_handler; wait' WINCH
|
||||||
|
trap 'rm "$PREVIEWPID" "$CURSEL" "$FIFO_UEBERZUG" "$FIFOPID" "$POSOFFSET" 2>/dev/null' INT HUP EXIT
|
||||||
|
wait "$!" 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
if [ ! -r "$NNN_FIFO" ]; then
|
||||||
|
clear
|
||||||
|
prompt "No FIFO available! (\$NNN_FIFO='$NNN_FIFO')\nPlease read Usage in preview-tui."
|
||||||
|
elif [ "$KITTY_WINDOW_ID" ] && [ -z "$TMUX" ] && [ -z "$KITTY_LISTEN_ON" ]; then
|
||||||
|
clear
|
||||||
|
prompt "\$KITTY_LISTEN_ON not set!\nPlease read Usage in preview-tui."
|
||||||
|
else
|
||||||
|
toggle_preview "$1" &
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Fuzzy list and kill a (zombie) process by name
|
||||||
|
#
|
||||||
|
# Dependencies: fzf, ps
|
||||||
|
#
|
||||||
|
# Note: To kill a zombie process enter "zombie"
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
printf "Enter process name ['defunct' for zombies]: "
|
||||||
|
read -r psname
|
||||||
|
|
||||||
|
# shellcheck disable=SC2009
|
||||||
|
if [ -n "$psname" ]; then
|
||||||
|
if type sudo >/dev/null 2>&1; then
|
||||||
|
sucmd=sudo
|
||||||
|
elif type doas >/dev/null 2>&1; then
|
||||||
|
sucmd=doas
|
||||||
|
else
|
||||||
|
sucmd=: # noop
|
||||||
|
fi
|
||||||
|
|
||||||
|
if type fzf >/dev/null 2>&1; then
|
||||||
|
fuzzy=fzf
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmd="$(ps -ax | grep -iw "$psname" | "$fuzzy" | sed -e 's/^[ \t]*//' | cut -d' ' -f1)"
|
||||||
|
if [ -n "$cmd" ]; then
|
||||||
|
$sucmd kill -9 "$cmd"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Batch rename selection or current directory with qmv or vidir
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Try to mimic current batch rename functionality but with correct
|
||||||
|
# handling of edge cases by qmv or vidir.
|
||||||
|
# - Qmv opens with hidden files if no selection is used. Selected
|
||||||
|
# directories are shown.
|
||||||
|
# - Vidir don't show directories nor hidden files.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: José Neder
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
if type qmv >/dev/null 2>&1; then
|
||||||
|
batchrenamesel="qmv -fdo -da"
|
||||||
|
batchrename="qmv -fdo -a"
|
||||||
|
elif type vidir >/dev/null 2>&1; then
|
||||||
|
batchrenamesel="vidir"
|
||||||
|
batchrename="vidir"
|
||||||
|
else
|
||||||
|
printf "there is not batchrename program installed."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "rename selection? "
|
||||||
|
read -r resp
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$resp" = "y" ]; then
|
||||||
|
# -o flag is necessary for interactive editors
|
||||||
|
xargs -o -0 $batchrenamesel < "$selection"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
elif [ ! "$(LC_ALL=C ls -a)" = ".
|
||||||
|
.." ]; then
|
||||||
|
# On older systems that don't have ls -A
|
||||||
|
$batchrename
|
||||||
|
fi
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Create an mp3 ringtone out of an audio file in any format
|
||||||
|
# Needs user to provide start and end where to cut the file
|
||||||
|
# Input file audio.ext results in audio_ringtone.mp3
|
||||||
|
#
|
||||||
|
# Tip: To convert a complete media file, set start as 0 and
|
||||||
|
# the runtime of the file as end.
|
||||||
|
#
|
||||||
|
# Dependencies: date, ffmpeg
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
printf "start (hh:mm:ss): "
|
||||||
|
read -r start
|
||||||
|
st=$(date -d "$start" +%s) || exit 1
|
||||||
|
|
||||||
|
printf "end (hh:mm:ss): "
|
||||||
|
read -r end
|
||||||
|
et=$(date -d "$end" +%s) || exit 1
|
||||||
|
|
||||||
|
if [ "$st" -ge "$et" ]; then
|
||||||
|
printf "error: start >= end "
|
||||||
|
read -r _
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
interval=$(( et - st ))
|
||||||
|
|
||||||
|
outfile=$(basename "$1")
|
||||||
|
outfile="${outfile%.*}"_ringtone.mp3
|
||||||
|
|
||||||
|
ffmpeg -i "$1" -ss "$start" -t "$interval" -vn -sn -acodec libmp3lame -q:a 2 "$outfile"
|
||||||
|
fi
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Simple script to give copy-paste a progress percentage
|
||||||
|
# by utilizing rsync.
|
||||||
|
#
|
||||||
|
# LIMITATION: this won't work when pasting to MTP device.
|
||||||
|
#
|
||||||
|
# Dependencies: rsync
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Benawi Adha
|
||||||
|
|
||||||
|
sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
# Choose one of these two schemes by commenting
|
||||||
|
|
||||||
|
# more verbose
|
||||||
|
xargs -0 -I % rsync -ah --progress % "$PWD" < "$sel"
|
||||||
|
|
||||||
|
# less verbose
|
||||||
|
# xargs -0 -I % rsync -ah --info=progress2 % "$PWD" < "$sel"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Splits the file passed as argument or joins selection
|
||||||
|
#
|
||||||
|
# Note: Adds numeric suffix to split files
|
||||||
|
# Adds '.out suffix to the first file to be joined and saves as output file for join
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Arun Prakash Jana, ath3
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
resp=s
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
printf "press 's' (split current file) or 'j' (join selection): "
|
||||||
|
read -r resp
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$resp" = "j" ]; then
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
arr=$(tr '\0' '\n' < "$selection")
|
||||||
|
if [ "$(echo "$arr" | wc -l)" -lt 2 ]; then
|
||||||
|
echo "joining needs at least 2 files"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
for entry in $arr
|
||||||
|
do
|
||||||
|
if [ -d "$entry" ]; then
|
||||||
|
echo "cant join directories"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
file="$(basename "$(echo "$arr" | sed -n '1p' | sed -e 's/[0-9][0-9]$//')")"
|
||||||
|
sort -z < "$selection" | xargs -0 -I{} cat {} > "${file}.out"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ "$resp" = "s" ]; then
|
||||||
|
if [ -n "$1" ] && [ -f "$1" ]; then
|
||||||
|
# a single file is passed
|
||||||
|
printf "split size in MB: "
|
||||||
|
read -r size
|
||||||
|
|
||||||
|
if [ -n "$size" ]; then
|
||||||
|
split -d -b "$size"M "$1" "$1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Edit file as superuser
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad
|
||||||
|
|
||||||
|
EDITOR="${EDITOR:-vim}"
|
||||||
|
|
||||||
|
if type sudo >/dev/null 2>&1; then
|
||||||
|
sudo -E "$EDITOR" "$1"
|
||||||
|
elif type sudoedit >/dev/null 2>&1; then
|
||||||
|
sudoedit -E "$1"
|
||||||
|
elif type doas >/dev/null 2>&1; then
|
||||||
|
doas "$EDITOR" "$1"
|
||||||
|
fi
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Toggles executable mode for selection
|
||||||
|
#
|
||||||
|
# Dependencies: chmod
|
||||||
|
#
|
||||||
|
# Note: Works _only_ with selection (nnn can toggle the mode for the hovered file)
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
xargs -0 -I {} sh -c 'if [ -x "{}" ] ; then chmod -x "{}" ; else chmod +x "{}" ; fi' < "$selection"
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
if [ -p "$NNN_PIPE" ]; then
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Autodetects a nnn remote mountpoint (mounted with `c`)
|
||||||
|
# from any of its subfolders and allows unmounting it
|
||||||
|
# from the subdir without navigating to the mountppoint
|
||||||
|
# or entering the remote name. Also works when hovering
|
||||||
|
# the mountpoint directly like vanilla `u`.
|
||||||
|
#
|
||||||
|
# Dependencies: fusermount
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Authors: Kabouik & 0xACE
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# - Avoid lazy unmount by forcing nnn context to leave the subfolder before fusermount.
|
||||||
|
# Tried `printf "%s" "0c$m" > "$NNN_PIPE"` but it breaks the nnn interface, see #854.
|
||||||
|
|
||||||
|
err=0
|
||||||
|
m=$HOME/.config/nnn/mounts
|
||||||
|
if [ "$PWD" = "$m" ]; then
|
||||||
|
# Allow running the script on hovered directory if user is in ~/.config/nnn/mounts
|
||||||
|
d="$1"
|
||||||
|
else
|
||||||
|
d=$(dirname "$(readlink -f "$1")" | grep -oP "^$m\K.*" | cut -d"/" -f2)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test if user is within $m or a subdir, abort if not
|
||||||
|
if [ "$d" = "" ]; then
|
||||||
|
clear && printf "You are not in a remote folder mounted with nnn. Press return to continue. " && read -r _
|
||||||
|
else
|
||||||
|
# Test if $m/$d is a mountpoint and try unmounting if it is
|
||||||
|
mountpoint -q -- "$m/$d"
|
||||||
|
if [ "$?" -eq "1" ]; then
|
||||||
|
clear && printf "Parent '%s' is not a mountpoint. Press return to continue. " "$d" && read -r _
|
||||||
|
else
|
||||||
|
cd "$m" && fusermount -uq "$m/$d" || err=1
|
||||||
|
if [ "$err" -eq "0" ]; then
|
||||||
|
rmdir "$m/$d" && clear && printf "Parent '%s' unmounted." "$d"
|
||||||
|
else
|
||||||
|
clear && printf "Failed to unmount. Try lazy unmount? [Yy/Nn] " && read -r
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If unmount fails, offer lazy unmount
|
||||||
|
if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then
|
||||||
|
err=0
|
||||||
|
cd "$m" && fusermount -uqz "$m/$d" || err=1
|
||||||
|
if [ "$err" -eq "0" ]; then
|
||||||
|
rmdir "$m/$d" && clear && printf "Parent '%s' unmounted with lazy unmount. " "$d"
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Selections are uploaded using Firefox Send
|
||||||
|
# For single files:
|
||||||
|
# Upload to Firefox Send if ffsend is found, else
|
||||||
|
# Paste contents of a text a file http://ix.io
|
||||||
|
# Upload a binary file to file.io
|
||||||
|
#
|
||||||
|
# Dependencies: ffsend (https://github.com/timvisee/ffsend), curl, jq, tr
|
||||||
|
#
|
||||||
|
# Note: Binary file set to expire after a week
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Arun Prakash Jana
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
if [ -s "$selection" ]; then
|
||||||
|
if type ffsend >/dev/null 2>&1; then
|
||||||
|
# File name will be randomized foo.tar
|
||||||
|
xargs -0 < "$selection" ffsend u
|
||||||
|
else
|
||||||
|
printf "ffsend is required to upload selection."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
printf "-" > "$NNN_PIPE"
|
||||||
|
else
|
||||||
|
if [ -n "$1" ] && [ -s "$1" ]; then
|
||||||
|
if type ffsend >/dev/null 2>&1; then
|
||||||
|
ffsend -fiq u "$1"
|
||||||
|
elif [ "$(mimetype --output-format %m "$1" | awk -F '/' '{print $1}')" = "text" ]; then
|
||||||
|
curl -F "f:1=@$1" ix.io
|
||||||
|
else
|
||||||
|
# Upload the file, show the download link and wait till user presses any key
|
||||||
|
curl -s -F "file=@$1" https://file.io/?expires=1w | jq '.link' | tr -d '"'
|
||||||
|
|
||||||
|
# To write download link to "$1".loc and exit
|
||||||
|
# curl -s -F "file=@$1" https://file.io/?expires=1w -o `basename "$1"`.loc
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "empty file!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r _
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Set the selected image as wallpaper.
|
||||||
|
# Uses nitrogen or pywal on X11, swww on wayland.
|
||||||
|
#
|
||||||
|
# Usage: Hover on an image and run the script to set it as wallpaper.
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: juacq97
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
if [ "$(file --mime-type "$1" | awk '{print $NF}' | awk -F '/' '{print $1}')" = "image" ]; then
|
||||||
|
if [ "$XDG_SESSION_TYPE" = "x11" ]; then
|
||||||
|
if type nitrogen >/dev/null 2>&1; then
|
||||||
|
nitrogen --set-zoom-fill --save "$1"
|
||||||
|
elif type wal >/dev/null 2>&1; then
|
||||||
|
wal -i "$1"
|
||||||
|
else
|
||||||
|
printf "nitrogen or pywal missing"
|
||||||
|
read -r _
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if type swww >/dev/null 2>&1; then
|
||||||
|
swww img "$1"
|
||||||
|
else
|
||||||
|
printf "swww missing"
|
||||||
|
read -r _
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If you want a system notification, uncomment the next 3 lines.
|
||||||
|
# notify-send -a "nnn" "Wallpaper changed!"
|
||||||
|
# else
|
||||||
|
# notify-send -a "nnn" "No image selected"
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Copy system clipboard newline-separated file list to selection
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - tr
|
||||||
|
# - xclip/xsel (Linux)
|
||||||
|
# - pbpaste (macOS)
|
||||||
|
# - termux-clipboard-get (Termux)
|
||||||
|
# - powershell (WSL)
|
||||||
|
# - cygwim's /dev/clipboard (Cygwin)
|
||||||
|
# - wl-paste (Wayland)
|
||||||
|
# - clipboard (Haiku)
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# - Limitation: breaks if a filename has newline in it
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Léo Villeveygoux, after Arun Prakash Jana's .cbcp
|
||||||
|
|
||||||
|
IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n
|
||||||
|
|
||||||
|
selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection}
|
||||||
|
|
||||||
|
getclip () {
|
||||||
|
if type xsel >/dev/null 2>&1; then
|
||||||
|
# Linux
|
||||||
|
xsel -bo
|
||||||
|
elif type xclip >/dev/null 2>&1; then
|
||||||
|
# Linux
|
||||||
|
xclip -sel clip -o
|
||||||
|
elif type pbpaste >/dev/null 2>&1; then
|
||||||
|
# macOS
|
||||||
|
pbpaste
|
||||||
|
elif type termux-clipboard-get >/dev/null 2>&1; then
|
||||||
|
# Termux
|
||||||
|
termux-clipboard-get
|
||||||
|
elif type powershell.exe >/dev/null 2>&1; then
|
||||||
|
# WSL
|
||||||
|
powershell.exe Get-Clipboard
|
||||||
|
elif [ -r /dev/clipboard ] ; then
|
||||||
|
# Cygwin
|
||||||
|
cat /dev/clipboard
|
||||||
|
elif type wl-paste >/dev/null 2>&1; then
|
||||||
|
# Wayland
|
||||||
|
wl-paste
|
||||||
|
elif type clipboard >/dev/null 2>&1; then
|
||||||
|
# Haiku
|
||||||
|
clipboard --print
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIPBOARD=$(getclip)
|
||||||
|
|
||||||
|
# Check if clipboard actually contains a file list
|
||||||
|
for file in $CLIPBOARD ; do
|
||||||
|
if [ ! -e "$file" ] ; then
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s" "$CLIPBOARD" | tr '\n' '\0' > "$selection"
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Sets the xdg-open's default application for the current entry's file
|
||||||
|
# type. ${XDG_DATA_DIRS} and ${XDG_DATA_HOME} are set to the recommended
|
||||||
|
# defaults if unset, as specified in XDG Base Directory Specification
|
||||||
|
# - http://specifications.freedesktop.org/basedir-spec/.
|
||||||
|
#
|
||||||
|
# Dependencies: xdg-utils, fzf or dmenu (GUI)
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: lwnctd
|
||||||
|
|
||||||
|
# set to 1 to enable GUI apps
|
||||||
|
GUI="${GUI:-0}"
|
||||||
|
|
||||||
|
if [ "$GUI" -ne 0 ] && command -v dmenu > /dev/null 2>& 1; then
|
||||||
|
menu="dmenu -i -l 7"
|
||||||
|
elif command -v fzf > /dev/null 2>& 1; then
|
||||||
|
menu="fzf -e --tiebreak=begin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$1" ] || [ -z "$menu" ] > /dev/null 2>& 1; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ftype=$(xdg-mime query filetype "$2/$1")
|
||||||
|
|
||||||
|
if [ -z "$ftype" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirs=${XDG_DATA_DIRS:-/usr/local/share:/usr/share}
|
||||||
|
dirs=${dirs}:${XDG_DATA_HOME:-$HOME/.local/share}:
|
||||||
|
|
||||||
|
while [ -n "$dirs" ]; do
|
||||||
|
d=${dirs%%:*}
|
||||||
|
if [ -n "$d" ] && [ -d "$d"/applications ]; then
|
||||||
|
set -- "$@" "$d"/applications
|
||||||
|
fi
|
||||||
|
dirs=${dirs#*:}
|
||||||
|
done
|
||||||
|
|
||||||
|
app=$(find "$@" -iname '*.desktop' -exec grep '^Name=' {} + \
|
||||||
|
| sort -u -t ':' -k 1,1 \
|
||||||
|
| sed -e 's;..*/\(..*desktop\):Name=\(..*\);\2:\1;' \
|
||||||
|
| sort -t ':' -k 1,1 \
|
||||||
|
| column -t -s ':' -o "$(printf '\t')" \
|
||||||
|
| $menu \
|
||||||
|
| cut -f 2)
|
||||||
|
|
||||||
|
if [ -n "$app" ]; then
|
||||||
|
xdg-mime default "${app%%[[:blank:]]*}" "$ftype"
|
||||||
|
fi
|
|
@ -0,0 +1,30 @@
|
||||||
|
n ()
|
||||||
|
{
|
||||||
|
# Block nesting of nnn in subshells
|
||||||
|
if [[ "${NNNLVL:-0}" -ge 1 ]]; then
|
||||||
|
echo "nnn is already running"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set)
|
||||||
|
# If NNN_TMPFILE is set to a custom path, it must be exported for nnn to
|
||||||
|
# see. To cd on quit only on ^G, remove the "export" and make sure not to
|
||||||
|
# use a custom path, i.e. set NNN_TMPFILE *exactly* as follows:
|
||||||
|
# NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd"
|
||||||
|
export NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd"
|
||||||
|
|
||||||
|
# Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn
|
||||||
|
# stty start undef
|
||||||
|
# stty stop undef
|
||||||
|
# stty lwrap undef
|
||||||
|
# stty lnext undef
|
||||||
|
|
||||||
|
# The backslash allows one to alias n to nnn if desired without making an
|
||||||
|
# infinitely recursive alias
|
||||||
|
\nnn "$@"
|
||||||
|
|
||||||
|
if [ -f "$NNN_TMPFILE" ]; then
|
||||||
|
. "$NNN_TMPFILE"
|
||||||
|
rm -f "$NNN_TMPFILE" > /dev/null
|
||||||
|
fi
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
# NOTE: set NNN_TMPFILE correctly if you use 'XDG_CONFIG_HOME'
|
||||||
|
|
||||||
|
# The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set)
|
||||||
|
# If NNN_TMPFILE is set to a custom path, it must be exported for nnn to see.
|
||||||
|
# To cd on quit only on ^G, set NNN_TMPFILE after the nnn invocation, and make
|
||||||
|
# sure not to use a custom path.
|
||||||
|
set NNN_TMPFILE=~/.config/nnn/.lastd
|
||||||
|
|
||||||
|
# Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn
|
||||||
|
# stty start undef
|
||||||
|
# stty stop undef
|
||||||
|
# stty lwrap undef
|
||||||
|
# stty lnext undef
|
||||||
|
|
||||||
|
# The backslash allows one to alias n to nnn if desired without making an
|
||||||
|
# infinitely recursive alias
|
||||||
|
alias n '\nnn; source "$NNN_TMPFILE"; rm -f "$NNN_TMPFILE"'
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Append this file to ~/.elvish/rc.elv (Elvish > 0.17.0)
|
||||||
|
|
||||||
|
use path
|
||||||
|
|
||||||
|
fn n {|@a|
|
||||||
|
# Block nesting of nnn in subshells
|
||||||
|
if (has-env NNNLVL) {
|
||||||
|
try {
|
||||||
|
if (>= $E:NNNLVL 1) {
|
||||||
|
echo "nnn is already running"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch e {
|
||||||
|
nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set)
|
||||||
|
# If NNN_TMPFILE is set to a custom path, it must be exported for nnn to
|
||||||
|
# see.
|
||||||
|
if (has-env XDG_CONFIG_HOME) {
|
||||||
|
set-env NNN_TMPFILE $E:XDG_CONFIG_HOME/nnn/.lastd
|
||||||
|
} else {
|
||||||
|
set-env NNN_TMPFILE $E:HOME/.config/nnn/.lastd
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn
|
||||||
|
# stty start undef
|
||||||
|
# stty stop undef
|
||||||
|
# stty lwrap undef
|
||||||
|
# stty lnext undef
|
||||||
|
|
||||||
|
# The e: prefix allows one to alias n to nnn if desired without making an
|
||||||
|
# infinitely recursive alias
|
||||||
|
e:nnn $@a
|
||||||
|
|
||||||
|
if (path:is-regular $E:NNN_TMPFILE) {
|
||||||
|
eval (slurp < $E:NNN_TMPFILE)
|
||||||
|
rm $E:NNN_TMPFILE
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Rename this file to match the name of the function
|
||||||
|
# e.g. ~/.config/fish/functions/n.fish
|
||||||
|
# or, add the lines to the 'config.fish' file.
|
||||||
|
|
||||||
|
function n --wraps nnn --description 'support nnn quit and change directory'
|
||||||
|
# Block nesting of nnn in subshells
|
||||||
|
if test -n "$NNNLVL" -a "$NNNLVL" -ge 1
|
||||||
|
echo "nnn is already running"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set)
|
||||||
|
# If NNN_TMPFILE is set to a custom path, it must be exported for nnn to
|
||||||
|
# see. To cd on quit only on ^G, remove the "-x" from both lines below,
|
||||||
|
# without changing the paths.
|
||||||
|
if test -n "$XDG_CONFIG_HOME"
|
||||||
|
set -x NNN_TMPFILE "$XDG_CONFIG_HOME/nnn/.lastd"
|
||||||
|
else
|
||||||
|
set -x NNN_TMPFILE "$HOME/.config/nnn/.lastd"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn
|
||||||
|
# stty start undef
|
||||||
|
# stty stop undef
|
||||||
|
# stty lwrap undef
|
||||||
|
# stty lnext undef
|
||||||
|
|
||||||
|
# The command function allows one to alias this function to `nnn` without
|
||||||
|
# making an infinitely recursive alias
|
||||||
|
command nnn $argv
|
||||||
|
|
||||||
|
if test -e $NNN_TMPFILE
|
||||||
|
source $NNN_TMPFILE
|
||||||
|
rm $NNN_TMPFILE
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,32 +0,0 @@
|
||||||
local return_code="%(?..%{$fg[red]%}%? ↵%{$reset_color%})"
|
|
||||||
local user_host="%B%(!.%{$fg[red]%}.%{$fg[green]%})%n@%m%{$reset_color%} "
|
|
||||||
local user_symbol='%(!.#.$)'
|
|
||||||
local current_dir="%B%{$fg[blue]%}%~ %{$reset_color%}"
|
|
||||||
|
|
||||||
local vcs_branch='$(git_prompt_info)$(hg_prompt_info)'
|
|
||||||
local rvm_ruby='$(ruby_prompt_info)'
|
|
||||||
local venv_prompt='$(virtualenv_prompt_info)'
|
|
||||||
|
|
||||||
ZSH_THEME_RVM_PROMPT_OPTIONS="i v g"
|
|
||||||
|
|
||||||
PROMPT="╭─${user_host}${current_dir}${rvm_ruby}${vcs_branch}${venv_prompt}
|
|
||||||
╰─%B${user_symbol}%b "
|
|
||||||
RPROMPT="%B${return_code}%b"
|
|
||||||
|
|
||||||
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg[yellow]%}‹"
|
|
||||||
ZSH_THEME_GIT_PROMPT_SUFFIX="› %{$reset_color%}"
|
|
||||||
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[red]%}●%{$fg[yellow]%}"
|
|
||||||
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[yellow]%}"
|
|
||||||
|
|
||||||
ZSH_THEME_HG_PROMPT_PREFIX="$ZSH_THEME_GIT_PROMPT_PREFIX"
|
|
||||||
ZSH_THEME_HG_PROMPT_SUFFIX="$ZSH_THEME_GIT_PROMPT_SUFFIX"
|
|
||||||
ZSH_THEME_HG_PROMPT_DIRTY="$ZSH_THEME_GIT_PROMPT_DIRTY"
|
|
||||||
ZSH_THEME_HG_PROMPT_CLEAN="$ZSH_THEME_GIT_PROMPT_CLEAN"
|
|
||||||
|
|
||||||
ZSH_THEME_RUBY_PROMPT_PREFIX="%{$fg[red]%}‹"
|
|
||||||
ZSH_THEME_RUBY_PROMPT_SUFFIX="› %{$reset_color%}"
|
|
||||||
|
|
||||||
ZSH_THEME_VIRTUAL_ENV_PROMPT_PREFIX="%{$fg[green]%}‹"
|
|
||||||
ZSH_THEME_VIRTUAL_ENV_PROMPT_SUFFIX="› %{$reset_color%}"
|
|
||||||
ZSH_THEME_VIRTUALENV_PREFIX="$ZSH_THEME_VIRTUAL_ENV_PROMPT_PREFIX"
|
|
||||||
ZSH_THEME_VIRTUALENV_SUFFIX="$ZSH_THEME_VIRTUAL_ENV_PROMPT_SUFFIX"
|
|
|
@ -1,5 +0,0 @@
|
||||||
* text=auto
|
|
||||||
*.zsh text eol=lf
|
|
||||||
*.zsh-theme text eol=lf
|
|
||||||
/prompt_powerlevel9k_setup text eol=lf
|
|
||||||
/prompt_powerlevel10k_setup text eol=lf
|
|
|
@ -1 +0,0 @@
|
||||||
*.zwc
|
|
|
@ -1,22 +0,0 @@
|
||||||
Copyright (c) 2009-2014 Robby Russell and contributors (see https://github.com/robbyrussell/oh-my-zsh/contributors)
|
|
||||||
Copyright (c) 2014-2017 Ben Hilburn <bhilburn@gmail.com>
|
|
||||||
Copyright (c) 2019 Roman Perepelitsa <roman.perepelitsa@gmail.com> and contributors (see https://github.com/romkatv/powerlevel10k/contributors)
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,14 +0,0 @@
|
||||||
ZSH := $(shell command -v zsh 2> /dev/null)
|
|
||||||
|
|
||||||
all:
|
|
||||||
|
|
||||||
zwc:
|
|
||||||
$(MAKE) -C gitstatus zwc
|
|
||||||
$(or $(ZSH),:) -fc 'for f in *.zsh-theme internal/*.zsh; do zcompile -R -- $$f.zwc $$f || exit; done'
|
|
||||||
|
|
||||||
minify:
|
|
||||||
$(MAKE) -C gitstatus minify
|
|
||||||
rm -rf -- .git .gitattributes .gitignore LICENSE Makefile README.md font.md powerlevel10k.png
|
|
||||||
|
|
||||||
pkg: zwc
|
|
||||||
$(MAKE) -C gitstatus pkg
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,193 +0,0 @@
|
||||||
# Config file for Powerlevel10k with the style of Pure (https://github.com/sindresorhus/pure).
|
|
||||||
#
|
|
||||||
# Differences from Pure:
|
|
||||||
#
|
|
||||||
# - Git:
|
|
||||||
# - `@c4d3ec2c` instead of something like `v1.4.0~11` when in detached HEAD state.
|
|
||||||
# - No automatic `git fetch` (the same as in Pure with `PURE_GIT_PULL=0`).
|
|
||||||
#
|
|
||||||
# Apart from the differences listed above, the replication of Pure prompt is exact. This includes
|
|
||||||
# even the questionable parts. For example, just like in Pure, there is no indication of Git status
|
|
||||||
# being stale; prompt symbol is the same in command, visual and overwrite vi modes; when prompt
|
|
||||||
# doesn't fit on one line, it wraps around with no attempt to shorten it.
|
|
||||||
#
|
|
||||||
# If you like the general style of Pure but not particularly attached to all its quirks, type
|
|
||||||
# `p10k configure` and pick "Lean" style. This will give you slick minimalist prompt while taking
|
|
||||||
# advantage of Powerlevel10k features that aren't present in Pure.
|
|
||||||
|
|
||||||
# Temporarily change options.
|
|
||||||
'builtin' 'local' '-a' 'p10k_config_opts'
|
|
||||||
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
|
|
||||||
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
|
|
||||||
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
|
|
||||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
|
||||||
|
|
||||||
() {
|
|
||||||
emulate -L zsh -o extended_glob
|
|
||||||
|
|
||||||
# Unset all configuration options.
|
|
||||||
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
|
|
||||||
|
|
||||||
# Zsh >= 5.1 is required.
|
|
||||||
[[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
|
|
||||||
|
|
||||||
# Prompt colors.
|
|
||||||
local grey=242
|
|
||||||
local red=1
|
|
||||||
local yellow=3
|
|
||||||
local blue=4
|
|
||||||
local magenta=5
|
|
||||||
local cyan=6
|
|
||||||
local white=7
|
|
||||||
|
|
||||||
# Left prompt segments.
|
|
||||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
|
|
||||||
# =========================[ Line #1 ]=========================
|
|
||||||
context # user@host
|
|
||||||
dir # current directory
|
|
||||||
vcs # git status
|
|
||||||
command_execution_time # previous command duration
|
|
||||||
# =========================[ Line #2 ]=========================
|
|
||||||
newline # \n
|
|
||||||
virtualenv # python virtual environment
|
|
||||||
prompt_char # prompt symbol
|
|
||||||
)
|
|
||||||
|
|
||||||
# Right prompt segments.
|
|
||||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
|
|
||||||
# =========================[ Line #1 ]=========================
|
|
||||||
# command_execution_time # previous command duration
|
|
||||||
# virtualenv # python virtual environment
|
|
||||||
# context # user@host
|
|
||||||
# time # current time
|
|
||||||
# =========================[ Line #2 ]=========================
|
|
||||||
newline # \n
|
|
||||||
)
|
|
||||||
|
|
||||||
# Basic style options that define the overall prompt look.
|
|
||||||
typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
|
|
||||||
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons
|
|
||||||
|
|
||||||
# Add an empty line before each prompt except the first. This doesn't emulate the bug
|
|
||||||
# in Pure that makes prompt drift down whenever you use the Alt-C binding from fzf or similar.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true
|
|
||||||
|
|
||||||
# Magenta prompt symbol if the last command succeeded.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta
|
|
||||||
# Red prompt symbol if the last command failed.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red
|
|
||||||
# Default prompt symbol.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯'
|
|
||||||
# Prompt symbol in command vi mode.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮'
|
|
||||||
# Prompt symbol in visual vi mode is the same as in command mode.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='❮'
|
|
||||||
# Prompt symbol in overwrite vi mode is the same as in command mode.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false
|
|
||||||
|
|
||||||
# Grey Python Virtual Environment.
|
|
||||||
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$grey
|
|
||||||
# Don't show Python version.
|
|
||||||
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
|
|
||||||
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
|
|
||||||
|
|
||||||
# Blue current directory.
|
|
||||||
typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue
|
|
||||||
|
|
||||||
# Context format when root: user@host. The first part white, the rest grey.
|
|
||||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f"
|
|
||||||
# Context format when not root: user@host. The whole thing grey.
|
|
||||||
typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f"
|
|
||||||
# Don't show context unless root or in SSH.
|
|
||||||
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION=
|
|
||||||
|
|
||||||
# Show previous command duration only if it's >= 5s.
|
|
||||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5
|
|
||||||
# Don't show fractional seconds. Thus, 7s rather than 7.3s.
|
|
||||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
|
|
||||||
# Duration format: 1d 2h 3m 4s.
|
|
||||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
|
|
||||||
# Yellow previous command duration.
|
|
||||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow
|
|
||||||
|
|
||||||
# Grey Git prompt. This makes stale prompts indistinguishable from up-to-date ones.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey
|
|
||||||
|
|
||||||
# Disable async loading indicator to make directories that aren't Git repositories
|
|
||||||
# indistinguishable from large Git repositories without known state.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_LOADING_TEXT=
|
|
||||||
|
|
||||||
# Don't wait for Git status even for a millisecond, so that prompt always updates
|
|
||||||
# asynchronously when Git state changes.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0
|
|
||||||
|
|
||||||
# Cyan ahead/behind arrows.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan
|
|
||||||
# Don't show remote branch, current tag or stashes.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind)
|
|
||||||
# Don't show the branch icon.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
|
|
||||||
# When in detached HEAD state, show @commit where branch normally goes.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@'
|
|
||||||
# Don't show staged, unstaged, untracked indicators.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON=
|
|
||||||
# Show '*' when there are staged, unstaged or untracked files.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*'
|
|
||||||
# Show '⇣' if local branch is behind remote.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣'
|
|
||||||
# Show '⇡' if local branch is ahead of remote.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡'
|
|
||||||
# Don't show the number of commits next to the ahead/behind arrows.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1
|
|
||||||
# Remove space between '⇣' and '⇡' and all trailing spaces.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }'
|
|
||||||
|
|
||||||
# Grey current time.
|
|
||||||
typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey
|
|
||||||
# Format for the current time: 09:51:02. See `man 3 strftime`.
|
|
||||||
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
|
|
||||||
# If set to true, time will update when you hit enter. This way prompts for the past
|
|
||||||
# commands will contain the start times of their commands rather than the end times of
|
|
||||||
# their preceding commands.
|
|
||||||
typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
|
|
||||||
|
|
||||||
# Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
|
|
||||||
# when accepting a command line. Supported values:
|
|
||||||
#
|
|
||||||
# - off: Don't change prompt when accepting a command line.
|
|
||||||
# - always: Trim down prompt when accepting a command line.
|
|
||||||
# - same-dir: Trim down prompt when accepting a command line unless this is the first command
|
|
||||||
# typed after changing current working directory.
|
|
||||||
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
|
|
||||||
|
|
||||||
# Instant prompt mode.
|
|
||||||
#
|
|
||||||
# - off: Disable instant prompt. Choose this if you've tried instant prompt and found
|
|
||||||
# it incompatible with your zsh configuration files.
|
|
||||||
# - quiet: Enable instant prompt and don't print warnings when detecting console output
|
|
||||||
# during zsh initialization. Choose this if you've read and understood
|
|
||||||
# https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
|
|
||||||
# - verbose: Enable instant prompt and print a warning when detecting console output during
|
|
||||||
# zsh initialization. Choose this if you've never tried instant prompt, haven't
|
|
||||||
# seen the warning, or if you are unsure what this all means.
|
|
||||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
|
|
||||||
|
|
||||||
# Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
|
|
||||||
# For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
|
|
||||||
# can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
|
|
||||||
# really need it.
|
|
||||||
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
|
|
||||||
|
|
||||||
# If p10k is already loaded, reload configuration.
|
|
||||||
# This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
|
|
||||||
(( ! $+functions[p10k] )) || p10k reload
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tell `p10k configure` which file it should overwrite.
|
|
||||||
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
|
|
||||||
|
|
||||||
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
|
|
||||||
'builtin' 'unset' 'p10k_config_opts'
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,111 +0,0 @@
|
||||||
# Config file for Powerlevel10k with the style of robbyrussell theme from Oh My Zsh.
|
|
||||||
#
|
|
||||||
# Original: https://github.com/ohmyzsh/ohmyzsh/wiki/Themes#robbyrussell.
|
|
||||||
#
|
|
||||||
# Replication of robbyrussell theme is exact. The only observable difference is in
|
|
||||||
# performance. Powerlevel10k prompt is very fast everywhere, even in large Git repositories.
|
|
||||||
#
|
|
||||||
# Usage: Source this file either before or after loading Powerlevel10k.
|
|
||||||
#
|
|
||||||
# source ~/powerlevel10k/config/p10k-robbyrussell.zsh
|
|
||||||
# source ~/powerlevel10k/powerlevel10k.zsh-theme
|
|
||||||
|
|
||||||
# Temporarily change options.
|
|
||||||
'builtin' 'local' '-a' 'p10k_config_opts'
|
|
||||||
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
|
|
||||||
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
|
|
||||||
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
|
|
||||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
|
||||||
|
|
||||||
() {
|
|
||||||
emulate -L zsh -o extended_glob
|
|
||||||
|
|
||||||
# Unset all configuration options.
|
|
||||||
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
|
|
||||||
|
|
||||||
# Zsh >= 5.1 is required.
|
|
||||||
[[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
|
|
||||||
|
|
||||||
# Left prompt segments.
|
|
||||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(prompt_char dir vcs)
|
|
||||||
# Right prompt segments.
|
|
||||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()
|
|
||||||
|
|
||||||
# Basic style options that define the overall prompt look.
|
|
||||||
typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
|
|
||||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
|
|
||||||
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons
|
|
||||||
|
|
||||||
# Green prompt symbol if the last command succeeded.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=green
|
|
||||||
# Red prompt symbol if the last command failed.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=red
|
|
||||||
# Prompt symbol: bold arrow.
|
|
||||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_CONTENT_EXPANSION='%B➜ '
|
|
||||||
|
|
||||||
# Cyan current directory.
|
|
||||||
typeset -g POWERLEVEL9K_DIR_FOREGROUND=cyan
|
|
||||||
# Show only the last segment of the current directory.
|
|
||||||
typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last
|
|
||||||
# Bold directory.
|
|
||||||
typeset -g POWERLEVEL9K_DIR_CONTENT_EXPANSION='%B$P9K_CONTENT'
|
|
||||||
|
|
||||||
# Git status formatter.
|
|
||||||
function my_git_formatter() {
|
|
||||||
emulate -L zsh
|
|
||||||
if [[ -n $P9K_CONTENT ]]; then
|
|
||||||
# If P9K_CONTENT is not empty, it's either "loading" or from vcs_info (not from
|
|
||||||
# gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
|
|
||||||
typeset -g my_git_format=$P9K_CONTENT
|
|
||||||
else
|
|
||||||
# Use VCS_STATUS_* parameters to assemble Git status. See reference:
|
|
||||||
# https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh.
|
|
||||||
typeset -g my_git_format="${1+%B%4F}git:(${1+%1F}"
|
|
||||||
my_git_format+=${${VCS_STATUS_LOCAL_BRANCH:-${VCS_STATUS_COMMIT[1,8]}}//\%/%%}
|
|
||||||
my_git_format+="${1+%4F})"
|
|
||||||
if (( VCS_STATUS_NUM_CONFLICTED || VCS_STATUS_NUM_STAGED ||
|
|
||||||
VCS_STATUS_NUM_UNSTAGED || VCS_STATUS_NUM_UNTRACKED )); then
|
|
||||||
my_git_format+=" ${1+%3F}✗"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
functions -M my_git_formatter 2>/dev/null
|
|
||||||
|
|
||||||
# Disable the default Git status formatting.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
|
|
||||||
# Install our own Git status formatter.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}'
|
|
||||||
typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter()))+${my_git_format}}'
|
|
||||||
# Grey Git status when loading.
|
|
||||||
typeset -g POWERLEVEL9K_VCS_LOADING_FOREGROUND=246
|
|
||||||
|
|
||||||
# Instant prompt mode.
|
|
||||||
#
|
|
||||||
# - off: Disable instant prompt. Choose this if you've tried instant prompt and found
|
|
||||||
# it incompatible with your zsh configuration files.
|
|
||||||
# - quiet: Enable instant prompt and don't print warnings when detecting console output
|
|
||||||
# during zsh initialization. Choose this if you've read and understood
|
|
||||||
# https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
|
|
||||||
# - verbose: Enable instant prompt and print a warning when detecting console output during
|
|
||||||
# zsh initialization. Choose this if you've never tried instant prompt, haven't
|
|
||||||
# seen the warning, or if you are unsure what this all means.
|
|
||||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
|
|
||||||
|
|
||||||
# Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
|
|
||||||
# For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
|
|
||||||
# can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
|
|
||||||
# really need it.
|
|
||||||
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
|
|
||||||
|
|
||||||
# If p10k is already loaded, reload configuration.
|
|
||||||
# This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
|
|
||||||
(( ! $+functions[p10k] )) || p10k reload
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tell `p10k configure` which file it should overwrite.
|
|
||||||
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
|
|
||||||
|
|
||||||
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
|
|
||||||
'builtin' 'unset' 'p10k_config_opts'
|
|
|
@ -1,152 +0,0 @@
|
||||||
# Recommended font: Meslo Nerd Font patched for Powerlevel10k
|
|
||||||
|
|
||||||
Gorgeous monospace font designed by Jim Lyles for Bitstream, customized by the same for Apple,
|
|
||||||
further customized by André Berg, and finally patched by yours truly with customized scripts
|
|
||||||
originally developed by Ryan L McIntyre of Nerd Fonts. Contains all glyphs and symbols that
|
|
||||||
Powerlevel10k may need. Battle-tested in dozens of different terminals on all major operating
|
|
||||||
systems.
|
|
||||||
|
|
||||||
*FAQ*: [How was the recommended font created?](README.md#how-was-the-recommended-font-created)
|
|
||||||
|
|
||||||
## Automatic font installation
|
|
||||||
|
|
||||||
If you are using iTerm2 or Termux, `p10k configure` can install the recommended font for you.
|
|
||||||
Simply answer `Yes` when asked whether to install *Meslo Nerd Font*.
|
|
||||||
|
|
||||||
If you are using a different terminal, proceed with manual font installation. 👇
|
|
||||||
|
|
||||||
## Manual font installation
|
|
||||||
|
|
||||||
1. Download these four ttf files:
|
|
||||||
- [MesloLGS NF Regular.ttf](
|
|
||||||
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
|
|
||||||
- [MesloLGS NF Bold.ttf](
|
|
||||||
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
|
|
||||||
- [MesloLGS NF Italic.ttf](
|
|
||||||
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
|
|
||||||
- [MesloLGS NF Bold Italic.ttf](
|
|
||||||
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
|
|
||||||
1. Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
|
|
||||||
applications on your system.
|
|
||||||
1. Configure your terminal to use this font:
|
|
||||||
- **iTerm2**: Type `p10k configure` and answer `Yes` when asked whether to install
|
|
||||||
*Meslo Nerd Font*. Alternatively, open *iTerm2 → Preferences → Profiles → Text* and set *Font* to
|
|
||||||
`MesloLGS NF`.
|
|
||||||
- **Apple Terminal**: Open *Terminal → Preferences → Profiles → Text*, click *Change* under *Font*
|
|
||||||
and select `MesloLGS NF` family.
|
|
||||||
- **Hyper**: Open *Hyper → Edit → Preferences* and change the value of `fontFamily` under
|
|
||||||
`module.exports.config` to `MesloLGS NF`.
|
|
||||||
- **Visual Studio Code**: Open *File → Preferences → Settings* (PC) or
|
|
||||||
*Code → Preferences → Settings* (Mac), enter `terminal.integrated.fontFamily` in the search box at
|
|
||||||
the top of *Settings* tab and set the value below to `MesloLGS NF`.
|
|
||||||
Consult [this screenshot](
|
|
||||||
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/389133fb8c9a2347929a23702ce3039aacc46c3d/visual-studio-code-font-settings.jpg)
|
|
||||||
to see how it should look like or see [this issue](
|
|
||||||
https://github.com/romkatv/powerlevel10k/issues/671) for extra information.
|
|
||||||
- **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal → Preferences* and click on the
|
|
||||||
selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
|
|
||||||
`MesloLGS NF Regular`.
|
|
||||||
- **Konsole**: Open *Settings → Edit Current Profile → Appearance*, click *Select Font* and select
|
|
||||||
`MesloLGS NF Regular`.
|
|
||||||
- **Tilix**: Open *Tilix → Preferences* and click on the selected profile under *Profiles*. Check
|
|
||||||
*Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
|
|
||||||
- **Windows Console Host** (the old thing): Click the icon in the top left corner, then
|
|
||||||
*Properties → Font* and set *Font* to `MesloLGS NF`.
|
|
||||||
- **Windows Terminal** by Microsoft (the new thing): Open *Settings* (<kbd>Ctrl+,</kbd>), click
|
|
||||||
either on the selected profile under *Profiles* or on *Defaults*, click *Appearance* and set
|
|
||||||
*Font face* to `MesloLGS NF`.
|
|
||||||
- **IntelliJ** (and other IDEs by Jet Brains): Open *IDE → Edit → Preferences → Editor →
|
|
||||||
Color Scheme → Console Font*. Select *Use console font instead of the default* and set the font
|
|
||||||
name to `MesloLGS NF`.
|
|
||||||
- **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
|
|
||||||
*Meslo Nerd Font*.
|
|
||||||
- **Blink**: Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
|
|
||||||
*MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
|
|
||||||
- **Terminus**: Open *Settings → Appearance* and set *Font* to `MesloLGS NF`.
|
|
||||||
- **Terminator**: Open *Preferences* using the context menu. Under *Profiles* select the *General*
|
|
||||||
tab (should be selected already), uncheck *Use the system fixed width font* (if not already)
|
|
||||||
and select `MesloLGS NF Regular`. Exit the Preferences dialog by clicking *Close*.
|
|
||||||
- **Guake**: Right Click on an open terminal and open *Preferences*. Under *Appearance*
|
|
||||||
tab, uncheck *Use the system fixed width font* (if not already) and select `MesloLGS NF Regular`.
|
|
||||||
Exit the Preferences dialog by clicking *Close*.
|
|
||||||
- **MobaXterm**: Open *Settings* → *Configuration* → *Terminal* → (under *Terminal look and feel*)
|
|
||||||
and change *Font* to `MesloLGS NF`.
|
|
||||||
- **Asbrú Connection Manager**: Open *Preferences → Local Shell Options → Look and Feel*, enable
|
|
||||||
*Use these personal options* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
|
|
||||||
To change the font for the remote host connections, go to *Preferences → Terminal Options →
|
|
||||||
Look and Feel* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
|
|
||||||
- **WSLtty**: Right click on an open terminal and then on *Options*. In the *Text* section, under
|
|
||||||
*Font*, click *"Select..."* and set Font to `MesloLGS NF Regular`.
|
|
||||||
- **Yakuake**: Click *≡* → *Manage Profiles* → *New* → *Appearance*. Click *Choose* next to the
|
|
||||||
*Font* dropdown, select `MesloLGS NF` and click *OK*. Click *OK* to save the profile. Select the
|
|
||||||
new profile and click *Set as Default*.
|
|
||||||
- **Alacritty**: Create or open `~/.config/alacritty/alacritty.yml` and add the following section
|
|
||||||
to it:
|
|
||||||
```yaml
|
|
||||||
font:
|
|
||||||
normal:
|
|
||||||
family: "MesloLGS NF"
|
|
||||||
```
|
|
||||||
- **kitty**: Create or open `~/.config/kitty/kitty.conf` and add the following line to it:
|
|
||||||
```text
|
|
||||||
font_family MesloLGS NF
|
|
||||||
```
|
|
||||||
Restart kitty by closing all sessions and opening a new session.
|
|
||||||
- **puTTY**: Set *Window* → *Appearance* → *Font* to `MesloLGS NF`. Requires puTTY
|
|
||||||
version >= 0.75.
|
|
||||||
- **WezTerm**: Create or open `$HOME/.config/wezterm/wezterm.lua` and add the following:
|
|
||||||
```lua
|
|
||||||
local wezterm = require 'wezterm';
|
|
||||||
return {
|
|
||||||
font = wezterm.font("MesloLGS NF"),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
If the file already exists, only add the line with the font to the existing return.
|
|
||||||
Also add the first line if it is not already present.
|
|
||||||
- **urxvt**: Create or open `~/.Xresources` and add the following line to it:
|
|
||||||
```text
|
|
||||||
URxvt.font: xft:MesloLGS NF:size=11
|
|
||||||
```
|
|
||||||
You can adjust the font size to your preference. After changing the config run
|
|
||||||
`xrdb ~/.Xresources` to reload it. The new config is applied to all new terminals.
|
|
||||||
- **xterm**: Create or open `~/.Xresources` and add the following line to it:
|
|
||||||
```text
|
|
||||||
xterm*faceName: MesloLGS NF
|
|
||||||
```
|
|
||||||
After changing the config run `xrdb ~/.Xresources` to reload it. The new config is applied to
|
|
||||||
all new terminals.
|
|
||||||
- Crostini (Linux on Chrome OS): Open
|
|
||||||
chrome-untrusted://terminal/html/nassh_preferences_editor.html, set *Text font family* to
|
|
||||||
`'MesloLGS NF'` (including the quotes) and *Custom CSS (inline text)* to the following:
|
|
||||||
```css
|
|
||||||
@font-face {
|
|
||||||
font-family: "MesloLGS NF";
|
|
||||||
src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Regular.ttf");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "MesloLGS NF";
|
|
||||||
src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold.ttf");
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "MesloLGS NF";
|
|
||||||
src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Italic.ttf");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "MesloLGS NF";
|
|
||||||
src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold%20Italic.ttf");
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**_CAVEAT_**: If you open the normal terminal preferences these settings will be overwritten.
|
|
||||||
1. Run `p10k configure` to generate a new `~/.p10k.zsh`. The old config may work
|
|
||||||
incorrectly with the new font.
|
|
||||||
|
|
||||||
_Using a different terminal and know how to set the font for it? Share your knowledge by sending a
|
|
||||||
PR to expand the list!_
|
|
|
@ -1,4 +0,0 @@
|
||||||
BasedOnStyle: Google
|
|
||||||
ColumnLimit: 100
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
PointerAlignment: Left
|
|
|
@ -1,16 +0,0 @@
|
||||||
* text=auto
|
|
||||||
|
|
||||||
*.cc text eol=lf
|
|
||||||
*.h text eol=lf
|
|
||||||
*.info text eol=lf
|
|
||||||
*.json text eol=lf
|
|
||||||
*.md text eol=lf
|
|
||||||
*.sh text eol=lf
|
|
||||||
*.zsh text eol=lf
|
|
||||||
|
|
||||||
/.clang-format text eol=lf
|
|
||||||
/LICENSE text eol=lf
|
|
||||||
/Makefile text eol=lf
|
|
||||||
/build text eol=lf
|
|
||||||
/install text eol=lf
|
|
||||||
/mbuild text eol=lf
|
|
|
@ -1,8 +0,0 @@
|
||||||
*.zwc
|
|
||||||
/core
|
|
||||||
/deps/libgit2-*.tar.gz
|
|
||||||
/locks
|
|
||||||
/logs
|
|
||||||
/obj
|
|
||||||
/usrbin/gitstatusd*
|
|
||||||
/.vscode/ipch
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Linux",
|
|
||||||
"includePath": [
|
|
||||||
"${workspaceFolder}/src"
|
|
||||||
],
|
|
||||||
"defines": [
|
|
||||||
],
|
|
||||||
"compilerPath": "/usr/bin/g++",
|
|
||||||
"cStandard": "c11",
|
|
||||||
"cppStandard": "c++17",
|
|
||||||
"intelliSenseMode": "gcc-x64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version": 4
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"*.zwc": true,
|
|
||||||
"core": true,
|
|
||||||
"locks/": true,
|
|
||||||
"logs/": true,
|
|
||||||
"obj/": true,
|
|
||||||
"usrbin/": true,
|
|
||||||
},
|
|
||||||
"files.associations": {
|
|
||||||
"array": "cpp",
|
|
||||||
"atomic": "cpp",
|
|
||||||
"*.tcc": "cpp",
|
|
||||||
"cctype": "cpp",
|
|
||||||
"chrono": "cpp",
|
|
||||||
"clocale": "cpp",
|
|
||||||
"cmath": "cpp",
|
|
||||||
"complex": "cpp",
|
|
||||||
"condition_variable": "cpp",
|
|
||||||
"cstddef": "cpp",
|
|
||||||
"cstdint": "cpp",
|
|
||||||
"cstdio": "cpp",
|
|
||||||
"cstdlib": "cpp",
|
|
||||||
"cstring": "cpp",
|
|
||||||
"ctime": "cpp",
|
|
||||||
"cwchar": "cpp",
|
|
||||||
"cwctype": "cpp",
|
|
||||||
"deque": "cpp",
|
|
||||||
"unordered_map": "cpp",
|
|
||||||
"unordered_set": "cpp",
|
|
||||||
"vector": "cpp",
|
|
||||||
"exception": "cpp",
|
|
||||||
"fstream": "cpp",
|
|
||||||
"functional": "cpp",
|
|
||||||
"future": "cpp",
|
|
||||||
"initializer_list": "cpp",
|
|
||||||
"iomanip": "cpp",
|
|
||||||
"iosfwd": "cpp",
|
|
||||||
"iostream": "cpp",
|
|
||||||
"istream": "cpp",
|
|
||||||
"limits": "cpp",
|
|
||||||
"memory": "cpp",
|
|
||||||
"mutex": "cpp",
|
|
||||||
"new": "cpp",
|
|
||||||
"numeric": "cpp",
|
|
||||||
"optional": "cpp",
|
|
||||||
"ostream": "cpp",
|
|
||||||
"ratio": "cpp",
|
|
||||||
"sstream": "cpp",
|
|
||||||
"stdexcept": "cpp",
|
|
||||||
"streambuf": "cpp",
|
|
||||||
"string_view": "cpp",
|
|
||||||
"system_error": "cpp",
|
|
||||||
"thread": "cpp",
|
|
||||||
"type_traits": "cpp",
|
|
||||||
"tuple": "cpp",
|
|
||||||
"typeinfo": "cpp",
|
|
||||||
"utility": "cpp",
|
|
||||||
"variant": "cpp",
|
|
||||||
"cstdarg": "cpp",
|
|
||||||
"charconv": "cpp",
|
|
||||||
"algorithm": "cpp",
|
|
||||||
"cinttypes": "cpp",
|
|
||||||
"iterator": "cpp",
|
|
||||||
"map": "cpp",
|
|
||||||
"memory_resource": "cpp",
|
|
||||||
"random": "cpp",
|
|
||||||
"string": "cpp",
|
|
||||||
"bit": "cpp",
|
|
||||||
"netfwd": "cpp"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,674 +0,0 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
|
@ -1,46 +0,0 @@
|
||||||
APPNAME ?= gitstatusd
|
|
||||||
OBJDIR ?= obj
|
|
||||||
|
|
||||||
CXX ?= g++
|
|
||||||
ZSH := $(shell command -v zsh 2> /dev/null)
|
|
||||||
|
|
||||||
VERSION ?= $(shell . ./build.info && printf "%s" "$$gitstatus_version")
|
|
||||||
|
|
||||||
# Note: -fsized-deallocation is not used to avoid binary compatibility issues on macOS.
|
|
||||||
#
|
|
||||||
# Sized delete is implemented as __ZdlPvm in /usr/lib/libc++.1.dylib but this symbol is
|
|
||||||
# missing in macOS prior to 10.13.
|
|
||||||
CXXFLAGS += -std=c++14 -funsigned-char -O3 -DNDEBUG -DGITSTATUS_VERSION=$(VERSION) -Wall -Werror # -g -fsanitize=thread
|
|
||||||
LDFLAGS += -pthread # -fsanitize=thread
|
|
||||||
LDLIBS += -lgit2 # -lprofiler -lunwind
|
|
||||||
|
|
||||||
SRCS := $(shell find src -name "*.cc")
|
|
||||||
OBJS := $(patsubst src/%.cc, $(OBJDIR)/%.o, $(SRCS))
|
|
||||||
|
|
||||||
all: $(APPNAME)
|
|
||||||
|
|
||||||
$(APPNAME): usrbin/$(APPNAME)
|
|
||||||
|
|
||||||
usrbin/$(APPNAME): $(OBJS)
|
|
||||||
$(CXX) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@
|
|
||||||
|
|
||||||
$(OBJDIR):
|
|
||||||
mkdir -p -- $(OBJDIR)
|
|
||||||
|
|
||||||
$(OBJDIR)/%.o: src/%.cc Makefile build.info | $(OBJDIR)
|
|
||||||
$(CXX) $(CXXFLAGS) -MM -MT $@ src/$*.cc >$(OBJDIR)/$*.dep
|
|
||||||
$(CXX) $(CXXFLAGS) -Wall -c -o $@ src/$*.cc
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf -- $(OBJDIR)
|
|
||||||
|
|
||||||
zwc:
|
|
||||||
$(or $(ZSH),:) -fc 'for f in *.zsh install; do zcompile -R -- $$f.zwc $$f || exit; done'
|
|
||||||
|
|
||||||
minify:
|
|
||||||
rm -rf -- .clang-format .git .gitattributes .gitignore .vscode deps docs src usrbin/.gitkeep LICENSE Makefile README.md build mbuild
|
|
||||||
|
|
||||||
pkg: zwc
|
|
||||||
GITSTATUS_DAEMON= GITSTATUS_CACHE_DIR=$(shell pwd)/usrbin ./install -f
|
|
||||||
|
|
||||||
-include $(OBJS:.o=.dep)
|
|
|
@ -1,530 +0,0 @@
|
||||||
# gitstatus
|
|
||||||
|
|
||||||
**gitstatus** is a 10x faster alternative to `git status` and `git describe`. Its primary use
|
|
||||||
case is to enable fast git prompt in interactive shells.
|
|
||||||
|
|
||||||
Heavy lifting is done by **gitstatusd** -- a custom binary written in C++. It comes with Zsh and
|
|
||||||
Bash bindings for integration with shell.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Using from Zsh](#using-from-zsh)
|
|
||||||
1. [Using from Bash](#using-from-bash)
|
|
||||||
2. [Using from other shells](#using-from-other-shells)
|
|
||||||
1. [How it works](#how-it-works)
|
|
||||||
1. [Benchmarks](#benchmarks)
|
|
||||||
1. [Why fast](#why-fast)
|
|
||||||
1. [Requirements](#requirements)
|
|
||||||
1. [Compiling](#compiling)
|
|
||||||
1. [License](#license)
|
|
||||||
|
|
||||||
## Using from Zsh
|
|
||||||
|
|
||||||
The easiest way to take advantage of gitstatus from Zsh is to use a theme that's already integrated
|
|
||||||
with it. For example, [Powerlevel10k](https://github.com/romkatv/powerlevel10k) is a flexible and
|
|
||||||
fast theme with first-class gitstatus integration. If you install Powerlevel10k, you don't need to
|
|
||||||
install gitstatus.
|
|
||||||
|
|
||||||
![Powerlevel10k Zsh Theme](
|
|
||||||
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/prompt-styles-high-contrast.png)
|
|
||||||
|
|
||||||
For those who wish to use gitstatus without a theme, there is
|
|
||||||
[gitstatus.prompt.zsh](gitstatus.prompt.zsh). Install it as follows:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
|
|
||||||
echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
|
|
||||||
```
|
|
||||||
|
|
||||||
Users in China can use the official mirror on gitee.com for faster download.<br>
|
|
||||||
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
|
|
||||||
echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, if you have Homebrew installed:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
brew install romkatv/gitstatus/gitstatus
|
|
||||||
echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh" >>! ~/.zshrc
|
|
||||||
```
|
|
||||||
|
|
||||||
(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`
|
|
||||||
in all code snippets below.)
|
|
||||||
|
|
||||||
_Make sure to disable your current theme if you have one._
|
|
||||||
|
|
||||||
This will give you a basic yet functional prompt with git status in it. It's
|
|
||||||
[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt. In order
|
|
||||||
to customize it, set `PROMPT` and/or `RPROMPT` at the end of `~/.zshrc` after sourcing
|
|
||||||
`gitstatus.prompt.zsh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
source ~/gitstatus/gitstatus.prompt.zsh
|
|
||||||
|
|
||||||
PROMPT='%~%# ' # left prompt: directory followed by %/# (normal/root)
|
|
||||||
RPROMPT='$GITSTATUS_PROMPT' # right prompt: git status
|
|
||||||
```
|
|
||||||
|
|
||||||
The expansion of `${GITSTATUS_PROMPT}` can contain the following bits:
|
|
||||||
|
|
||||||
| segment | meaning |
|
|
||||||
|-------------|-------------------------------------------------------|
|
|
||||||
| `master` | current branch |
|
|
||||||
| `#v1` | HEAD is tagged with `v1`; not shown when on a branch |
|
|
||||||
| `@5fc6fca4` | current commit; not shown when on a branch or tag |
|
|
||||||
| `⇣1` | local branch is behind the remote by 1 commit |
|
|
||||||
| `⇡2` | local branch is ahead of the remote by 2 commits |
|
|
||||||
| `⇠3` | local branch is behind the push remote by 3 commits |
|
|
||||||
| `⇢4` | local branch is ahead of the push remote by 4 commits |
|
|
||||||
| `*5` | there are 5 stashes |
|
|
||||||
| `merge` | merge is in progress (could be some other action) |
|
|
||||||
| `~6` | there are 6 merge conflicts |
|
|
||||||
| `+7` | there are 7 staged changes |
|
|
||||||
| `!8` | there are 8 unstaged changes |
|
|
||||||
| `?9` | there are 9 untracked files |
|
|
||||||
|
|
||||||
`$GITSTATUS_PROMPT_LEN` tells you how long `$GITSTATUS_PROMPT` is when printed to the console.
|
|
||||||
[gitstatus.prompt.zsh](gitstatus.prompt.zsh) has an example of using it to truncate the current
|
|
||||||
directory.
|
|
||||||
|
|
||||||
If you'd like to change the format of git status, or want to have greater control over the
|
|
||||||
process of assembling `PROMPT`, you can copy and modify parts of
|
|
||||||
[gitstatus.prompt.zsh](gitstatus.prompt.zsh) instead of sourcing the script. Your `~/.zshrc`
|
|
||||||
might look something like this:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
source ~/gitstatus/gitstatus.plugin.zsh
|
|
||||||
|
|
||||||
function my_set_prompt() {
|
|
||||||
PROMPT='%~%# '
|
|
||||||
RPROMPT=''
|
|
||||||
|
|
||||||
if gitstatus_query MY && [[ $VCS_STATUS_RESULT == ok-sync ]]; then
|
|
||||||
RPROMPT=${${VCS_STATUS_LOCAL_BRANCH:-@${VCS_STATUS_COMMIT}}//\%/%%} # escape %
|
|
||||||
(( VCS_STATUS_NUM_STAGED )) && RPROMPT+='+'
|
|
||||||
(( VCS_STATUS_NUM_UNSTAGED )) && RPROMPT+='!'
|
|
||||||
(( VCS_STATUS_NUM_UNTRACKED )) && RPROMPT+='?'
|
|
||||||
fi
|
|
||||||
|
|
||||||
setopt no_prompt_{bang,subst} prompt_percent # enable/disable correct prompt expansions
|
|
||||||
}
|
|
||||||
|
|
||||||
gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
|
|
||||||
autoload -Uz add-zsh-hook
|
|
||||||
add-zsh-hook precmd my_set_prompt
|
|
||||||
```
|
|
||||||
|
|
||||||
This snippet is sourcing `gitstatus.plugin.zsh` rather than `gitstatus.prompt.zsh`. The former
|
|
||||||
defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
|
|
||||||
script that uses these bindings to assemble git prompt.
|
|
||||||
|
|
||||||
Unlike [Powerlevel10k](https://github.com/romkatv/powerlevel10k), code based on
|
|
||||||
[gitstatus.prompt.zsh](gitstatus.prompt.zsh) is communicating with gitstatusd synchronously. This
|
|
||||||
can make your prompt slow when working in a large git repository or on a slow machine. To avoid
|
|
||||||
this problem, call `gitstatus_query` asynchronously as documented in
|
|
||||||
[gitstatus.plugin.zsh](gitstatus.plugin.zsh). This can be quite challenging.
|
|
||||||
|
|
||||||
## Using from Bash
|
|
||||||
|
|
||||||
The easiest way to take advantage of gitstatus from Bash is via
|
|
||||||
[gitstatus.prompt.sh](gitstatus.prompt.sh). Install it as follows:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
|
|
||||||
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
Users in China can use the official mirror on gitee.com for faster download.<br>
|
|
||||||
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
|
|
||||||
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, if you have Homebrew installed:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
brew install romkatv/gitstatus/gitstatus
|
|
||||||
echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.sh" >> ~/.bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`
|
|
||||||
in all code snippets below.)
|
|
||||||
|
|
||||||
This will give you a basic yet functional prompt with git status in it. It's
|
|
||||||
[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt.
|
|
||||||
|
|
||||||
![Bash Prompt with GitStatus](
|
|
||||||
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/bash-prompt.png)
|
|
||||||
|
|
||||||
In order to customize your prompt, set `PS1` at the end of `~/.bashrc` after sourcing
|
|
||||||
`gitstatus.prompt.sh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source ~/gitstatus/gitstatus.prompt.sh
|
|
||||||
|
|
||||||
PS1='\w ${GITSTATUS_PROMPT}\n\$ ' # directory followed by git status and $/# (normal/root)
|
|
||||||
```
|
|
||||||
|
|
||||||
The expansion of `${GITSTATUS_PROMPT}` can contain the following bits:
|
|
||||||
|
|
||||||
| segment | meaning |
|
|
||||||
|-------------|-------------------------------------------------------|
|
|
||||||
| `master` | current branch |
|
|
||||||
| `#v1` | HEAD is tagged with `v1`; not shown when on a branch |
|
|
||||||
| `@5fc6fca4` | current commit; not shown when on a branch or tag |
|
|
||||||
| `⇣1` | local branch is behind the remote by 1 commit |
|
|
||||||
| `⇡2` | local branch is ahead of the remote by 2 commits |
|
|
||||||
| `⇠3` | local branch is behind the push remote by 3 commits |
|
|
||||||
| `⇢4` | local branch is ahead of the push remote by 4 commits |
|
|
||||||
| `*5` | there are 5 stashes |
|
|
||||||
| `merge` | merge is in progress (could be some other action) |
|
|
||||||
| `~6` | there are 6 merge conflicts |
|
|
||||||
| `+7` | there are 7 staged changes |
|
|
||||||
| `!8` | there are 8 unstaged changes |
|
|
||||||
| `?9` | there are 9 untracked files |
|
|
||||||
|
|
||||||
If you'd like to change the format of git status, or want to have greater control over the
|
|
||||||
process of assembling `PS1`, you can copy and modify parts of
|
|
||||||
[gitstatus.prompt.sh](gitstatus.prompt.sh) instead of sourcing the script. Your `~/.bashrc` might
|
|
||||||
look something like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source ~/gitstatus/gitstatus.plugin.sh
|
|
||||||
|
|
||||||
function my_set_prompt() {
|
|
||||||
PS1='\w'
|
|
||||||
|
|
||||||
if gitstatus_query && [[ "$VCS_STATUS_RESULT" == ok-sync ]]; then
|
|
||||||
if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
|
|
||||||
PS1+=" ${VCS_STATUS_LOCAL_BRANCH//\\/\\\\}" # escape backslash
|
|
||||||
else
|
|
||||||
PS1+=" @${VCS_STATUS_COMMIT//\\/\\\\}" # escape backslash
|
|
||||||
fi
|
|
||||||
(( VCS_STATUS_HAS_STAGED" )) && PS1+='+'
|
|
||||||
(( VCS_STATUS_HAS_UNSTAGED" )) && PS1+='!'
|
|
||||||
(( VCS_STATUS_HAS_UNTRACKED" )) && PS1+='?'
|
|
||||||
fi
|
|
||||||
|
|
||||||
PS1+='\n\$ '
|
|
||||||
|
|
||||||
shopt -u promptvars # disable expansion of '$(...)' and the like
|
|
||||||
}
|
|
||||||
|
|
||||||
gitstatus_stop && gitstatus_start
|
|
||||||
PROMPT_COMMAND=my_set_prompt
|
|
||||||
```
|
|
||||||
|
|
||||||
This snippet is sourcing `gitstatus.plugin.sh` rather than `gitstatus.prompt.sh`. The former
|
|
||||||
defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
|
|
||||||
script that uses these bindings to assemble git prompt.
|
|
||||||
|
|
||||||
Note: Bash bindings, unlike Zsh bindings, don't support asynchronous calls.
|
|
||||||
|
|
||||||
## Using from other shells
|
|
||||||
|
|
||||||
If there are no gitstatusd bindings for your shell, you'll need to get your hands dirty.
|
|
||||||
Use the existing bindings for inspiration; run `gitstatusd --help` or read the same thing in
|
|
||||||
[options.cc](src/options.cc).
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
gitstatusd reads requests from stdin and prints responses to stdout. Requests contain an ID and
|
|
||||||
a directory. Responses contain the same ID and machine-readable git status for the directory.
|
|
||||||
gitstatusd keeps some state in memory for the directories it has seen in order to serve future
|
|
||||||
requests faster.
|
|
||||||
|
|
||||||
[Zsh bindings](gitstatus.plugin.zsh) and [Bash bindings](gitstatus.plugin.sh) start gitstatusd in
|
|
||||||
the background and communicate with it via pipes. Themes such as
|
|
||||||
[Powerlevel10k](https://github.com/romkatv/powerlevel10k) use these bindings to put git status in
|
|
||||||
`PROMPT`.
|
|
||||||
|
|
||||||
Note that gitstatus cannot be used as a drop-in replacement for `git status` command as it doesn't
|
|
||||||
produce output in the same format. It does perform the same computation though.
|
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
The following benchmark results were obtained on Intel i9-7900X running Ubuntu 18.04 in
|
|
||||||
a clean [chromium](https://github.com/chromium/chromium) repository synced to `9394e49a`. The
|
|
||||||
repository was checked out to an ext4 filesystem on M.2 SSD.
|
|
||||||
|
|
||||||
Three functionally equivalent tools for computing git status were benchmarked:
|
|
||||||
|
|
||||||
* `gitstatusd`
|
|
||||||
* `git` with untracked cache enabled
|
|
||||||
* `lg2` -- a demo/example executable from [libgit2](https://github.com/romkatv/libgit2) that
|
|
||||||
implements a subset of `git` functionality on top of libgit2 API; for the purposes of this
|
|
||||||
benchmark the subset is sufficient to generate the same data as the other tools
|
|
||||||
|
|
||||||
Every tool was benchmark in cold and hot conditions. For `git` the first run in a repository was
|
|
||||||
considered cold, with the following runs considered hot. `lg2` was patched to compute results twice
|
|
||||||
in a single invocation without freeing the repository in between; the second run was considered hot.
|
|
||||||
The same patching was not done for `git` because `git` cannot be easily modified to refresh inmemory
|
|
||||||
index state between invocations; in fact, this limitation is one of the primary reasons developers
|
|
||||||
use libgit2. `gitstatusd` was benchmarked similarly to `lg2` with two result computations in the
|
|
||||||
same invocation.
|
|
||||||
|
|
||||||
Two commands were benchmarked: `status` and `describe`.
|
|
||||||
|
|
||||||
### Status
|
|
||||||
|
|
||||||
In this benchmark all tools were computing the equivalent of `git status`. Lower numbers are better.
|
|
||||||
|
|
||||||
| Tool | Cold | Hot |
|
|
||||||
|---------------|-----------:|------------:|
|
|
||||||
| **gitstatus** | **291 ms** | **30.9 ms** |
|
|
||||||
| git | 876 ms | 295 ms |
|
|
||||||
| lg2 | 1730 ms | 1310 ms |
|
|
||||||
|
|
||||||
gitstatusd is substantially faster than the alternatives, especially on hot runs. Note that hot runs
|
|
||||||
are of primary importance to the main use case of gitstatus in interactive shells.
|
|
||||||
|
|
||||||
The performance of `git status` fluctuated wildly in this benchmarks for reasons unknown to the
|
|
||||||
author. Moreover, performance is sticky -- once `git status` settles around a number, it stays
|
|
||||||
there for a long time. Numbers as diverse as 295, 352, 663 and 730 had been observed on hot runs on
|
|
||||||
the same repository. The number in the table is the lowest (fastest or best) that `git status` had
|
|
||||||
shown.
|
|
||||||
|
|
||||||
### Describe
|
|
||||||
|
|
||||||
In this benchmark all tools were computing the equivalent of `git describe --tags --exact-match`
|
|
||||||
to find tags that resolve to the same commit as `HEAD`. Lower numbers are better.
|
|
||||||
|
|
||||||
| Tool | Cold | Hot |
|
|
||||||
|---------------|------------:|--------------:|
|
|
||||||
| **gitstatus** | **4.04 ms** | **0.0345 ms** |
|
|
||||||
| git | 18.0 ms | 14.5 ms |
|
|
||||||
| lg2 | 185 ms | 45.2 ms |
|
|
||||||
|
|
||||||
gitstatusd is once again faster than the alternatives, more so on hot runs.
|
|
||||||
|
|
||||||
## Why fast
|
|
||||||
|
|
||||||
Since gitstatusd doesn't have to print all staged/unstaged/untracked files but only report
|
|
||||||
whether there are any, it can terminate repository scan early. It can also remember which files
|
|
||||||
were dirty on the previous run and check them first on the next run to avoid the scan entirely if
|
|
||||||
the files are still dirty. However, the benchmarks above were performed in a clean repository where
|
|
||||||
these shortcuts do not trigger. All benchmarked tools had to do the same work -- check the status
|
|
||||||
of every file in the index to see if it has changed, check every directory for newly created files,
|
|
||||||
etc. And yet, gitstatusd came ahead by a large margin. This section describes what it does that
|
|
||||||
makes it so fast.
|
|
||||||
|
|
||||||
Most of the following comparisons are done against libgit2 rather than git because of the author's
|
|
||||||
familiarity with the former but not the with latter. libgit2 has clean, well-documented APIs and an
|
|
||||||
elegant implementation, which makes it so much easier to work with and to analyze performance
|
|
||||||
bottlenecks.
|
|
||||||
|
|
||||||
### Summary for the impatient
|
|
||||||
|
|
||||||
Under the benchmark conditions described above, the equivalent of libgit2's
|
|
||||||
`git_diff_index_to_workdir` (the most expensive part of `status` command) is 46.3 times faster in
|
|
||||||
gitstatusd. The speedup comes from the following sources.
|
|
||||||
|
|
||||||
* gitstatusd uses more efficient data structures and algorithms and employs performance-conscious
|
|
||||||
coding style throughout the codebase. This reduces CPU time in userspace by 32x compared to libgit2.
|
|
||||||
* gitstatusd uses less expensive system calls and makes fewer of them. This reduces CPU time spent
|
|
||||||
in kernel by 1.9x.
|
|
||||||
* gitstatusd can utilize multiple cores to scan index and workdir in parallel with almost perfect
|
|
||||||
scaling. This reduces total run time by 12.4x while having virtually no effect on total CPU time.
|
|
||||||
|
|
||||||
### Problem statement
|
|
||||||
|
|
||||||
The most resource-intensive part of the `status` command is finding the difference between _index_
|
|
||||||
and _workdir_ (`git_diff_index_to_workdir` in libgit2). Index is a list of all files in the git
|
|
||||||
repository with their last modification times. This is an obvious simplification but it suffices for
|
|
||||||
this exposition. On disk, index is stored sorted by file path. Here's an example of git index:
|
|
||||||
|
|
||||||
| File | Last modification time |
|
|
||||||
|-------------|-----------------------:|
|
|
||||||
| Makefile | 2019-04-01T14:12:32Z |
|
|
||||||
| src/hello.c | 2019-04-01T14:12:00Z |
|
|
||||||
| src/hello.h | 2019-04-01T14:12:32Z |
|
|
||||||
|
|
||||||
This list needs to be compared to the list of files in the working directory. If any of the files
|
|
||||||
listed in the index are missing from the workdir or have different last modification time, they are
|
|
||||||
"unstaged" in gitstatusd parlance. If you run `git status`, they'll be shown as "changes not staged
|
|
||||||
for commit". Thus, any implementation of `status` command has to call `stat()` or one of its
|
|
||||||
variants on every file in the index.
|
|
||||||
|
|
||||||
In addition, all files in the working directory for which there is no entry in the index at all are
|
|
||||||
"untracked". `git status` will show them as "untracked files". Finding untracked files requires some
|
|
||||||
form of work directory traversal.
|
|
||||||
|
|
||||||
### Single-threaded scan
|
|
||||||
|
|
||||||
Let's see how `git_diff_index_to_workdir` from libgit2 accomplishes these tasks. Here's its CPU
|
|
||||||
profile from 200 hot runs over chromium repository.
|
|
||||||
|
|
||||||
![libgit2 CPU profile (hot)](
|
|
||||||
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-libgit2.png)
|
|
||||||
|
|
||||||
(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
|
|
||||||
rendered with [pprof](https://github.com/google/pprof)).
|
|
||||||
|
|
||||||
We can see `__GI__lxstat` taking a lot of time. This is the `stat()` call for every file in the
|
|
||||||
index. We can also identify `__opendir`, `__readdir` and `__GI___close_nocancel` -- glibc wrappers
|
|
||||||
for reading the contents of a directory. This is for finding untracked files. Out of the total 232
|
|
||||||
seconds, 111 seconds -- or 47.7% -- was spent on these calls. The rest is computation -- comparing
|
|
||||||
strings, sorting arrays, etc.
|
|
||||||
|
|
||||||
Now let's take a look at the CPU profile of gitstatusd on the same task.
|
|
||||||
|
|
||||||
![gitstatusd CPU profile (hot)](
|
|
||||||
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-hot.png)
|
|
||||||
|
|
||||||
The first impression is that this profile looks pruned. This isn't an artifact. The profile was
|
|
||||||
generated with the same tools and the same flags as the profile of libgit2.
|
|
||||||
|
|
||||||
Since both profiles were generated from the same workload, absolute numbers can be compared. We can
|
|
||||||
see that gitstatusd took 62 seconds in total compared to libgit2's 232 seconds. System calls at the
|
|
||||||
core of the algorithm are cleary visible. `__GI___fxstatat` is a flavor of `stat()`, and the other
|
|
||||||
three calls -- `__libc_openat64`, `__libc_close` and `__GI___fxstat` are responsible for opening
|
|
||||||
directories and finding untracked files. Notice that there is almost nothing else in the profile
|
|
||||||
apart from these calls. The rest of the code accounts for 3.77 seconds of CPU time -- 32 times less
|
|
||||||
than in libgit2.
|
|
||||||
|
|
||||||
So, one reason gitstatusd is fast is that it has efficient diffing code -- very little time is spent
|
|
||||||
outside of kernel. However, if we look closely, we can notice that system calls in gitstatusd are
|
|
||||||
_also_ faster than in libgit2. For example, libgit2 spent 72.07 seconds in `__GI__lxstat` while
|
|
||||||
gitstatusd spent only 48.82 seconds in `__GI___fxstatat`. There are two reasons for this difference.
|
|
||||||
First, libgit2 makes more `stat()` calls than is strictly required. It's not necessary to stat
|
|
||||||
directories because index only has files. There are 25k directories in chromium repository (and 300k
|
|
||||||
files) -- that's 25k `stat()` calls that could be avoided. The second reason is that libgit2 and
|
|
||||||
gitstatusd use different flavors of `stat()`. libgit2 uses `lstat()`, which takes a path to the file
|
|
||||||
as input. Its performance is linear in the number of subdirectories in the path because it needs to
|
|
||||||
perform a lookup for every one of them and to check permissions. gitstatusd uses `fstatat()`, which
|
|
||||||
takes a file descriptor to the parent directory and a name of the file. Just a single lookup, less
|
|
||||||
CPU time.
|
|
||||||
|
|
||||||
Similarly to `lstat()` vs `fstatat()`, it's faster to open files and directories with `openat()`
|
|
||||||
from the parent directory file descriptor than with regular `open()` that accepts full file path.
|
|
||||||
gitstatusd takes advantage of `openat()` to open directories as fast as possible. It opens about 90%
|
|
||||||
of the directories (this depends on the actual directory structure of the repository) from the
|
|
||||||
immediate parent -- the most efficient way -- and the remaining 10% it opens from the repository's
|
|
||||||
root directory. The reason it's done this way is to keep the maximum number of simultaneously open
|
|
||||||
file descriptors bounded. libgit2 can have O(repository depth) simultaneously open file descriptors,
|
|
||||||
which may be OK for a single-threaded application but can balloon to a large number when scans are
|
|
||||||
done by many threads simultaneously, like in gitstatusd.
|
|
||||||
|
|
||||||
There is no equivalent to `__opendir` or `__readdir` in the gitstatusd profile because it uses the
|
|
||||||
equivalent of [untracked cache](https://git-scm.com/docs/git-update-index#_untracked_cache) from
|
|
||||||
git. On the first scan of the workdir gitstatusd lists all files just like libgit2. But, unlike
|
|
||||||
libgit2, it remembers the last modification time of every directory along with the list of
|
|
||||||
untracked files under it. On the next scan, gitstatusd can skip listing files in directories whose
|
|
||||||
last modification time hasn't changed.
|
|
||||||
|
|
||||||
To summarize, here's what gitstatusd was doing when the CPU profile was captured:
|
|
||||||
|
|
||||||
1. `__libc_openat64`: Open every directory for which there are files in the index.
|
|
||||||
2. `__GI___fxstat`: Check last modification time of the directory. Since it's the same as on the
|
|
||||||
last scan, this directory has the same list of untracked files as before, which is empty (the
|
|
||||||
repository is clean).
|
|
||||||
3. `__GI___fxstatat`: Check last modification time for every file in the index that belongs to this
|
|
||||||
directory.
|
|
||||||
4. `__libc_close`: Close the file descriptor to the directory.
|
|
||||||
|
|
||||||
Here's how the very first scan of a repository looks like in gitstatusd:
|
|
||||||
|
|
||||||
![gitstatusd CPU profile (cold)](
|
|
||||||
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-cold.png)
|
|
||||||
|
|
||||||
(Some glibc functions are mislabel on this profile. `explicit_bzero` and `__nss_passwd_lookup` are
|
|
||||||
in reality `strcmp` and `memcmp`.)
|
|
||||||
|
|
||||||
This is a superset of the previous -- hot -- profile, with an extra `syscall` and string sorting for
|
|
||||||
directory listing. gitstatusd uses `getdents64` Linux system call directly, bypassing the glibc
|
|
||||||
wrapper that libgit2 uses. This is 23% faster. The details of this optimization can be found in a
|
|
||||||
[separate document](docs/listdir.md).
|
|
||||||
|
|
||||||
### Multithreading
|
|
||||||
|
|
||||||
The diffing algorithm in gitstatusd was designed from the ground up with the intention of using it
|
|
||||||
concurrently from multiple threads. With a fast SSD, `status` is CPU bound, so taking advantage of
|
|
||||||
all available CPU cores is an obvious way to yield results faster.
|
|
||||||
|
|
||||||
gitstatusd exhibits almost perfect scaling from multithreading. Engaging all cores allows it to
|
|
||||||
produce results 12.4 times faster than in single-threaded execution. This is on Intel i9-7900X with
|
|
||||||
10 cores (20 with hyperthreading) with single-core frequency of 4.3GHz and all-core frequency of
|
|
||||||
4.0GHz.
|
|
||||||
|
|
||||||
Note: `git status` also uses all available cores in some parts of its algorithm while `lg2` does
|
|
||||||
everything in a single thread.
|
|
||||||
|
|
||||||
### Postprocessing
|
|
||||||
|
|
||||||
Once the difference between the index and the workdir is found, we have a list of _candidates_ --
|
|
||||||
files that may be unstaged or untracked. To make the final judgement, these files need to be checked
|
|
||||||
against `.gitignore` rules and a few other things.
|
|
||||||
|
|
||||||
gitstatusd uses [patched libgit2](https://github.com/romkatv/libgit2) for this step. This fork
|
|
||||||
adds several optimizations that make libgit2 faster. The patched libgit2 performs more than twice
|
|
||||||
as fast in the benchmark as the original even without changes in the user code (that is, in the
|
|
||||||
code that uses the libgit2 APIs). The fork also adds several API extensions, most notable of which
|
|
||||||
is the support for multi-threaded scans. If `lg2 status` is modified to take advantage of these
|
|
||||||
extensions, it outperforms the original libgit2 by a factor of 18. Lastly, the fork fixes a score of
|
|
||||||
bugs, most of which become apparent only when using libgit2 from multiple threads.
|
|
||||||
|
|
||||||
_WARNING: Changes to libgit2 are extensive but the testing they underwent isn't. It is
|
|
||||||
**not recommended** to use the patched libgit2 in production._
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
* To compile: binutils, cmake, gcc, g++, git and GNU make.
|
|
||||||
* To run: Linux, macOS, FreeBSD, Android, WSL, Cygwin or MSYS2.
|
|
||||||
|
|
||||||
## Compiling
|
|
||||||
|
|
||||||
There are prebuilt `gitstatusd` binaries in [releases](
|
|
||||||
https://github.com/romkatv/gitstatus/releases). When using the official shell bindings
|
|
||||||
provided by gitstatus, the right binary for your architecture gets downloaded automatically.
|
|
||||||
|
|
||||||
If prebuilt binaries don't work for you, you'll need to get your hands dirty.
|
|
||||||
|
|
||||||
### Compiling for personal use
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
git clone --depth=1 https://github.com/romkatv/gitstatus.git
|
|
||||||
cd gitstatus
|
|
||||||
./build -w -s -d docker
|
|
||||||
```
|
|
||||||
|
|
||||||
Users in China can use the official mirror on gitee.com for faster download.<br>
|
|
||||||
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
git clone --depth=1 https://gitee.com/romkatv/gitstatus.git
|
|
||||||
cd gitstatus
|
|
||||||
./build -w -s -d docker
|
|
||||||
```
|
|
||||||
|
|
||||||
- If it says that `-d docker` is not supported on your OS, remove this flag.
|
|
||||||
- If it says that `-s` is not supported on your OS, remove this flag.
|
|
||||||
- If it tell you to install docker but you cannot or don't want to, remove `-d docker`.
|
|
||||||
- If it says that some command is missing, install it.
|
|
||||||
|
|
||||||
If everything goes well, the newly built binary will appear in `./usrbin`. It'll be picked up
|
|
||||||
by shell bindings automatically.
|
|
||||||
|
|
||||||
When you update shell bindings, they may refuse to work with the binary you've built earlier. In
|
|
||||||
this case you'll need to rebuild.
|
|
||||||
|
|
||||||
If you are using gitstatus through [Powerlevel10k](https://github.com/romkatv/powerlevel10k), the
|
|
||||||
instructions are the same except that you don't need to clone gitstatus. Instead, change your
|
|
||||||
current directory to `/path/to/powerlevel10k/gitstatus` (`/path/to/powerlevel10k` is the directory
|
|
||||||
where you've installed Powerlevel10k) and run `./build -w -s -d docker` from there as described
|
|
||||||
above.
|
|
||||||
|
|
||||||
### Compiling for distribution
|
|
||||||
|
|
||||||
It's currently neither easy nor recommended to package and distribute gitstatus. There are no
|
|
||||||
instructions you can follow that would allow you to easily update your package when new versions of
|
|
||||||
gitstatus are released. This may change in the future but not soon.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
GNU General Public License v3.0. See [LICENSE](LICENSE). Contributions are covered by the same
|
|
||||||
license.
|
|
|
@ -1,656 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# Type `build -h` for help and see https://github.com/romkatv/gitstatus
|
|
||||||
# for full documentation.
|
|
||||||
|
|
||||||
set -ue
|
|
||||||
|
|
||||||
if [ -n "${ZSH_VERSION:-}" ]; then
|
|
||||||
emulate sh -o err_exit -o no_unset
|
|
||||||
fi
|
|
||||||
|
|
||||||
export LC_ALL=C
|
|
||||||
|
|
||||||
if [ -z "${ZSH_VERSION-}" ] && command -v zsh >/dev/null 2>&1; then
|
|
||||||
# Avoid bash 3.*.
|
|
||||||
case "${BASH_VERSION-}" in
|
|
||||||
[0-3].*) exec zsh "$0" "$@";;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Avoid ksh: https://github.com/romkatv/gitstatus/issues/282.
|
|
||||||
if [ -n "${KSH_VERSION-}" ]; then
|
|
||||||
if [ -z "${ZSH_VERSION-}" ] && command -v zsh >/dev/null 2>&1; then
|
|
||||||
exec zsh "$0" "$@"
|
|
||||||
elif [ -z "${BASH_VERSION-}" ] && command -v bash >/dev/null 2>&1 &&
|
|
||||||
bash_version="$(bash --version 2>&1)"; then
|
|
||||||
case "$bash_version" in
|
|
||||||
*version\ [4-9]*|*version\ [1-9][0-9]*) exec bash "$0" "$@";;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
usage="$(command cat <<\END
|
|
||||||
Usage: build [-m ARCH] [-c CPU] [-d CMD] [-i IMAGE] [-s] [-w]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-m ARCH `uname -m` from the target machine; defaults to `uname -m`
|
|
||||||
from the local machine
|
|
||||||
-c CPU generate machine instructions for CPU of this type; this
|
|
||||||
value gets passed as `-march` (or `-mcpu` for ppc64le) to gcc;
|
|
||||||
inferred from ARCH if not set explicitly
|
|
||||||
-d CMD build in a Docker container and use CMD as the `docker`
|
|
||||||
command; e.g., `-d docker` or `-d podman`
|
|
||||||
-i IMAGE build in this Docker image; inferred from ARCH if not set
|
|
||||||
explicitly
|
|
||||||
-s install whatever software is necessary for build to
|
|
||||||
succeed; on some operating systems this option is not
|
|
||||||
supported; on others it can have partial effect
|
|
||||||
-w automatically download tarballs for dependencies if they
|
|
||||||
do not already exist in ./deps; dependencies are described
|
|
||||||
in ./build.info
|
|
||||||
END
|
|
||||||
)"
|
|
||||||
|
|
||||||
build="$(command cat <<\END
|
|
||||||
outdir="$(command pwd)"
|
|
||||||
|
|
||||||
if command -v mktemp >/dev/null 2>&1; then
|
|
||||||
workdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-build.XXXXXXXXXX)"
|
|
||||||
else
|
|
||||||
workdir="${TMPDIR:-/tmp}/gitstatus-build.tmp.$$"
|
|
||||||
command mkdir -- "$workdir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd -- "$workdir"
|
|
||||||
workdir="$(command pwd)"
|
|
||||||
|
|
||||||
narg() { echo $#; }
|
|
||||||
|
|
||||||
if [ "$(narg $workdir)" != 1 -o -z "${workdir##*:*}" -o -z "${workdir##*=*}" ]; then
|
|
||||||
>&2 echo "[error] cannot build in this directory: $workdir"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
appname=gitstatusd
|
|
||||||
libgit2_tmp="$outdir"/deps/"$appname".libgit2.tmp
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
trap - INT QUIT TERM ILL PIPE
|
|
||||||
cd /
|
|
||||||
if ! command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"; then
|
|
||||||
command sleep 5
|
|
||||||
command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
trap cleanup INT QUIT TERM ILL PIPE
|
|
||||||
|
|
||||||
if [ -n "$gitstatus_install_tools" ]; then
|
|
||||||
case "$gitstatus_kernel" in
|
|
||||||
linux)
|
|
||||||
if command -v apk >/dev/null 2>&1; then
|
|
||||||
command apk update
|
|
||||||
command apk add binutils cmake gcc g++ git make musl-dev perl-utils
|
|
||||||
elif command -v apt-get >/dev/null 2>&1; then
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y binutils cmake gcc g++ make wget
|
|
||||||
else
|
|
||||||
>&2 echo "[error] -s is not supported on this system"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
freebsd|dragonfly)
|
|
||||||
command pkg install -y cmake gmake binutils git perl5 wget
|
|
||||||
;;
|
|
||||||
openbsd)
|
|
||||||
command pkg_add cmake gmake gcc g++ git wget
|
|
||||||
;;
|
|
||||||
netbsd)
|
|
||||||
command pkgin -y install cmake gmake binutils git
|
|
||||||
;;
|
|
||||||
darwin)
|
|
||||||
if ! command -v make >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1; then
|
|
||||||
>&2 echo "[error] please run 'xcode-select --install' and retry"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if command -v port >/dev/null 2>&1; then
|
|
||||||
sudo port -N install libiconv cmake wget
|
|
||||||
elif command -v brew >/dev/null 2>&1; then
|
|
||||||
for formula in libiconv cmake git wget; do
|
|
||||||
if command brew ls --version "$formula" &>/dev/null; then
|
|
||||||
command brew upgrade "$formula"
|
|
||||||
else
|
|
||||||
command brew install "$formula"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
>&2 echo "[error] please install MacPorts or Homebrew and retry"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
msys*|mingw*)
|
|
||||||
command pacman -Syu --noconfirm
|
|
||||||
command pacman -S --needed --noconfirm binutils cmake gcc git make perl
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
>&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
cpus="$(command getconf _NPROCESSORS_ONLN 2>/dev/null)" ||
|
|
||||||
cpus="$(command sysctl -n hw.ncpu 2>/dev/null)" ||
|
|
||||||
cpus=8
|
|
||||||
|
|
||||||
case "$gitstatus_cpu" in
|
|
||||||
powerpc64|powerpc64le)
|
|
||||||
archflag="-mcpu"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
archflag="-march"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cflags="$archflag=$gitstatus_cpu -fno-plt -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fpie"
|
|
||||||
ldflags=
|
|
||||||
static_pie=
|
|
||||||
|
|
||||||
if [ -z "${CC-}" ]; then
|
|
||||||
case "$gitstatus_kernel" in
|
|
||||||
freebsd) export CC=clang;;
|
|
||||||
*) export CC=cc;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'int main() {}\n' >"$workdir"/cc-test.c
|
|
||||||
if 2>/dev/null "$CC" \
|
|
||||||
-ffile-prefix-map=x=y \
|
|
||||||
-Werror \
|
|
||||||
-c "$workdir"/cc-test.c \
|
|
||||||
-o "$workdir"/cc-test.o; then
|
|
||||||
cflags="$cflags -ffile-prefix-map=$workdir/="
|
|
||||||
fi
|
|
||||||
|
|
||||||
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
|
|
||||||
if 2>/dev/null "$CC" \
|
|
||||||
-fstack-clash-protection \
|
|
||||||
-Werror \
|
|
||||||
-c "$workdir"/cc-test.c \
|
|
||||||
-o "$workdir"/cc-test.o; then
|
|
||||||
cflags="$cflags -fstack-clash-protection"
|
|
||||||
fi
|
|
||||||
|
|
||||||
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
|
|
||||||
if 2>/dev/null "$CC" \
|
|
||||||
-fcf-protection \
|
|
||||||
-Werror \
|
|
||||||
-c "$workdir"/cc-test.c \
|
|
||||||
-o "$workdir"/cc-test.o; then
|
|
||||||
cflags="$cflags -fcf-protection"
|
|
||||||
fi
|
|
||||||
|
|
||||||
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
|
|
||||||
if 2>/dev/null "$CC" \
|
|
||||||
-Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now \
|
|
||||||
-Werror \
|
|
||||||
"$workdir"/cc-test.c \
|
|
||||||
-o "$workdir"/cc-test; then
|
|
||||||
ldflags="$ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
|
|
||||||
fi
|
|
||||||
|
|
||||||
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
|
|
||||||
if 2>/dev/null "$CC" \
|
|
||||||
-fpie -static-pie \
|
|
||||||
-Werror \
|
|
||||||
"$workdir"/cc-test.c \
|
|
||||||
-o "$workdir"/cc-test; then
|
|
||||||
static_pie='-static-pie'
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$gitstatus_cpu" = x86-64 ]; then
|
|
||||||
cflags="$cflags -mtune=generic"
|
|
||||||
fi
|
|
||||||
|
|
||||||
libgit2_cmake_flags=
|
|
||||||
libgit2_cflags="${CFLAGS-} $cflags -O3 -DNDEBUG"
|
|
||||||
|
|
||||||
gitstatus_cxx=g++
|
|
||||||
gitstatus_cxxflags="${CXXFLAGS-} $cflags -I${workdir}/libgit2/include -DGITSTATUS_ZERO_NSEC -D_GNU_SOURCE -D_GLIBCXX_ASSERTIONS"
|
|
||||||
gitstatus_ldflags="${LDFLAGS-} $ldflags -L${workdir}/libgit2/build"
|
|
||||||
gitstatus_ldlibs=
|
|
||||||
gitstatus_make=make
|
|
||||||
|
|
||||||
case "$gitstatus_kernel" in
|
|
||||||
linux)
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
freebsd)
|
|
||||||
gitstatus_cxx=clang++
|
|
||||||
gitstatus_make=gmake
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
dragonfly)
|
|
||||||
gitstatus_cxx=clang++12
|
|
||||||
gitstatus_make=gmake
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
openbsd)
|
|
||||||
gitstatus_cxx=eg++
|
|
||||||
gitstatus_make=gmake
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
netbsd)
|
|
||||||
gitstatus_make=gmake
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
darwin)
|
|
||||||
command mkdir -- "$workdir"/lib
|
|
||||||
if [ -e /opt/local/lib/libiconv.a ]; then
|
|
||||||
command ln -s -- /opt/local/lib/libiconv.a "$workdir"/lib
|
|
||||||
libgit2_cflags="$libgit2_cflags -I/opt/local/include"
|
|
||||||
gitstatus_cxxflags="$gitstatus_cxxflags -I/opt/local/include"
|
|
||||||
else
|
|
||||||
brew_prefix="$(command brew --prefix)"
|
|
||||||
command ln -s -- "$brew_prefix"/opt/libiconv/lib/libiconv.a "$workdir"/lib
|
|
||||||
libgit2_cflags="$libgit2_cflags -I"$brew_prefix"/opt/libiconv/include"
|
|
||||||
gitstatus_cxxflags="$gitstatus_cxxflags -I"$brew_prefix"/opt/libiconv/include"
|
|
||||||
fi
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DUSE_ICONV=ON"
|
|
||||||
gitstatus_ldlibs="$gitstatus_ldlibs -liconv"
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags -L${workdir}/lib"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=OFF"
|
|
||||||
;;
|
|
||||||
msys*|mingw*)
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
cygwin*)
|
|
||||||
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
|
|
||||||
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
>&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
for cmd in cat cmake git ld ln mkdir rm strip tar "$gitstatus_make"; do
|
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
if [ -n "$gitstatus_install_tools" ]; then
|
|
||||||
>&2 echo "[internal error] $cmd not found"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
>&2 echo "[error] command not found: $cmd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
. "$outdir"/build.info
|
|
||||||
if [ -z "${libgit2_version:-}" ]; then
|
|
||||||
>&2 echo "[internal error] libgit2_version not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "${libgit2_sha256:-}" ]; then
|
|
||||||
>&2 echo "[internal error] libgit2_sha256 not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
libgit2_tarball="$outdir"/deps/libgit2-"$libgit2_version".tar.gz
|
|
||||||
if [ ! -e "$libgit2_tarball" ]; then
|
|
||||||
if [ -n "$gitstatus_download_deps" ]; then
|
|
||||||
if ! command -v wget >/dev/null 2>&1; then
|
|
||||||
if [ -n "$gitstatus_install_tools" ]; then
|
|
||||||
>&2 echo "[internal error] wget not found"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
>&2 echo "[error] command not found: wget"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
libgit2_url=https://github.com/romkatv/libgit2/archive/"$libgit2_version".tar.gz
|
|
||||||
if ! >"$libgit2_tmp" command wget --no-config -qO- -- "$libgit2_url" &&
|
|
||||||
! >"$libgit2_tmp" command wget -qO- -- "$libgit2_url"; then
|
|
||||||
set -x
|
|
||||||
>&2 command which wget
|
|
||||||
>&2 command ls -lAd -- "$(command which wget)"
|
|
||||||
>&2 command ls -lAd -- "$outdir"
|
|
||||||
>&2 command ls -lA -- "$outdir"
|
|
||||||
>&2 command ls -lAd -- "$outdir"/deps
|
|
||||||
>&2 command ls -lA -- "$outdir"/deps
|
|
||||||
set +x
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
command mv -f -- "$libgit2_tmp" "$libgit2_tarball"
|
|
||||||
else
|
|
||||||
>&2 echo "[error] file not found: deps/libgit2-"$libgit2_version".tar.gz"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
libgit2_actual_sha256=
|
|
||||||
if command -v shasum >/dev/null 2>/dev/null; then
|
|
||||||
libgit2_actual_sha256="$(command shasum -b -a 256 -- "$libgit2_tarball")"
|
|
||||||
libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
|
|
||||||
elif command -v sha256sum >/dev/null 2>/dev/null; then
|
|
||||||
libgit2_actual_sha256="$(command sha256sum -b -- "$libgit2_tarball")"
|
|
||||||
libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
|
|
||||||
elif command -v sha256 >/dev/null 2>/dev/null; then
|
|
||||||
libgit2_actual_sha256="$(command sha256 -- "$libgit2_tarball" </dev/null)"
|
|
||||||
# Ignore sha256 output if it's from hashalot. It's incompatible.
|
|
||||||
if [ ${#libgit2_actual_sha256} -lt 64 ]; then
|
|
||||||
libgit2_actual_sha256=
|
|
||||||
else
|
|
||||||
libgit2_actual_sha256="${libgit2_actual_sha256##* }"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$libgit2_actual_sha256" ]; then
|
|
||||||
>&2 echo "[error] command not found: shasum or sha256sum"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$libgit2_actual_sha256" != "$libgit2_sha256" ]; then
|
|
||||||
>&2 echo "[error] sha256 mismatch"
|
|
||||||
>&2 echo ""
|
|
||||||
>&2 echo " file : deps/libgit2-$libgit2_version.tar.gz"
|
|
||||||
>&2 echo " expected: $libgit2_sha256"
|
|
||||||
>&2 echo " actual : $libgit2_actual_sha256"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd -- "$workdir"
|
|
||||||
command tar -xzf "$libgit2_tarball"
|
|
||||||
command mv -- libgit2-"$libgit2_version" libgit2
|
|
||||||
command mkdir libgit2/build
|
|
||||||
cd libgit2/build
|
|
||||||
|
|
||||||
CFLAGS="$libgit2_cflags" command cmake \
|
|
||||||
-DCMAKE_BUILD_TYPE=None \
|
|
||||||
-DZERO_NSEC=ON \
|
|
||||||
-DTHREADSAFE=ON \
|
|
||||||
-DUSE_BUNDLED_ZLIB=ON \
|
|
||||||
-DREGEX_BACKEND=builtin \
|
|
||||||
-DUSE_HTTP_PARSER=builtin \
|
|
||||||
-DUSE_SSH=OFF \
|
|
||||||
-DUSE_HTTPS=OFF \
|
|
||||||
-DBUILD_CLAR=OFF \
|
|
||||||
-DUSE_GSSAPI=OFF \
|
|
||||||
-DUSE_NTLMCLIENT=OFF \
|
|
||||||
-DBUILD_SHARED_LIBS=OFF \
|
|
||||||
$libgit2_cmake_flags \
|
|
||||||
..
|
|
||||||
command make -j "$cpus" VERBOSE=1
|
|
||||||
|
|
||||||
APPNAME="$appname".tmp \
|
|
||||||
OBJDIR="$workdir"/gitstatus \
|
|
||||||
CXX="${CXX:-$gitstatus_cxx}" \
|
|
||||||
CXXFLAGS="$gitstatus_cxxflags" \
|
|
||||||
LDFLAGS="$gitstatus_ldflags" \
|
|
||||||
LDLIBS="$gitstatus_ldlibs" \
|
|
||||||
command "$gitstatus_make" -C "$outdir" -j "$cpus"
|
|
||||||
|
|
||||||
app="$outdir"/usrbin/"$appname"
|
|
||||||
|
|
||||||
command strip "$app".tmp
|
|
||||||
|
|
||||||
command mkdir -- "$workdir"/repo
|
|
||||||
printf '[init]\n defaultBranch = master\n' >"$workdir"/.gitconfig
|
|
||||||
(
|
|
||||||
cd -- "$workdir"/repo
|
|
||||||
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git init
|
|
||||||
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.name "Your Name"
|
|
||||||
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.email "you@example.com"
|
|
||||||
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git commit \
|
|
||||||
--allow-empty --allow-empty-message --no-gpg-sign -m ''
|
|
||||||
)
|
|
||||||
|
|
||||||
resp="$(printf "hello\037$workdir/repo\036" | "$app".tmp)"
|
|
||||||
case "$resp" in
|
|
||||||
hello*1*/repo*master*);;
|
|
||||||
*)
|
|
||||||
>&2 echo 'error: invalid gitstatusd response for a git repo'
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
resp="$(printf 'hello\037\036' | "$app".tmp)"
|
|
||||||
case "$resp" in
|
|
||||||
hello*0*);;
|
|
||||||
*)
|
|
||||||
>&2 echo 'error: invalid gitstatusd response for a non-repo'
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
command mv -f -- "$app".tmp "$app"
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
command cat >&2 <<-END
|
|
||||||
-------------------------------------------------
|
|
||||||
SUCCESS: created usrbin/$appname
|
|
||||||
END
|
|
||||||
END
|
|
||||||
)"
|
|
||||||
|
|
||||||
docker_image=
|
|
||||||
docker_cmd=
|
|
||||||
|
|
||||||
gitstatus_arch=
|
|
||||||
gitstatus_cpu=
|
|
||||||
gitstatus_install_tools=
|
|
||||||
gitstatus_download_deps=
|
|
||||||
|
|
||||||
while getopts ':m:c:i:d:swh' opt "$@"; do
|
|
||||||
case "$opt" in
|
|
||||||
h)
|
|
||||||
printf '%s\n' "$usage"
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
m)
|
|
||||||
if [ -n "$gitstatus_arch" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gitstatus_arch="$OPTARG"
|
|
||||||
;;
|
|
||||||
c)
|
|
||||||
if [ -n "$gitstatus_cpu" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gitstatus_cpu="$OPTARG"
|
|
||||||
;;
|
|
||||||
i)
|
|
||||||
if [ -n "$docker_image" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
docker_image="$OPTARG"
|
|
||||||
;;
|
|
||||||
d)
|
|
||||||
if [ -n "$docker_cmd" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
docker_cmd="$OPTARG"
|
|
||||||
;;
|
|
||||||
s)
|
|
||||||
if [ -n "$gitstatus_install_tools" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gitstatus_install_tools=1
|
|
||||||
;;
|
|
||||||
w)
|
|
||||||
if [ -n "$gitstatus_download_deps" ]; then
|
|
||||||
>&2 echo "[error] duplicate option: -$opt"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gitstatus_download_deps=1
|
|
||||||
;;
|
|
||||||
\?) >&2 echo "[error] invalid option: -$OPTARG" ; exit 1;;
|
|
||||||
:) >&2 echo "[error] missing required argument: -$OPTARG"; exit 1;;
|
|
||||||
*) >&2 echo "[internal error] unhandled option: -$opt" ; exit 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$OPTIND" -le $# ]; then
|
|
||||||
>&2 echo "[error] unexpected positional argument"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$docker_image" -a -z "$docker_cmd" ]; then
|
|
||||||
>&2 echo "[error] cannot use -i without -d"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$gitstatus_arch" ]; then
|
|
||||||
gitstatus_arch="$(uname -m)"
|
|
||||||
gitstatus_arch="$(printf '%s' "$gitstatus_arch" | tr '[A-Z]' '[a-z]')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$gitstatus_cpu" ]; then
|
|
||||||
case "$gitstatus_arch" in
|
|
||||||
armel) gitstatus_cpu=armv5;;
|
|
||||||
armv6l|armhf) gitstatus_cpu=armv6;;
|
|
||||||
armv7l) gitstatus_cpu=armv7;;
|
|
||||||
arm64|aarch64) gitstatus_cpu=armv8-a;;
|
|
||||||
ppc64|ppc64le) gitstatus_cpu=powerpc64le;;
|
|
||||||
riscv64) gitstatus_cpu=rv64imafdc;;
|
|
||||||
loongarch64) gitstatus_cpu=loongarch64;;
|
|
||||||
x86_64|amd64) gitstatus_cpu=x86-64;;
|
|
||||||
x86) gitstatus_cpu=i586;;
|
|
||||||
s390x) gitstatus_cpu=z900;;
|
|
||||||
i386|i586|i686) gitstatus_cpu="$gitstatus_arch";;
|
|
||||||
*)
|
|
||||||
>&2 echo '[error] unable to infer target CPU architecture'
|
|
||||||
>&2 echo 'Please specify explicitly with `-c CPU`.'
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
gitstatus_kernel="$(uname -s)"
|
|
||||||
gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | tr '[A-Z]' '[a-z]')"
|
|
||||||
|
|
||||||
case "$gitstatus_kernel" in
|
|
||||||
linux)
|
|
||||||
if [ -n "$docker_cmd" ]; then
|
|
||||||
if [ -z "${docker_cmd##*/*}" ]; then
|
|
||||||
if [ ! -x "$docker_cmd" ]; then
|
|
||||||
>&2 echo "[error] not an executable file: $docker_cmd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if ! command -v "$docker_cmd" >/dev/null 2>&1; then
|
|
||||||
>&2 echo "[error] command not found: $docker_cmd"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ -z "$docker_image" ]; then
|
|
||||||
case "$gitstatus_arch" in
|
|
||||||
x86_64) docker_image=alpine:3.11.6;;
|
|
||||||
x86|i386|i586|i686) docker_image=i386/alpine:3.11.6;;
|
|
||||||
armv6l|armhf) docker_image=arm32v6/alpine:3.11.6;;
|
|
||||||
armv7l) docker_image=arm32v7/alpine:3.11.6;;
|
|
||||||
aarch64) docker_image=arm64v8/alpine:3.11.6;;
|
|
||||||
ppc64|ppc64le) docker_image=ppc64le/alpine:3.11.6;;
|
|
||||||
s390x) docker_image=s390x/alpine:3.11.6;;
|
|
||||||
*)
|
|
||||||
>&2 echo '[error] unable to infer docker image'
|
|
||||||
>&2 echo 'Please specify explicitly with `-i IMAGE`.'
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
freebsd|openbsd|netbsd|darwin|dragonfly)
|
|
||||||
if [ -n "$docker_cmd" ]; then
|
|
||||||
>&2 echo "[error] docker (-d) is not supported on $gitstatus_kernel"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
msys_nt-*|mingw32_nt-*|mingw64_nt-*|cygwin_nt-*)
|
|
||||||
if ! printf '%s' "$gitstatus_kernel" | grep -Eqx '[^-]+-[0-9]+\.[0-9]+(-.*)?'; then
|
|
||||||
>&2 echo '[error] unsupported kernel, sorry!'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | sed 's/^\([^-]*-[0-9]*\.[0-9]*\).*/\1/')"
|
|
||||||
if [ -n "$docker_cmd" ]; then
|
|
||||||
>&2 echo '[error] docker (-d) is not supported on windows'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -n "$gitstatus_install_tools" -a -z "${gitstatus_kernel##cygwin_nt-*}" ]; then
|
|
||||||
>&2 echo '[error] -s is not supported on cygwin'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
>&2 echo '[error] unsupported kernel, sorry!'
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
dir="$(dirname -- "$0")"
|
|
||||||
cd -- "$dir"
|
|
||||||
dir="$(pwd)"
|
|
||||||
|
|
||||||
>&2 echo "Building gitstatusd..."
|
|
||||||
>&2 echo ""
|
|
||||||
>&2 echo " kernel := $gitstatus_kernel"
|
|
||||||
>&2 echo " arch := $gitstatus_arch"
|
|
||||||
>&2 echo " cpu := $gitstatus_cpu"
|
|
||||||
[ -z "$docker_cmd" ] || >&2 echo " docker command := $docker_cmd"
|
|
||||||
[ -z "$docker_image" ] || >&2 echo " docker image := $docker_image"
|
|
||||||
if [ -n "$gitstatus_install_tools" ]; then
|
|
||||||
>&2 echo " install tools := yes"
|
|
||||||
else
|
|
||||||
>&2 echo " install tools := no"
|
|
||||||
fi
|
|
||||||
if [ -n "$gitstatus_download_deps" ]; then
|
|
||||||
>&2 echo " download deps := yes"
|
|
||||||
else
|
|
||||||
>&2 echo " download deps := no"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$docker_cmd" ]; then
|
|
||||||
"$docker_cmd" run \
|
|
||||||
-e docker_cmd="$docker_cmd" \
|
|
||||||
-e docker_image="$docker_image" \
|
|
||||||
-e gitstatus_kernel="$gitstatus_kernel" \
|
|
||||||
-e gitstatus_arch="$gitstatus_arch" \
|
|
||||||
-e gitstatus_cpu="$gitstatus_cpu" \
|
|
||||||
-e gitstatus_install_tools="$gitstatus_install_tools" \
|
|
||||||
-e gitstatus_download_deps="$gitstatus_download_deps" \
|
|
||||||
-v "$dir":/out \
|
|
||||||
-w /out \
|
|
||||||
--rm \
|
|
||||||
-- "$docker_image" /bin/sh -uexc "$build"
|
|
||||||
else
|
|
||||||
eval "$build"
|
|
||||||
fi
|
|
|
@ -1,22 +0,0 @@
|
||||||
# This value gets embedded in gitstatusd at build time. It is
|
|
||||||
# read by ./Makefile. `gitstatusd --version` reports it back.
|
|
||||||
#
|
|
||||||
# This value is also read by shell bindings (indirectly, through
|
|
||||||
# ./install) when using GITSTATUS_DAEMON or usrbin/gitstatusd.
|
|
||||||
gitstatus_version="v1.5.4"
|
|
||||||
|
|
||||||
# libgit2 is a build time dependency of gitstatusd. The values of
|
|
||||||
# libgit2_version and libgit2_sha256 are read by ./build.
|
|
||||||
#
|
|
||||||
# If ./deps/libgit2-${libgit2_version}.tar.gz doesn't exist, build
|
|
||||||
# downloads it from the following location:
|
|
||||||
#
|
|
||||||
# https://github.com/romkatv/libgit2/archive/${libgit2_version}.tar.gz
|
|
||||||
#
|
|
||||||
# Once downloaded, the tarball is stored at the path indicated
|
|
||||||
# above so that repeated builds don't consume network bandwidth.
|
|
||||||
#
|
|
||||||
# If sha256 of ./deps/libgit2-${libgit2_version}.tar.gz doesn't match,
|
|
||||||
# build gets aborted.
|
|
||||||
libgit2_version="tag-0ad3d776aa86dd607dc86dcd7f77ad3ed7ebec61"
|
|
||||||
libgit2_sha256="c5d0117ae74d3ef244c26f10cce022019077dbc4563e6251fa9f56d36868ce74"
|
|
|
@ -1,330 +0,0 @@
|
||||||
# Fast directory listing
|
|
||||||
|
|
||||||
In order to find untracked files in a git repository, [gitstatusd](../README.md) needs to list the
|
|
||||||
contents of every directory. gitstatusd does it 27% faster than a reasonable implementation that a
|
|
||||||
seasoned C/C++ practitioner might write. This document explains the optimizations that went into it.
|
|
||||||
As directory listing is a common operation, many other projects can benefit from applying these
|
|
||||||
optimizations.
|
|
||||||
|
|
||||||
## v1
|
|
||||||
|
|
||||||
Given a path to a directory, `ListDir()` must produce the list of files in that directory. Moreover,
|
|
||||||
the list must be sorted lexicographically to enable fast comparison with Git index.
|
|
||||||
|
|
||||||
The following C++ implementation gets the job done. For simplicity, it returns an empty list on
|
|
||||||
error.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
vector<string> ListDir(const char* dirname) {
|
|
||||||
vector<string> entries;
|
|
||||||
if (DIR* dir = opendir(dirname)) {
|
|
||||||
while (struct dirent* ent = (errno = 0, readdir(dir))) {
|
|
||||||
if (!Dots(ent->d_name)) entries.push_back(ent->d_name);
|
|
||||||
}
|
|
||||||
if (errno) entries.clear();
|
|
||||||
sort(entries.begin(), entries.end());
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Every directory has entries `"."` and `".."`, which we aren't interested in. We filter them out with
|
|
||||||
a helper function `Dots()`.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
bool Dots(const char* s) { return s[0] == '.' && (!s[1] || (s[1] == '.' && !s[2])); }
|
|
||||||
```
|
|
||||||
|
|
||||||
To check how fast `ListDir()` performs, we can run it many times on a typical directory. One million
|
|
||||||
runs on a directory with 32 files with 16-character names takes 12.7 seconds.
|
|
||||||
|
|
||||||
## v2
|
|
||||||
|
|
||||||
Experienced C++ practitioners will scoff at our implementation of `ListDir()`. If it's meant to be
|
|
||||||
efficient, returning `vector<string>` is an unaffordable convenience. To avoid heap allocations we
|
|
||||||
can use a simple arena that will allow us to reuse memory between different `ListDir()` calls.
|
|
||||||
|
|
||||||
(Changed and added lines are marked with comments.)
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void ListDir(const char* dirname, string& arena, vector<char*>& entries) { // +
|
|
||||||
entries.clear(); // +
|
|
||||||
if (DIR* dir = opendir(dirname)) {
|
|
||||||
arena.clear(); // +
|
|
||||||
while (struct dirent* ent = (errno = 0, readdir(dir))) {
|
|
||||||
if (!Dots(ent->d_name)) {
|
|
||||||
entries.push_back(reinterpret_cast<char*>(arena.size())); // +
|
|
||||||
arena.append(ent->d_name, strlen(ent->d_name) + 1); // +
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errno) entries.clear();
|
|
||||||
for (char*& p : entries) p = &arena[reinterpret_cast<size_t>(p)]; // +
|
|
||||||
sort(entries.begin(), entries.end(), // +
|
|
||||||
[](const char* a, const char* b) { return strcmp(a, b) < 0; }); // +
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To make performance comparison easier, we can normalize them relative to the baseline. v1 will get
|
|
||||||
performance score of 100. A twice-as-fast alternative will be 200.
|
|
||||||
|
|
||||||
| version | optimization | score |
|
|
||||||
|---------|----------------------------|----------:|
|
|
||||||
| v1 | baseline | 100.0 |
|
|
||||||
| **v2** | **avoid heap allocations** | **112.7** |
|
|
||||||
|
|
||||||
Avoiding heap allocations makes `ListDir()` 12.7% faster. Not bad. As an added bonus, those casts
|
|
||||||
will fend off the occasional frontend developer who accidentally wanders into the codebase.
|
|
||||||
|
|
||||||
## v3
|
|
||||||
|
|
||||||
`opendir()` is an expensive call whose performance is linear in the number of subdirectories in the
|
|
||||||
path because it needs to perform a lookup for every one of them. We can replace it with `openat()`,
|
|
||||||
which takes a file descriptor to the parent directory and a name of the subdirectory. Just a single
|
|
||||||
lookup, less CPU time. This optimization assumes that callers already have a descriptor to the
|
|
||||||
parent directory, which is indeed the case for gitstatusd, and is often the case in other
|
|
||||||
applications that traverse filesystem.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void ListDir(int parent_fd, const char* dirname, string& arena, vector<char*>& entries) { // +
|
|
||||||
entries.clear();
|
|
||||||
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC); // +
|
|
||||||
if (dir_fd < 0) return; // +
|
|
||||||
if (DIR* dir = fdopendir(dir_fd)) {
|
|
||||||
arena.clear();
|
|
||||||
while (struct dirent* ent = (errno = 0, readdir(dir))) {
|
|
||||||
if (!Dots(ent->d_name)) {
|
|
||||||
entries.push_back(reinterpret_cast<char*>(arena.size()));
|
|
||||||
arena.append(ent->d_name, strlen(ent->d_name) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errno) entries.clear();
|
|
||||||
for (char*& p : entries) p = &arena[reinterpret_cast<size_t>(p)];
|
|
||||||
sort(entries.begin(), entries.end(),
|
|
||||||
[](const char* a, const char* b) { return strcmp(a, b) < 0; });
|
|
||||||
closedir(dir);
|
|
||||||
} else { // +
|
|
||||||
close(dir_fd); // +
|
|
||||||
} // +
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is worth about 3.5% in speed.
|
|
||||||
|
|
||||||
| version | optimization | score |
|
|
||||||
|---------|--------------------------------------|----------:|
|
|
||||||
| v1 | baseline | 100.0 |
|
|
||||||
| v2 | avoid heap allocations | 112.7 |
|
|
||||||
| **v3** | **open directories with `openat()`** | **116.2** |
|
|
||||||
|
|
||||||
## v4
|
|
||||||
|
|
||||||
Copying file names to the arena isn't free but it doesn't seem like we can avoid it. Poking around
|
|
||||||
we can see that the POSIX API we are using is implemented on Linux on top of `getdents64` system
|
|
||||||
call. Its documentation isn't very encouraging:
|
|
||||||
|
|
||||||
```text
|
|
||||||
These are not the interfaces you are interested in. Look at
|
|
||||||
readdir(3) for the POSIX-conforming C library interface. This page
|
|
||||||
documents the bare kernel system call interfaces.
|
|
||||||
|
|
||||||
Note: There are no glibc wrappers for these system calls.
|
|
||||||
```
|
|
||||||
|
|
||||||
Hmm... The API looks like something we can take advantage of, so let's try it anyway.
|
|
||||||
|
|
||||||
First, we'll need a simple `Arena` class that can allocate 8KB blocks of memory.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
class Arena {
|
|
||||||
public:
|
|
||||||
enum { kBlockSize = 8 << 10 };
|
|
||||||
|
|
||||||
char* Alloc() {
|
|
||||||
if (cur_ == blocks_.size()) blocks_.emplace_back(kBlockSize, 0);
|
|
||||||
return blocks_[cur_++].data();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clear() { cur_ = 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t cur_ = 0;
|
|
||||||
vector<string> blocks_;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, we need to define `struct dirent64_t` ourselves because there is no wrapper for the system
|
|
||||||
call we are about to use.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
struct dirent64_t {
|
|
||||||
ino64_t d_ino;
|
|
||||||
off64_t d_off;
|
|
||||||
unsigned short d_reclen;
|
|
||||||
unsigned char d_type;
|
|
||||||
char d_name[];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally we can get to the implementation of `ListDir()`.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void ListDir(int parent_fd, Arena& arena, vector<char*>& entries) { // +
|
|
||||||
entries.clear();
|
|
||||||
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
|
||||||
if (dir_fd < 0) return;
|
|
||||||
arena.Clear(); // +
|
|
||||||
while (true) { // +
|
|
||||||
char* buf = arena.Alloc(); // +
|
|
||||||
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize); // +
|
|
||||||
if (n <= 0) { // +
|
|
||||||
if (n) entries.clear(); // +
|
|
||||||
break; // +
|
|
||||||
} // +
|
|
||||||
for (int pos = 0; pos < n;) { // +
|
|
||||||
auto* ent = reinterpret_cast<dirent64_t*>(buf + pos); // +
|
|
||||||
if (!Dots(ent->d_name)) entries.push_back(ent->d_name); // +
|
|
||||||
pos += ent->d_reclen; // +
|
|
||||||
} // +
|
|
||||||
} // +
|
|
||||||
sort(entries.begin(), entries.end(),
|
|
||||||
[](const char* a, const char* b) { return strcmp(a, b) < 0; });
|
|
||||||
close(dir_fd);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
How are we doing with this one?
|
|
||||||
|
|
||||||
| version | optimization | score |
|
|
||||||
|---------|----------------------------------|----------:|
|
|
||||||
| v1 | baseline | 100.0 |
|
|
||||||
| v2 | avoid heap allocations | 112.7 |
|
|
||||||
| v3 | open directories with `openat()` | 116.2 |
|
|
||||||
| **v4** | **call `getdents64()` directly** | **137.8** |
|
|
||||||
|
|
||||||
Solid 20% speedup. Worth the trouble. Unfortunately, we now have just one `reinterpret_cast` instead
|
|
||||||
of two, and it's not nearly as scary-looking. Hopefully with the next iteration we can get back some
|
|
||||||
of that evil vibe of low-level code.
|
|
||||||
|
|
||||||
As a bonus, every element in `entries` has `d_type` at offset -1. This can be useful to the callers
|
|
||||||
that need to distinguish between regular files and directories (gitstatusd, in fact, needs this).
|
|
||||||
Note how `ListDir()` implements this feature at zero cost, as a lucky accident of `dirent64_t`
|
|
||||||
memory layout.
|
|
||||||
|
|
||||||
## v5
|
|
||||||
|
|
||||||
The CPU profile of `ListDir()` reveals that almost all userspace CPU time is spent in `strcmp()`.
|
|
||||||
Digging into the source code of `std::sort()` we can see that it uses Insertion Sort for short
|
|
||||||
collections. Our 32-element vector falls under the threshold. Insertion Sort makes `O(N^2)`
|
|
||||||
comparisons, hence a lot of CPU time in `strcmp()`. Switching to `qsort()` or
|
|
||||||
[Timsort](https://en.wikipedia.org/wiki/Timsort) is of no use as all good sorting algorithms fall
|
|
||||||
back to Insertion Sort.
|
|
||||||
|
|
||||||
If we cannot make fewer comparisons, perhaps we can make each of them faster? `strcmp()` compares
|
|
||||||
characters one at a time. It cannot read ahead as it can be illegal to touch memory past the first
|
|
||||||
null byte. But _we_ know that it's safe to read a few extra bytes past the end of `d_name` for every
|
|
||||||
entry except the last in the buffer. And since we own the buffer, we can overallocate it so that
|
|
||||||
reading past the end of the last entry is also safe.
|
|
||||||
|
|
||||||
Combining these ideas with the fact that file names on Linux are at most 255 bytes long, we can
|
|
||||||
invoke `getdents64()` like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256);
|
|
||||||
```
|
|
||||||
|
|
||||||
And then compare entries like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
[](const char* a, const char* b) { return memcmp(a, b, 255) < 0; }
|
|
||||||
```
|
|
||||||
|
|
||||||
This version doesn't give any speedup compared to the previous but it opens an avenue for another
|
|
||||||
optimization. The pointers we pass to `memcmp()` aren't aligned. To be more specific, their
|
|
||||||
numerical values are `N * 8 + 3` for some `N`. When given such a pointer, `memcmp()` will check the
|
|
||||||
first 5 bytes one by one, and only then switch to comparing 8 bytes at a time. If we can handle the
|
|
||||||
first 5 bytes ourselves, we can pass aligned memory to `memcmp()` and take full advantage of its
|
|
||||||
vectorized loop.
|
|
||||||
|
|
||||||
Here's the implementation:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
uint64_t Read64(const void* p) { // +
|
|
||||||
uint64_t x; // +
|
|
||||||
memcpy(&x, p, sizeof(x)); // +
|
|
||||||
return x; // +
|
|
||||||
} // +
|
|
||||||
|
|
||||||
void ByteSwap64(void* p) { // +
|
|
||||||
uint64_t x = __builtin_bswap64(Read64(p)); // +
|
|
||||||
memcpy(p, &x, sizeof(x)); // +
|
|
||||||
} // +
|
|
||||||
|
|
||||||
void ListDir(int parent_fd, Arena& arena, vector<char*>& entries) {
|
|
||||||
entries.clear();
|
|
||||||
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
|
||||||
if (dir_fd < 0) return;
|
|
||||||
arena.Clear();
|
|
||||||
while (true) {
|
|
||||||
char* buf = arena.Alloc();
|
|
||||||
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256); // +
|
|
||||||
if (n <= 0) {
|
|
||||||
if (n) entries.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (int pos = 0; pos < n;) {
|
|
||||||
auto* ent = reinterpret_cast<dirent64_t*>(buf + pos);
|
|
||||||
if (!Dots(ent->d_name)) {
|
|
||||||
ByteSwap64(ent->d_name); // +
|
|
||||||
entries.push_back(ent->d_name);
|
|
||||||
}
|
|
||||||
pos += ent->d_reclen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort(entries.begin(), entries.end(), [](const char* a, const char* b) {
|
|
||||||
uint64_t x = Read64(a); // +
|
|
||||||
uint64_t y = Read64(b); // +
|
|
||||||
return x < y || (x == y && a != b && memcmp(a + 5, b + 5, 256) < 0); // +
|
|
||||||
});
|
|
||||||
for (char* p : entries) ByteSwap64(p); // +
|
|
||||||
close(dir_fd);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is for Little Endian architecture. Big Endian doesn't need `ByteSwap64()`, so it'll be a bit
|
|
||||||
faster.
|
|
||||||
|
|
||||||
| version | optimization | score |
|
|
||||||
|---------|----------------------------------|----------:|
|
|
||||||
| v1 | baseline | 100.0 |
|
|
||||||
| v2 | avoid heap allocations | 112.7 |
|
|
||||||
| v3 | open directories with `openat()` | 116.2 |
|
|
||||||
| v4 | call `getdents64()` directly | 137.8 |
|
|
||||||
| **v5** | **hand-optimize `strcmp()`** | **143.3** |
|
|
||||||
|
|
||||||
Fast and respectably arcane.
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
Through a series of incremental improvements we've sped up directory listing by 43.3% compared to a
|
|
||||||
naive implementation (v1) and 27.2% compared to a reasonable implementation that a seasoned C/C++
|
|
||||||
practitioner might write (v2).
|
|
||||||
|
|
||||||
However, these numbers are based on an artificial benchmark while the real judge is always the real
|
|
||||||
code. Our goal was to speed up gitstatusd. Benchmark was just a tool. Thankfully, the different
|
|
||||||
versions of `ListDir()` have the same comparative performance within gitstatusd as in the benchmark.
|
|
||||||
In truth, the directory chosen for the benchmark wasn't arbitrary. It was picked by sampling
|
|
||||||
gitstatusd when it runs on [chromium](https://github.com/chromium/chromium) git repository.
|
|
||||||
|
|
||||||
The final version of `ListDir()` spends 97% of its CPU time in the kernel. If we assume that it
|
|
||||||
makes the minimum possible number of system calls and these calls are optimal (true to the best
|
|
||||||
of my knowledge), it puts the upper bound on possible future performance improvements at just 3%.
|
|
||||||
There is almost nothing left in `ListDir()` to optimize.
|
|
||||||
|
|
||||||
![ListDir() CPU profile](
|
|
||||||
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-listdir.png)
|
|
||||||
|
|
||||||
(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
|
|
||||||
rendered with [pprof](https://github.com/google/pprof)).
|
|
|
@ -1,474 +0,0 @@
|
||||||
# Bash bindings for gitstatus.
|
|
||||||
|
|
||||||
[[ $- == *i* ]] || return # non-interactive shell
|
|
||||||
|
|
||||||
# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd
|
|
||||||
# is already running.
|
|
||||||
#
|
|
||||||
# Usage: gitstatus_start [OPTION]...
|
|
||||||
#
|
|
||||||
# -t FLOAT Fail the self-check on initialization if not getting a response from
|
|
||||||
# gitstatusd for this this many seconds. Defaults to 5.
|
|
||||||
#
|
|
||||||
# -s INT Report at most this many staged changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -u INT Report at most this many unstaged changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -c INT Report at most this many conflicted changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -d INT Report at most this many untracked files; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -m INT Report -1 unstaged, untracked and conflicted if there are more than this many
|
|
||||||
# files in the index. Negative value means infinity. Defaults to -1.
|
|
||||||
#
|
|
||||||
# -e Count files within untracked directories like `git status --untracked-files`.
|
|
||||||
#
|
|
||||||
# -U Unless this option is specified, report zero untracked files for repositories
|
|
||||||
# with status.showUntrackedFiles = false.
|
|
||||||
#
|
|
||||||
# -W Unless this option is specified, report zero untracked files for repositories
|
|
||||||
# with bash.showUntrackedFiles = false.
|
|
||||||
#
|
|
||||||
# -D Unless this option is specified, report zero staged, unstaged and conflicted
|
|
||||||
# changes for repositories with bash.showDirtyState = false.
|
|
||||||
#
|
|
||||||
# -r INT Close git repositories that haven't been used for this many seconds. This is
|
|
||||||
# meant to release resources such as memory and file descriptors. The next request
|
|
||||||
# for a repo that's been closed is much slower than for a repo that hasn't been.
|
|
||||||
# Negative value means infinity. The default is 3600 (one hour).
|
|
||||||
function gitstatus_start() {
|
|
||||||
if [[ "$BASH_VERSION" < 4 ]]; then
|
|
||||||
>&2 printf 'gitstatus_start: need bash version >= 4.0, found %s\n' "$BASH_VERSION"
|
|
||||||
>&2 printf '\n'
|
|
||||||
>&2 printf 'To see the version of the current shell, type:\n'
|
|
||||||
>&2 printf '\n'
|
|
||||||
>&2 printf ' \033[32mecho\033[0m \033[33m"$BASH_VERSION"\033[0m\n'
|
|
||||||
>&2 printf '\n'
|
|
||||||
>&2 printf 'The output of `\033[32mbash\033[0m --version` may be different and is not relevant.\n'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset OPTIND
|
|
||||||
local opt timeout=5 max_dirty=-1 ttl=3600 extra_flags=
|
|
||||||
local max_num_staged=1 max_num_unstaged=1 max_num_conflicted=1 max_num_untracked=1
|
|
||||||
while getopts "t:s:u:c:d:m:r:eUWD" opt; do
|
|
||||||
case "$opt" in
|
|
||||||
t) timeout=$OPTARG;;
|
|
||||||
s) max_num_staged=$OPTARG;;
|
|
||||||
u) max_num_unstaged=$OPTARG;;
|
|
||||||
c) max_num_conflicted=$OPTARG;;
|
|
||||||
d) max_num_untracked=$OPTARG;;
|
|
||||||
m) max_dirty=$OPTARG;;
|
|
||||||
r) ttl=$OPTARG;;
|
|
||||||
e) extra_flags+='--recurse-untracked-dirs ';;
|
|
||||||
U) extra_flags+='--ignore-status-show-untracked-files ';;
|
|
||||||
W) extra_flags+='--ignore-bash-show-untracked-files ';;
|
|
||||||
D) extra_flags+='--ignore-bash-show-dirty-state ';;
|
|
||||||
*) return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
(( OPTIND == $# + 1 )) || { echo "usage: gitstatus_start [OPTION]..." >&2; return 1; }
|
|
||||||
|
|
||||||
[[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || return 0 # already started
|
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == */* ]]; then
|
|
||||||
local gitstatus_plugin_dir="${BASH_SOURCE[0]%/*}"
|
|
||||||
if [[ "$gitstatus_plugin_dir" != /* ]]; then
|
|
||||||
gitstatus_plugin_dir="$PWD"/"$gitstatus_plugin_dir"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
local gitstatus_plugin_dir="$PWD"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local tmpdir req_fifo resp_fifo culprit
|
|
||||||
|
|
||||||
function gitstatus_start_impl() {
|
|
||||||
local log_level="${GITSTATUS_LOG_LEVEL:-}"
|
|
||||||
[[ -n "$log_level" || "${GITSTATUS_ENABLE_LOGGING:-0}" != 1 ]] || log_level=INFO
|
|
||||||
|
|
||||||
local uname_sm
|
|
||||||
uname_sm="$(command uname -sm)" || return
|
|
||||||
uname_sm="${uname_sm,,}"
|
|
||||||
local uname_s="${uname_sm% *}"
|
|
||||||
local uname_m="${uname_sm#* }"
|
|
||||||
|
|
||||||
if [[ "${GITSTATUS_NUM_THREADS:-0}" -gt 0 ]]; then
|
|
||||||
local threads="$GITSTATUS_NUM_THREADS"
|
|
||||||
else
|
|
||||||
local cpus
|
|
||||||
if ! command -v sysctl &>/dev/null || [[ "$uname_s" == linux ]] ||
|
|
||||||
! cpus="$(command sysctl -n hw.ncpu)"; then
|
|
||||||
if ! command -v getconf &>/dev/null || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
|
|
||||||
cpus=8
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
local threads=$((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16))
|
|
||||||
fi
|
|
||||||
|
|
||||||
local daemon_args=(
|
|
||||||
--parent-pid="$$"
|
|
||||||
--num-threads="$threads"
|
|
||||||
--max-num-staged="$max_num_staged"
|
|
||||||
--max-num-unstaged="$max_num_unstaged"
|
|
||||||
--max-num-conflicted="$max_num_conflicted"
|
|
||||||
--max-num-untracked="$max_num_untracked"
|
|
||||||
--dirty-max-index-size="$max_dirty"
|
|
||||||
--repo-ttl-seconds="$ttl"
|
|
||||||
$extra_flags)
|
|
||||||
|
|
||||||
if [[ -n "$TMPDIR" && ( ( -d "$TMPDIR" && -w "$TMPDIR" ) || ! ( -d /tmp && -w /tmp ) ) ]]; then
|
|
||||||
local tmpdir=$TMPDIR
|
|
||||||
else
|
|
||||||
local tmpdir=/tmp
|
|
||||||
fi
|
|
||||||
tmpdir="$(command mktemp -d "$tmpdir"/gitstatus.bash.$$.XXXXXXXXXX)" || return
|
|
||||||
|
|
||||||
if [[ -n "$log_level" ]]; then
|
|
||||||
GITSTATUS_DAEMON_LOG="$tmpdir"/daemon.log
|
|
||||||
[[ "$log_level" == INFO ]] || daemon_args+=(--log-level="$log_level")
|
|
||||||
else
|
|
||||||
GITSTATUS_DAEMON_LOG=/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
req_fifo="$tmpdir"/req.fifo
|
|
||||||
resp_fifo="$tmpdir"/resp.fifo
|
|
||||||
command mkfifo -- "$req_fifo" "$resp_fifo" || return
|
|
||||||
|
|
||||||
{
|
|
||||||
(
|
|
||||||
trap '' INT QUIT TSTP
|
|
||||||
[[ "$GITSTATUS_DAEMON_LOG" == /dev/null ]] || set -x
|
|
||||||
builtin cd /
|
|
||||||
|
|
||||||
(
|
|
||||||
local fd_in fd_out
|
|
||||||
exec {fd_in}<"$req_fifo" {fd_out}>>"$resp_fifo" || exit
|
|
||||||
echo "$BASHPID" >&"$fd_out"
|
|
||||||
|
|
||||||
local _gitstatus_bash_daemon _gitstatus_bash_version _gitstatus_bash_downloaded
|
|
||||||
|
|
||||||
function _gitstatus_set_daemon() {
|
|
||||||
_gitstatus_bash_daemon="$1"
|
|
||||||
_gitstatus_bash_version="$2"
|
|
||||||
_gitstatus_bash_downloaded="$3"
|
|
||||||
}
|
|
||||||
|
|
||||||
set -- -d "$gitstatus_plugin_dir" -s "$uname_s" -m "$uname_m" \
|
|
||||||
-p "printf '.\036' >&$fd_out" -e "$fd_out" -- _gitstatus_set_daemon
|
|
||||||
[[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || set -- -n "$@"
|
|
||||||
source "$gitstatus_plugin_dir"/install || return
|
|
||||||
[[ -n "$_gitstatus_bash_daemon" ]] || return
|
|
||||||
[[ -n "$_gitstatus_bash_version" ]] || return
|
|
||||||
[[ "$_gitstatus_bash_downloaded" == [01] ]] || return
|
|
||||||
|
|
||||||
local sig=(TERM ILL PIPE)
|
|
||||||
|
|
||||||
if (( UID == EUID )); then
|
|
||||||
local home=~
|
|
||||||
else
|
|
||||||
local user
|
|
||||||
user="$(command id -un)" || return
|
|
||||||
[[ "$user" =~ ^[a-zA-Z0-9_,.-]+$ ]] || return
|
|
||||||
eval "local home=~$user"
|
|
||||||
[[ -n "$home" ]] || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -x "$_gitstatus_bash_daemon" ]]; then
|
|
||||||
HOME="$home" "$_gitstatus_bash_daemon" \
|
|
||||||
-G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
|
|
||||||
local pid=$!
|
|
||||||
trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
|
|
||||||
wait "$pid"
|
|
||||||
local ret=$?
|
|
||||||
trap - ${sig[@]}
|
|
||||||
case "$ret" in
|
|
||||||
0|129|130|131|137|141|143|159)
|
|
||||||
echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
|
|
||||||
exit "$ret"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( ! _gitstatus_bash_downloaded )) || return
|
|
||||||
[[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || return
|
|
||||||
[[ "$_gitstatus_bash_daemon" == \
|
|
||||||
"${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"/* ]] || return
|
|
||||||
|
|
||||||
set -- -f "$@"
|
|
||||||
_gitstatus_bash_daemon=
|
|
||||||
_gitstatus_bash_version=
|
|
||||||
_gitstatus_bash_downloaded=
|
|
||||||
source "$gitstatus_plugin_dir"/install || return
|
|
||||||
[[ -n "$_gitstatus_bash_daemon" ]] || return
|
|
||||||
[[ -n "$_gitstatus_bash_version" ]] || return
|
|
||||||
[[ "$_gitstatus_bash_downloaded" == 1 ]] || return
|
|
||||||
|
|
||||||
HOME="$home" "$_gitstatus_bash_daemon" \
|
|
||||||
-G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
|
|
||||||
local pid=$!
|
|
||||||
trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
|
|
||||||
wait "$pid"
|
|
||||||
trap - ${sig[@]}
|
|
||||||
echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
|
|
||||||
) & disown
|
|
||||||
) & disown
|
|
||||||
} 0</dev/null &>"$GITSTATUS_DAEMON_LOG"
|
|
||||||
|
|
||||||
exec {_GITSTATUS_REQ_FD}>>"$req_fifo" {_GITSTATUS_RESP_FD}<"$resp_fifo" || return
|
|
||||||
command rm -f -- "$req_fifo" "$resp_fifo" || return
|
|
||||||
[[ "$GITSTATUS_DAEMON_LOG" != /dev/null ]] || command rmdir -- "$tmpdir" 2>/dev/null
|
|
||||||
|
|
||||||
IFS='' read -r -u $_GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID || return
|
|
||||||
[[ "$GITSTATUS_DAEMON_PID" == [1-9]* ]] || return
|
|
||||||
|
|
||||||
local reply
|
|
||||||
echo -nE $'}hello\x1f\x1e' >&$_GITSTATUS_REQ_FD || return
|
|
||||||
local dl=
|
|
||||||
while true; do
|
|
||||||
reply=
|
|
||||||
if ! IFS='' read -rd $'\x1e' -u $_GITSTATUS_RESP_FD -t "$timeout" reply; then
|
|
||||||
culprit="$reply"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
[[ "$reply" == $'}hello\x1f0' ]] && break
|
|
||||||
if [[ -z "$dl" ]]; then
|
|
||||||
dl=1
|
|
||||||
if [[ -t 2 ]]; then
|
|
||||||
local spinner=('\b\033[33m-\033[0m' '\b\033[33m\\\033[0m' '\b\033[33m|\033[0m' '\b\033[33m/\033[0m')
|
|
||||||
>&2 printf '[\033[33mgitstatus\033[0m] fetching \033[32mgitstatusd\033[0m .. '
|
|
||||||
else
|
|
||||||
local spinner=('.')
|
|
||||||
>&2 printf '[gitstatus] fetching gitstatusd ..'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
>&2 printf "${spinner[0]}"
|
|
||||||
spinner=("${spinner[@]:1}" "${spinner[0]}")
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "$dl" ]]; then
|
|
||||||
if [[ -t 2 ]]; then
|
|
||||||
>&2 printf '\b[\033[32mok\033[0m]\n'
|
|
||||||
else
|
|
||||||
>&2 echo ' [ok]'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
_GITSTATUS_DIRTY_MAX_INDEX_SIZE=$max_dirty
|
|
||||||
_GITSTATUS_CLIENT_PID="$BASHPID"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! gitstatus_start_impl; then
|
|
||||||
>&2 printf '\n'
|
|
||||||
>&2 printf '[\033[31mERROR\033[0m]: gitstatus failed to initialize.\n'
|
|
||||||
if [[ -n "${culprit-}" ]]; then
|
|
||||||
>&2 printf '\n%s\n' "$culprit"
|
|
||||||
fi
|
|
||||||
[[ -z "${req_fifo:-}" ]] || command rm -f "$req_fifo"
|
|
||||||
[[ -z "${resp_fifo:-}" ]] || command rm -f "$resp_fifo"
|
|
||||||
unset -f gitstatus_start_impl
|
|
||||||
gitstatus_stop
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export _GITSTATUS_CLIENT_PID _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID
|
|
||||||
unset -f gitstatus_start_impl
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stops gitstatusd if it's running.
|
|
||||||
function gitstatus_stop() {
|
|
||||||
if [[ "${_GITSTATUS_CLIENT_PID:-$BASHPID}" == "$BASHPID" ]]; then
|
|
||||||
[[ -z "${_GITSTATUS_REQ_FD:-}" ]] || exec {_GITSTATUS_REQ_FD}>&- || true
|
|
||||||
[[ -z "${_GITSTATUS_RESP_FD:-}" ]] || exec {_GITSTATUS_RESP_FD}>&- || true
|
|
||||||
[[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || kill "$GITSTATUS_DAEMON_PID" &>/dev/null || true
|
|
||||||
fi
|
|
||||||
unset _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID
|
|
||||||
unset _GITSTATUS_DIRTY_MAX_INDEX_SIZE _GITSTATUS_CLIENT_PID
|
|
||||||
}
|
|
||||||
|
|
||||||
# Retrives status of a git repository from a directory under its working tree.
|
|
||||||
#
|
|
||||||
# Usage: gitstatus_query [OPTION]...
|
|
||||||
#
|
|
||||||
# -d STR Directory to query. Defaults to $PWD. Has no effect if GIT_DIR is set.
|
|
||||||
# -t FLOAT Timeout in seconds. Will block for at most this long. If no results
|
|
||||||
# are available by then, will return error.
|
|
||||||
# -p Don't compute anything that requires reading Git index. If this option is used,
|
|
||||||
# the following parameters will be 0: VCS_STATUS_INDEX_SIZE,
|
|
||||||
# VCS_STATUS_{NUM,HAS}_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED}.
|
|
||||||
#
|
|
||||||
# On success sets VCS_STATUS_RESULT to one of the following values:
|
|
||||||
#
|
|
||||||
# norepo-sync The directory doesn't belong to a git repository.
|
|
||||||
# ok-sync The directory belongs to a git repository.
|
|
||||||
#
|
|
||||||
# If VCS_STATUS_RESULT is ok-sync, additional variables are set:
|
|
||||||
#
|
|
||||||
# VCS_STATUS_WORKDIR Git repo working directory. Not empty.
|
|
||||||
# VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or
|
|
||||||
# empty if there is no HEAD (empty repo).
|
|
||||||
# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8.
|
|
||||||
# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line.
|
|
||||||
# VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch.
|
|
||||||
# VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin".
|
|
||||||
# VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty.
|
|
||||||
# VCS_STATUS_REMOTE_URL Remote URL. Can be empty.
|
|
||||||
# VCS_STATUS_ACTION Repository state, A.K.A. action. Can be empty.
|
|
||||||
# VCS_STATUS_INDEX_SIZE The number of files in the index.
|
|
||||||
# VCS_STATUS_NUM_STAGED The number of staged changes.
|
|
||||||
# VCS_STATUS_NUM_CONFLICTED The number of conflicted changes.
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED The number of unstaged changes.
|
|
||||||
# VCS_STATUS_NUM_UNTRACKED The number of untracked files.
|
|
||||||
# VCS_STATUS_HAS_STAGED 1 if there are staged changes, 0 otherwise.
|
|
||||||
# VCS_STATUS_HAS_CONFLICTED 1 if there are conflicted changes, 0 otherwise.
|
|
||||||
# VCS_STATUS_HAS_UNSTAGED 1 if there are unstaged changes, 0 if there aren't, -1 if
|
|
||||||
# unknown.
|
|
||||||
# VCS_STATUS_NUM_STAGED_NEW The number of staged new files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_NUM_STAGED_DELETED The number of staged deleted files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED_DELETED The number of unstaged deleted files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_HAS_UNTRACKED 1 if there are untracked files, 0 if there aren't, -1 if
|
|
||||||
# unknown.
|
|
||||||
# VCS_STATUS_COMMITS_AHEAD Number of commits the current branch is ahead of upstream.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_COMMITS_BEHIND Number of commits the current branch is behind upstream.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_STASHES Number of stashes. Non-negative integer.
|
|
||||||
# VCS_STATUS_TAG The last tag (in lexicographical order) that points to the same
|
|
||||||
# commit as HEAD.
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_NAME The push remote name, e.g. "upstream" or "origin".
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_URL Push remote URL. Can be empty.
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_AHEAD Number of commits the current branch is ahead of push remote.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_BEHIND Number of commits the current branch is behind push remote.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_NUM_SKIP_WORKTREE The number of files in the index with skip-worktree bit set.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_NUM_ASSUME_UNCHANGED The number of files in the index with assume-unchanged bit set.
|
|
||||||
# Non-negative integer.
|
|
||||||
#
|
|
||||||
# The point of reporting -1 via VCS_STATUS_HAS_* is to allow the command to skip scanning files in
|
|
||||||
# large repos. See -m flag of gitstatus_start.
|
|
||||||
#
|
|
||||||
# gitstatus_query returns an error if gitstatus_start hasn't been called in the same
|
|
||||||
# shell or the call had failed.
|
|
||||||
function gitstatus_query() {
|
|
||||||
unset OPTIND
|
|
||||||
local opt dir= timeout=() no_diff=0
|
|
||||||
while getopts "d:c:t:p" opt "$@"; do
|
|
||||||
case "$opt" in
|
|
||||||
d) dir=$OPTARG;;
|
|
||||||
t) timeout=(-t "$OPTARG");;
|
|
||||||
p) no_diff=1;;
|
|
||||||
*) return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
(( OPTIND == $# + 1 )) || { echo "usage: gitstatus_query [OPTION]..." >&2; return 1; }
|
|
||||||
|
|
||||||
[[ -n "${GITSTATUS_DAEMON_PID-}" ]] || return # not started
|
|
||||||
|
|
||||||
local req_id="$RANDOM.$RANDOM.$RANDOM.$RANDOM"
|
|
||||||
if [[ -z "${GIT_DIR:-}" ]]; then
|
|
||||||
[[ "$dir" == /* ]] || dir="$(pwd -P)/$dir" || return
|
|
||||||
elif [[ "$GIT_DIR" == /* ]]; then
|
|
||||||
dir=:"$GIT_DIR"
|
|
||||||
else
|
|
||||||
dir=:"$(pwd -P)/$GIT_DIR" || return
|
|
||||||
fi
|
|
||||||
echo -nE "$req_id"$'\x1f'"$dir"$'\x1f'"$no_diff"$'\x1e' >&$_GITSTATUS_REQ_FD || return
|
|
||||||
|
|
||||||
local -a resp
|
|
||||||
while true; do
|
|
||||||
IFS=$'\x1f' read -rd $'\x1e' -a resp -u $_GITSTATUS_RESP_FD "${timeout[@]}" || return
|
|
||||||
[[ "${resp[0]}" == "$req_id" ]] && break
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "${resp[1]}" == 1 ]]; then
|
|
||||||
VCS_STATUS_RESULT=ok-sync
|
|
||||||
VCS_STATUS_WORKDIR="${resp[2]}"
|
|
||||||
VCS_STATUS_COMMIT="${resp[3]}"
|
|
||||||
VCS_STATUS_LOCAL_BRANCH="${resp[4]}"
|
|
||||||
VCS_STATUS_REMOTE_BRANCH="${resp[5]}"
|
|
||||||
VCS_STATUS_REMOTE_NAME="${resp[6]}"
|
|
||||||
VCS_STATUS_REMOTE_URL="${resp[7]}"
|
|
||||||
VCS_STATUS_ACTION="${resp[8]}"
|
|
||||||
VCS_STATUS_INDEX_SIZE="${resp[9]}"
|
|
||||||
VCS_STATUS_NUM_STAGED="${resp[10]}"
|
|
||||||
VCS_STATUS_NUM_UNSTAGED="${resp[11]}"
|
|
||||||
VCS_STATUS_NUM_CONFLICTED="${resp[12]}"
|
|
||||||
VCS_STATUS_NUM_UNTRACKED="${resp[13]}"
|
|
||||||
VCS_STATUS_COMMITS_AHEAD="${resp[14]}"
|
|
||||||
VCS_STATUS_COMMITS_BEHIND="${resp[15]}"
|
|
||||||
VCS_STATUS_STASHES="${resp[16]}"
|
|
||||||
VCS_STATUS_TAG="${resp[17]}"
|
|
||||||
VCS_STATUS_NUM_UNSTAGED_DELETED="${resp[18]}"
|
|
||||||
VCS_STATUS_NUM_STAGED_NEW="${resp[19]:-0}"
|
|
||||||
VCS_STATUS_NUM_STAGED_DELETED="${resp[20]:-0}"
|
|
||||||
VCS_STATUS_PUSH_REMOTE_NAME="${resp[21]:-}"
|
|
||||||
VCS_STATUS_PUSH_REMOTE_URL="${resp[22]:-}"
|
|
||||||
VCS_STATUS_PUSH_COMMITS_AHEAD="${resp[23]:-0}"
|
|
||||||
VCS_STATUS_PUSH_COMMITS_BEHIND="${resp[24]:-0}"
|
|
||||||
VCS_STATUS_NUM_SKIP_WORKTREE="${resp[25]:-0}"
|
|
||||||
VCS_STATUS_NUM_ASSUME_UNCHANGED="${resp[26]:-0}"
|
|
||||||
VCS_STATUS_COMMIT_ENCODING="${resp[27]-}"
|
|
||||||
VCS_STATUS_COMMIT_SUMMARY="${resp[28]-}"
|
|
||||||
VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0))
|
|
||||||
if (( _GITSTATUS_DIRTY_MAX_INDEX_SIZE >= 0 &&
|
|
||||||
VCS_STATUS_INDEX_SIZE > _GITSTATUS_DIRTY_MAX_INDEX_SIZE_ )); then
|
|
||||||
VCS_STATUS_HAS_UNSTAGED=-1
|
|
||||||
VCS_STATUS_HAS_CONFLICTED=-1
|
|
||||||
VCS_STATUS_HAS_UNTRACKED=-1
|
|
||||||
else
|
|
||||||
VCS_STATUS_HAS_UNSTAGED=$((VCS_STATUS_NUM_UNSTAGED > 0))
|
|
||||||
VCS_STATUS_HAS_CONFLICTED=$((VCS_STATUS_NUM_CONFLICTED > 0))
|
|
||||||
VCS_STATUS_HAS_UNTRACKED=$((VCS_STATUS_NUM_UNTRACKED > 0))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
VCS_STATUS_RESULT=norepo-sync
|
|
||||||
unset VCS_STATUS_WORKDIR
|
|
||||||
unset VCS_STATUS_COMMIT
|
|
||||||
unset VCS_STATUS_LOCAL_BRANCH
|
|
||||||
unset VCS_STATUS_REMOTE_BRANCH
|
|
||||||
unset VCS_STATUS_REMOTE_NAME
|
|
||||||
unset VCS_STATUS_REMOTE_URL
|
|
||||||
unset VCS_STATUS_ACTION
|
|
||||||
unset VCS_STATUS_INDEX_SIZE
|
|
||||||
unset VCS_STATUS_NUM_STAGED
|
|
||||||
unset VCS_STATUS_NUM_UNSTAGED
|
|
||||||
unset VCS_STATUS_NUM_CONFLICTED
|
|
||||||
unset VCS_STATUS_NUM_UNTRACKED
|
|
||||||
unset VCS_STATUS_HAS_STAGED
|
|
||||||
unset VCS_STATUS_HAS_UNSTAGED
|
|
||||||
unset VCS_STATUS_HAS_CONFLICTED
|
|
||||||
unset VCS_STATUS_HAS_UNTRACKED
|
|
||||||
unset VCS_STATUS_COMMITS_AHEAD
|
|
||||||
unset VCS_STATUS_COMMITS_BEHIND
|
|
||||||
unset VCS_STATUS_STASHES
|
|
||||||
unset VCS_STATUS_TAG
|
|
||||||
unset VCS_STATUS_NUM_UNSTAGED_DELETED
|
|
||||||
unset VCS_STATUS_NUM_STAGED_NEW
|
|
||||||
unset VCS_STATUS_NUM_STAGED_DELETED
|
|
||||||
unset VCS_STATUS_PUSH_REMOTE_NAME
|
|
||||||
unset VCS_STATUS_PUSH_REMOTE_URL
|
|
||||||
unset VCS_STATUS_PUSH_COMMITS_AHEAD
|
|
||||||
unset VCS_STATUS_PUSH_COMMITS_BEHIND
|
|
||||||
unset VCS_STATUS_NUM_SKIP_WORKTREE
|
|
||||||
unset VCS_STATUS_NUM_ASSUME_UNCHANGED
|
|
||||||
unset VCS_STATUS_COMMIT_ENCODING
|
|
||||||
unset VCS_STATUS_COMMIT_SUMMARY
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Usage: gitstatus_check.
|
|
||||||
#
|
|
||||||
# Returns 0 if and only if gitstatus_start has succeeded previously.
|
|
||||||
# If it returns non-zero, gitstatus_query is guaranteed to return non-zero.
|
|
||||||
function gitstatus_check() {
|
|
||||||
[[ -n "$GITSTATUS_DAEMON_PID" ]]
|
|
||||||
}
|
|
|
@ -1,908 +0,0 @@
|
||||||
# Zsh bindings for gitstatus.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Example: Start gitstatusd, send it a request, wait for response and print it.
|
|
||||||
#
|
|
||||||
# source ~/gitstatus/gitstatus.plugin.zsh
|
|
||||||
# gitstatus_start MY
|
|
||||||
# gitstatus_query -d $PWD MY
|
|
||||||
# typeset -m 'VCS_STATUS_*'
|
|
||||||
#
|
|
||||||
# Output:
|
|
||||||
#
|
|
||||||
# VCS_STATUS_ACTION=''
|
|
||||||
# VCS_STATUS_COMMIT=c000eddcff0fb38df2d0137efe24d9d2d900f209
|
|
||||||
# VCS_STATUS_COMMITS_AHEAD=0
|
|
||||||
# VCS_STATUS_COMMITS_BEHIND=0
|
|
||||||
# VCS_STATUS_COMMIT_ENCODING=''
|
|
||||||
# VCS_STATUS_COMMIT_SUMMARY='pull upstream changes from gitstatus'
|
|
||||||
# VCS_STATUS_HAS_CONFLICTED=0
|
|
||||||
# VCS_STATUS_HAS_STAGED=0
|
|
||||||
# VCS_STATUS_HAS_UNSTAGED=1
|
|
||||||
# VCS_STATUS_HAS_UNTRACKED=1
|
|
||||||
# VCS_STATUS_INDEX_SIZE=33
|
|
||||||
# VCS_STATUS_LOCAL_BRANCH=master
|
|
||||||
# VCS_STATUS_NUM_ASSUME_UNCHANGED=0
|
|
||||||
# VCS_STATUS_NUM_CONFLICTED=0
|
|
||||||
# VCS_STATUS_NUM_STAGED=0
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED=1
|
|
||||||
# VCS_STATUS_NUM_SKIP_WORKTREE=0
|
|
||||||
# VCS_STATUS_NUM_STAGED_NEW=0
|
|
||||||
# VCS_STATUS_NUM_STAGED_DELETED=0
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED_DELETED=0
|
|
||||||
# VCS_STATUS_NUM_UNTRACKED=1
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_AHEAD=0
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_BEHIND=0
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_NAME=''
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_URL=''
|
|
||||||
# VCS_STATUS_REMOTE_BRANCH=master
|
|
||||||
# VCS_STATUS_REMOTE_NAME=origin
|
|
||||||
# VCS_STATUS_REMOTE_URL=git@github.com:romkatv/powerlevel10k.git
|
|
||||||
# VCS_STATUS_RESULT=ok-sync
|
|
||||||
# VCS_STATUS_STASHES=0
|
|
||||||
# VCS_STATUS_TAG=''
|
|
||||||
# VCS_STATUS_WORKDIR=/home/romka/powerlevel10k
|
|
||||||
|
|
||||||
[[ -o 'interactive' ]] || 'return'
|
|
||||||
|
|
||||||
# Temporarily change options.
|
|
||||||
'builtin' 'local' '-a' '_gitstatus_opts'
|
|
||||||
[[ ! -o 'aliases' ]] || _gitstatus_opts+=('aliases')
|
|
||||||
[[ ! -o 'sh_glob' ]] || _gitstatus_opts+=('sh_glob')
|
|
||||||
[[ ! -o 'no_brace_expand' ]] || _gitstatus_opts+=('no_brace_expand')
|
|
||||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
|
||||||
|
|
||||||
autoload -Uz add-zsh-hook || return
|
|
||||||
zmodload zsh/datetime zsh/system || return
|
|
||||||
zmodload -F zsh/files b:zf_rm || return
|
|
||||||
|
|
||||||
typeset -g _gitstatus_plugin_dir"${1:-}"="${${(%):-%x}:A:h}"
|
|
||||||
|
|
||||||
# Retrives status of a git repo from a directory under its working tree.
|
|
||||||
#
|
|
||||||
## Usage: gitstatus_query [OPTION]... NAME
|
|
||||||
#
|
|
||||||
# -d STR Directory to query. Defaults to the current directory. Has no effect if GIT_DIR
|
|
||||||
# is set.
|
|
||||||
# -c STR Callback function to call once the results are available. Called only after
|
|
||||||
# gitstatus_query returns 0 with VCS_STATUS_RESULT=tout.
|
|
||||||
# -t FLOAT Timeout in seconds. Negative value means infinity. Will block for at most this long.
|
|
||||||
# If no results are available by then: if -c isn't specified, will return 1; otherwise
|
|
||||||
# will set VCS_STATUS_RESULT=tout and return 0.
|
|
||||||
# -p Don't compute anything that requires reading Git index. If this option is used,
|
|
||||||
# the following parameters will be 0: VCS_STATUS_INDEX_SIZE,
|
|
||||||
# VCS_STATUS_{NUM,HAS}_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED}.
|
|
||||||
#
|
|
||||||
# On success sets VCS_STATUS_RESULT to one of the following values:
|
|
||||||
#
|
|
||||||
# tout Timed out waiting for data; will call the user-specified callback later.
|
|
||||||
# norepo-sync The directory isn't a git repo.
|
|
||||||
# ok-sync The directory is a git repo.
|
|
||||||
#
|
|
||||||
# When the callback is called, VCS_STATUS_RESULT is set to one of the following values:
|
|
||||||
#
|
|
||||||
# norepo-async The directory isn't a git repo.
|
|
||||||
# ok-async The directory is a git repo.
|
|
||||||
#
|
|
||||||
# If VCS_STATUS_RESULT is ok-sync or ok-async, additional variables are set:
|
|
||||||
#
|
|
||||||
# VCS_STATUS_WORKDIR Git repo working directory. Not empty.
|
|
||||||
# VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or
|
|
||||||
# empty if there is no HEAD (empty repo).
|
|
||||||
# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8.
|
|
||||||
# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line.
|
|
||||||
# VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch.
|
|
||||||
# VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin".
|
|
||||||
# VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty.
|
|
||||||
# VCS_STATUS_REMOTE_URL Remote URL. Can be empty.
|
|
||||||
# VCS_STATUS_ACTION Repository state, A.K.A. action. Can be empty.
|
|
||||||
# VCS_STATUS_INDEX_SIZE The number of files in the index.
|
|
||||||
# VCS_STATUS_NUM_STAGED The number of staged changes.
|
|
||||||
# VCS_STATUS_NUM_CONFLICTED The number of conflicted changes.
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED The number of unstaged changes.
|
|
||||||
# VCS_STATUS_NUM_UNTRACKED The number of untracked files.
|
|
||||||
# VCS_STATUS_HAS_STAGED 1 if there are staged changes, 0 otherwise.
|
|
||||||
# VCS_STATUS_HAS_CONFLICTED 1 if there are conflicted changes, 0 otherwise.
|
|
||||||
# VCS_STATUS_HAS_UNSTAGED 1 if there are unstaged changes, 0 if there aren't, -1 if
|
|
||||||
# unknown.
|
|
||||||
# VCS_STATUS_NUM_STAGED_NEW The number of staged new files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_NUM_STAGED_DELETED The number of staged deleted files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_NUM_UNSTAGED_DELETED The number of unstaged deleted files. Note that renamed files
|
|
||||||
# are reported as deleted plus new.
|
|
||||||
# VCS_STATUS_HAS_UNTRACKED 1 if there are untracked files, 0 if there aren't, -1 if
|
|
||||||
# unknown.
|
|
||||||
# VCS_STATUS_COMMITS_AHEAD Number of commits the current branch is ahead of upstream.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_COMMITS_BEHIND Number of commits the current branch is behind upstream.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_STASHES Number of stashes. Non-negative integer.
|
|
||||||
# VCS_STATUS_TAG The last tag (in lexicographical order) that points to the same
|
|
||||||
# commit as HEAD.
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_NAME The push remote name, e.g. "upstream" or "origin".
|
|
||||||
# VCS_STATUS_PUSH_REMOTE_URL Push remote URL. Can be empty.
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_AHEAD Number of commits the current branch is ahead of push remote.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_PUSH_COMMITS_BEHIND Number of commits the current branch is behind push remote.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_NUM_SKIP_WORKTREE The number of files in the index with skip-worktree bit set.
|
|
||||||
# Non-negative integer.
|
|
||||||
# VCS_STATUS_NUM_ASSUME_UNCHANGED The number of files in the index with assume-unchanged bit set.
|
|
||||||
# Non-negative integer.
|
|
||||||
#
|
|
||||||
# The point of reporting -1 via VCS_STATUS_HAS_* is to allow the command to skip scanning files in
|
|
||||||
# large repos. See -m flag of gitstatus_start.
|
|
||||||
#
|
|
||||||
# gitstatus_query returns an error if gitstatus_start hasn't been called in the same shell or
|
|
||||||
# the call had failed.
|
|
||||||
#
|
|
||||||
# !!!!! WARNING: CONCURRENT CALLS WITH THE SAME NAME ARE NOT ALLOWED !!!!!
|
|
||||||
#
|
|
||||||
# It's illegal to call gitstatus_query if the last asynchronous call with the same NAME hasn't
|
|
||||||
# completed yet. If you need to issue concurrent requests, use different NAME arguments.
|
|
||||||
function gitstatus_query"${1:-}"() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
|
|
||||||
local fsuf=${${(%):-%N}#gitstatus_query}
|
|
||||||
|
|
||||||
unset VCS_STATUS_RESULT
|
|
||||||
|
|
||||||
local opt dir callback OPTARG
|
|
||||||
local -i no_diff OPTIND
|
|
||||||
local -F timeout=-1
|
|
||||||
while getopts ":d:c:t:p" opt; do
|
|
||||||
case $opt in
|
|
||||||
+p) no_diff=0;;
|
|
||||||
p) no_diff=1;;
|
|
||||||
d) dir=$OPTARG;;
|
|
||||||
c) callback=$OPTARG;;
|
|
||||||
t)
|
|
||||||
if [[ $OPTARG != (|+|-)<->(|.<->)(|[eE](|-|+)<->) ]]; then
|
|
||||||
print -ru2 -- "gitstatus_query: invalid -t argument: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
timeout=OPTARG
|
|
||||||
;;
|
|
||||||
\?) print -ru2 -- "gitstatus_query: invalid option: $OPTARG" ; return 1;;
|
|
||||||
:) print -ru2 -- "gitstatus_query: missing required argument: $OPTARG"; return 1;;
|
|
||||||
*) print -ru2 -- "gitstatus_query: invalid option: $opt" ; return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( OPTIND != ARGC )); then
|
|
||||||
print -ru2 -- "gitstatus_query: exactly one positional argument is required"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local name=$*[OPTIND]
|
|
||||||
if [[ $name != [[:IDENT:]]## ]]; then
|
|
||||||
print -ru2 -- "gitstatus_query: invalid positional argument: $name"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( _GITSTATUS_STATE_$name == 2 )) || return
|
|
||||||
|
|
||||||
if [[ -z $GIT_DIR ]]; then
|
|
||||||
if [[ $dir != /* ]]; then
|
|
||||||
if [[ $PWD == /* && $PWD -ef . ]]; then
|
|
||||||
dir=$PWD/$dir
|
|
||||||
else
|
|
||||||
dir=${dir:a}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ $GIT_DIR == /* ]]; then
|
|
||||||
dir=:$GIT_DIR
|
|
||||||
elif [[ $PWD == /* && $PWD -ef . ]]; then
|
|
||||||
dir=:$PWD/$GIT_DIR
|
|
||||||
else
|
|
||||||
dir=:${GIT_DIR:a}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $dir != (|:)/* ]]; then
|
|
||||||
typeset -g VCS_STATUS_RESULT=norepo-sync
|
|
||||||
_gitstatus_clear$fsuf
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local -i req_fd=${(P)${:-_GITSTATUS_REQ_FD_$name}}
|
|
||||||
local req_id=$EPOCHREALTIME
|
|
||||||
print -rnu $req_fd -- $req_id' '$callback$'\x1f'$dir$'\x1f'$no_diff$'\x1e' || return
|
|
||||||
|
|
||||||
(( ++_GITSTATUS_NUM_INFLIGHT_$name ))
|
|
||||||
|
|
||||||
if (( timeout == 0 )); then
|
|
||||||
typeset -g VCS_STATUS_RESULT=tout
|
|
||||||
_gitstatus_clear$fsuf
|
|
||||||
else
|
|
||||||
while true; do
|
|
||||||
_gitstatus_process_response$fsuf $name $timeout $req_id || return
|
|
||||||
[[ $VCS_STATUS_RESULT == *-async ]] || break
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
[[ $VCS_STATUS_RESULT != tout || -n $callback ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# If the last call to gitstatus_query timed out (VCS_STATUS_RESULT=tout), wait for the callback
|
|
||||||
# to be called. Otherwise do nothing.
|
|
||||||
#
|
|
||||||
# Usage: gitstatus_process_results [OPTION]... NAME
|
|
||||||
#
|
|
||||||
# -t FLOAT Timeout in seconds. Negative value means infinity. Will block for at most this long.
|
|
||||||
#
|
|
||||||
# Returns an error only when invoked with incorrect arguments and when gitstatusd isn't running or
|
|
||||||
# broken.
|
|
||||||
#
|
|
||||||
# If a callback gets called, VCS_STATUS_* parameters are set as in gitstatus_query.
|
|
||||||
# VCS_STATUS_RESULT is either norepo-async or ok-async.
|
|
||||||
function gitstatus_process_results"${1:-}"() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
|
|
||||||
local fsuf=${${(%):-%N}#gitstatus_process_results}
|
|
||||||
|
|
||||||
local opt OPTARG
|
|
||||||
local -i OPTIND
|
|
||||||
local -F timeout=-1
|
|
||||||
while getopts ":t:" opt; do
|
|
||||||
case $opt in
|
|
||||||
t)
|
|
||||||
if [[ $OPTARG != (|+|-)<->(|.<->)(|[eE](|-|+)<->) ]]; then
|
|
||||||
print -ru2 -- "gitstatus_process_results: invalid -t argument: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
timeout=OPTARG
|
|
||||||
;;
|
|
||||||
\?) print -ru2 -- "gitstatus_process_results: invalid option: $OPTARG" ; return 1;;
|
|
||||||
:) print -ru2 -- "gitstatus_process_results: missing required argument: $OPTARG"; return 1;;
|
|
||||||
*) print -ru2 -- "gitstatus_process_results: invalid option: $opt" ; return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( OPTIND != ARGC )); then
|
|
||||||
print -ru2 -- "gitstatus_process_results: exactly one positional argument is required"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local name=$*[OPTIND]
|
|
||||||
if [[ $name != [[:IDENT:]]## ]]; then
|
|
||||||
print -ru2 -- "gitstatus_process_results: invalid positional argument: $name"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( _GITSTATUS_STATE_$name == 2 )) || return
|
|
||||||
|
|
||||||
while (( _GITSTATUS_NUM_INFLIGHT_$name )); do
|
|
||||||
_gitstatus_process_response$fsuf $name $timeout '' || return
|
|
||||||
[[ $VCS_STATUS_RESULT == *-async ]] || break
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function _gitstatus_clear"${1:-}"() {
|
|
||||||
unset VCS_STATUS_{WORKDIR,COMMIT,LOCAL_BRANCH,REMOTE_BRANCH,REMOTE_NAME,REMOTE_URL,ACTION,INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,HAS_STAGED,HAS_UNSTAGED,HAS_CONFLICTED,HAS_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,TAG,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_REMOTE_NAME,PUSH_REMOTE_URL,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _gitstatus_process_response"${1:-}"() {
|
|
||||||
local name=$1 timeout req_id=$3 buf
|
|
||||||
local -i resp_fd=_GITSTATUS_RESP_FD_$name
|
|
||||||
local -i dirty_max_index_size=_GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name
|
|
||||||
|
|
||||||
(( $2 >= 0 )) && timeout=-t$2 && [[ -t $resp_fd ]]
|
|
||||||
sysread $timeout -i $resp_fd 'buf[$#buf+1]' || {
|
|
||||||
if (( $? == 4 )); then
|
|
||||||
if [[ -n $req_id ]]; then
|
|
||||||
typeset -g VCS_STATUS_RESULT=tout
|
|
||||||
_gitstatus_clear$fsuf
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
gitstatus_stop$fsuf $name
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
while [[ $buf != *$'\x1e' ]]; do
|
|
||||||
if ! sysread -i $resp_fd 'buf[$#buf+1]'; then
|
|
||||||
gitstatus_stop$fsuf $name
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
local s
|
|
||||||
for s in ${(ps:\x1e:)buf}; do
|
|
||||||
local -a resp=("${(@ps:\x1f:)s}")
|
|
||||||
if (( resp[2] )); then
|
|
||||||
if [[ $resp[1] == $req_id' '* ]]; then
|
|
||||||
typeset -g VCS_STATUS_RESULT=ok-sync
|
|
||||||
else
|
|
||||||
typeset -g VCS_STATUS_RESULT=ok-async
|
|
||||||
fi
|
|
||||||
for VCS_STATUS_WORKDIR \
|
|
||||||
VCS_STATUS_COMMIT \
|
|
||||||
VCS_STATUS_LOCAL_BRANCH \
|
|
||||||
VCS_STATUS_REMOTE_BRANCH \
|
|
||||||
VCS_STATUS_REMOTE_NAME \
|
|
||||||
VCS_STATUS_REMOTE_URL \
|
|
||||||
VCS_STATUS_ACTION \
|
|
||||||
VCS_STATUS_INDEX_SIZE \
|
|
||||||
VCS_STATUS_NUM_STAGED \
|
|
||||||
VCS_STATUS_NUM_UNSTAGED \
|
|
||||||
VCS_STATUS_NUM_CONFLICTED \
|
|
||||||
VCS_STATUS_NUM_UNTRACKED \
|
|
||||||
VCS_STATUS_COMMITS_AHEAD \
|
|
||||||
VCS_STATUS_COMMITS_BEHIND \
|
|
||||||
VCS_STATUS_STASHES \
|
|
||||||
VCS_STATUS_TAG \
|
|
||||||
VCS_STATUS_NUM_UNSTAGED_DELETED \
|
|
||||||
VCS_STATUS_NUM_STAGED_NEW \
|
|
||||||
VCS_STATUS_NUM_STAGED_DELETED \
|
|
||||||
VCS_STATUS_PUSH_REMOTE_NAME \
|
|
||||||
VCS_STATUS_PUSH_REMOTE_URL \
|
|
||||||
VCS_STATUS_PUSH_COMMITS_AHEAD \
|
|
||||||
VCS_STATUS_PUSH_COMMITS_BEHIND \
|
|
||||||
VCS_STATUS_NUM_SKIP_WORKTREE \
|
|
||||||
VCS_STATUS_NUM_ASSUME_UNCHANGED \
|
|
||||||
VCS_STATUS_COMMIT_ENCODING \
|
|
||||||
VCS_STATUS_COMMIT_SUMMARY in "${(@)resp[3,29]}"; do
|
|
||||||
done
|
|
||||||
typeset -gi VCS_STATUS_{INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED}
|
|
||||||
typeset -gi VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0))
|
|
||||||
if (( dirty_max_index_size >= 0 && VCS_STATUS_INDEX_SIZE > dirty_max_index_size )); then
|
|
||||||
typeset -gi \
|
|
||||||
VCS_STATUS_HAS_UNSTAGED=-1 \
|
|
||||||
VCS_STATUS_HAS_CONFLICTED=-1 \
|
|
||||||
VCS_STATUS_HAS_UNTRACKED=-1
|
|
||||||
else
|
|
||||||
typeset -gi \
|
|
||||||
VCS_STATUS_HAS_UNSTAGED=$((VCS_STATUS_NUM_UNSTAGED > 0)) \
|
|
||||||
VCS_STATUS_HAS_CONFLICTED=$((VCS_STATUS_NUM_CONFLICTED > 0)) \
|
|
||||||
VCS_STATUS_HAS_UNTRACKED=$((VCS_STATUS_NUM_UNTRACKED > 0))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ $resp[1] == $req_id' '* ]]; then
|
|
||||||
typeset -g VCS_STATUS_RESULT=norepo-sync
|
|
||||||
else
|
|
||||||
typeset -g VCS_STATUS_RESULT=norepo-async
|
|
||||||
fi
|
|
||||||
_gitstatus_clear$fsuf
|
|
||||||
fi
|
|
||||||
(( --_GITSTATUS_NUM_INFLIGHT_$name ))
|
|
||||||
[[ $VCS_STATUS_RESULT == *-async ]] && emulate zsh -c "${resp[1]#* }"
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function _gitstatus_daemon"${1:-}"() {
|
|
||||||
local -i pipe_fd
|
|
||||||
exec 0<&- {pipe_fd}>&1 1>>$daemon_log 2>&1 || return
|
|
||||||
local pgid=$sysparams[pid]
|
|
||||||
[[ $pgid == <1-> ]] || return
|
|
||||||
builtin cd -q / || return
|
|
||||||
|
|
||||||
{
|
|
||||||
{
|
|
||||||
trap '' PIPE
|
|
||||||
|
|
||||||
local uname_sm
|
|
||||||
uname_sm="${${(L)$(command uname -sm)}//ı/i}" || return
|
|
||||||
[[ $uname_sm == [^' ']##' '[^' ']## ]] || return
|
|
||||||
local uname_s=${uname_sm% *}
|
|
||||||
local uname_m=${uname_sm#* }
|
|
||||||
|
|
||||||
if [[ $GITSTATUS_NUM_THREADS == <1-> ]]; then
|
|
||||||
args+=(-t $GITSTATUS_NUM_THREADS)
|
|
||||||
else
|
|
||||||
local cpus
|
|
||||||
if (( ! $+commands[sysctl] )) || [[ $uname_s == linux ]] ||
|
|
||||||
! cpus="$(command sysctl -n hw.ncpu)"; then
|
|
||||||
if (( ! $+commands[getconf] )) || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
|
|
||||||
cpus=8
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
args+=(-t $((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16)))
|
|
||||||
fi
|
|
||||||
|
|
||||||
command mkfifo -- $file_prefix.fifo || return
|
|
||||||
print -rnu $pipe_fd -- ${(l:20:)pgid} || return
|
|
||||||
exec <$file_prefix.fifo || return
|
|
||||||
zf_rm -- $file_prefix.fifo || return
|
|
||||||
|
|
||||||
local _gitstatus_zsh_daemon _gitstatus_zsh_version _gitstatus_zsh_downloaded
|
|
||||||
|
|
||||||
function _gitstatus_set_daemon$fsuf() {
|
|
||||||
_gitstatus_zsh_daemon="$1"
|
|
||||||
_gitstatus_zsh_version="$2"
|
|
||||||
_gitstatus_zsh_downloaded="$3"
|
|
||||||
}
|
|
||||||
|
|
||||||
local gitstatus_plugin_dir_var=_gitstatus_plugin_dir$fsuf
|
|
||||||
local gitstatus_plugin_dir=${(P)gitstatus_plugin_dir_var}
|
|
||||||
builtin set -- -d $gitstatus_plugin_dir -s $uname_s -m $uname_m \
|
|
||||||
-p "printf '\\001' >&$pipe_fd" -e $pipe_fd -- _gitstatus_set_daemon$fsuf
|
|
||||||
[[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || builtin set -- -n "$@"
|
|
||||||
builtin source $gitstatus_plugin_dir/install || return
|
|
||||||
[[ -n $_gitstatus_zsh_daemon ]] || return
|
|
||||||
[[ -n $_gitstatus_zsh_version ]] || return
|
|
||||||
[[ $_gitstatus_zsh_downloaded == [01] ]] || return
|
|
||||||
|
|
||||||
if (( UID == EUID )); then
|
|
||||||
local home=~
|
|
||||||
else
|
|
||||||
local user
|
|
||||||
user="$(command id -un)" || return
|
|
||||||
local home=${userdirs[$user]}
|
|
||||||
[[ -n $home ]] || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -x $_gitstatus_zsh_daemon ]]; then
|
|
||||||
HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
|
|
||||||
local -i ret=$?
|
|
||||||
[[ $ret == (0|129|130|131|137|141|143|159) ]] && return ret
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( ! _gitstatus_zsh_downloaded )) || return
|
|
||||||
[[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || return
|
|
||||||
[[ $_gitstatus_zsh_daemon == \
|
|
||||||
${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}/* ]] || return
|
|
||||||
|
|
||||||
builtin set -- -f "$@"
|
|
||||||
_gitstatus_zsh_daemon=
|
|
||||||
_gitstatus_zsh_version=
|
|
||||||
_gitstatus_zsh_downloaded=
|
|
||||||
builtin source $gitstatus_plugin_dir/install || return
|
|
||||||
[[ -n $_gitstatus_zsh_daemon ]] || return
|
|
||||||
[[ -n $_gitstatus_zsh_version ]] || return
|
|
||||||
[[ $_gitstatus_zsh_downloaded == 1 ]] || return
|
|
||||||
|
|
||||||
HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
|
|
||||||
} always {
|
|
||||||
local -i ret=$?
|
|
||||||
zf_rm -f -- $file_prefix.lock $file_prefix.fifo
|
|
||||||
kill -- -$pgid
|
|
||||||
}
|
|
||||||
} &!
|
|
||||||
|
|
||||||
(( lock_fd == -1 )) && return
|
|
||||||
|
|
||||||
{
|
|
||||||
if zsystem flock -- $file_prefix.lock && command sleep 5 && [[ -e $file_prefix.lock ]]; then
|
|
||||||
zf_rm -f -- $file_prefix.lock $file_prefix.fifo
|
|
||||||
kill -- -$pgid
|
|
||||||
fi
|
|
||||||
} &!
|
|
||||||
}
|
|
||||||
|
|
||||||
# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd is already running.
|
|
||||||
#
|
|
||||||
# Usage: gitstatus_start [OPTION]... NAME
|
|
||||||
#
|
|
||||||
# -t FLOAT Fail the self-check on initialization if not getting a response from gitstatusd for
|
|
||||||
# this this many seconds. Defaults to 5.
|
|
||||||
#
|
|
||||||
# -s INT Report at most this many staged changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -u INT Report at most this many unstaged changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -c INT Report at most this many conflicted changes; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -d INT Report at most this many untracked files; negative value means infinity.
|
|
||||||
# Defaults to 1.
|
|
||||||
#
|
|
||||||
# -m INT Report -1 unstaged, untracked and conflicted if there are more than this many
|
|
||||||
# files in the index. Negative value means infinity. Defaults to -1.
|
|
||||||
#
|
|
||||||
# -e Count files within untracked directories like `git status --untracked-files`.
|
|
||||||
#
|
|
||||||
# -U Unless this option is specified, report zero untracked files for repositories
|
|
||||||
# with status.showUntrackedFiles = false.
|
|
||||||
#
|
|
||||||
# -W Unless this option is specified, report zero untracked files for repositories
|
|
||||||
# with bash.showUntrackedFiles = false.
|
|
||||||
#
|
|
||||||
# -D Unless this option is specified, report zero staged, unstaged and conflicted
|
|
||||||
# changes for repositories with bash.showDirtyState = false.
|
|
||||||
function gitstatus_start"${1:-}"() {
|
|
||||||
emulate -L zsh -o no_aliases -o no_bg_nice -o extended_glob -o typeset_silent || return
|
|
||||||
print -rnu2 || return
|
|
||||||
|
|
||||||
local fsuf=${${(%):-%N}#gitstatus_start}
|
|
||||||
|
|
||||||
local opt OPTARG
|
|
||||||
local -i OPTIND
|
|
||||||
local -F timeout=5
|
|
||||||
local -i async=0
|
|
||||||
local -a args=()
|
|
||||||
local -i dirty_max_index_size=-1
|
|
||||||
|
|
||||||
while getopts ":t:s:u:c:d:m:eaUWD" opt; do
|
|
||||||
case $opt in
|
|
||||||
a) async=1;;
|
|
||||||
+a) async=0;;
|
|
||||||
t)
|
|
||||||
if [[ $OPTARG != (|+)<->(|.<->)(|[eE](|-|+)<->) ]] || (( ${timeout::=OPTARG} <= 0 )); then
|
|
||||||
print -ru2 -- "gitstatus_start: invalid -t argument: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
s|u|c|d|m)
|
|
||||||
if [[ $OPTARG != (|-|+)<-> ]]; then
|
|
||||||
print -ru2 -- "gitstatus_start: invalid -$opt argument: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
args+=(-$opt $OPTARG)
|
|
||||||
[[ $opt == m ]] && dirty_max_index_size=OPTARG
|
|
||||||
;;
|
|
||||||
e|U|W|D) args+=-$opt;;
|
|
||||||
+(e|U|W|D)) args=(${(@)args:#-$opt});;
|
|
||||||
\?) print -ru2 -- "gitstatus_start: invalid option: $OPTARG" ; return 1;;
|
|
||||||
:) print -ru2 -- "gitstatus_start: missing required argument: $OPTARG"; return 1;;
|
|
||||||
*) print -ru2 -- "gitstatus_start: invalid option: $opt" ; return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( OPTIND != ARGC )); then
|
|
||||||
print -ru2 -- "gitstatus_start: exactly one positional argument is required"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local name=$*[OPTIND]
|
|
||||||
if [[ $name != [[:IDENT:]]## ]]; then
|
|
||||||
print -ru2 -- "gitstatus_start: invalid positional argument: $name"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local -i lock_fd resp_fd stderr_fd
|
|
||||||
local file_prefix xtrace=/dev/null daemon_log=/dev/null culprit
|
|
||||||
|
|
||||||
{
|
|
||||||
if (( _GITSTATUS_STATE_$name )); then
|
|
||||||
(( async )) && return
|
|
||||||
(( _GITSTATUS_STATE_$name == 2 )) && return
|
|
||||||
lock_fd=_GITSTATUS_LOCK_FD_$name
|
|
||||||
resp_fd=_GITSTATUS_RESP_FD_$name
|
|
||||||
xtrace=${(P)${:-GITSTATUS_XTRACE_$name}}
|
|
||||||
daemon_log=${(P)${:-GITSTATUS_DAEMON_LOG_$name}}
|
|
||||||
file_prefix=${(P)${:-_GITSTATUS_FILE_PREFIX_$name}}
|
|
||||||
else
|
|
||||||
typeset -gi _GITSTATUS_START_COUNTER
|
|
||||||
local log_level=$GITSTATUS_LOG_LEVEL
|
|
||||||
if [[ -n "$TMPDIR" && ( ( -d "$TMPDIR" && -w "$TMPDIR" ) || ! ( -d /tmp && -w /tmp ) ) ]]; then
|
|
||||||
local tmpdir=$TMPDIR
|
|
||||||
else
|
|
||||||
local tmpdir=/tmp
|
|
||||||
fi
|
|
||||||
local file_prefix=${tmpdir:A}/gitstatus.$name.$EUID
|
|
||||||
file_prefix+=.$sysparams[pid].$EPOCHSECONDS.$((++_GITSTATUS_START_COUNTER))
|
|
||||||
(( GITSTATUS_ENABLE_LOGGING )) && : ${log_level:=INFO}
|
|
||||||
if [[ -n $log_level ]]; then
|
|
||||||
xtrace=$file_prefix.xtrace.log
|
|
||||||
daemon_log=$file_prefix.daemon.log
|
|
||||||
fi
|
|
||||||
args+=(-v ${log_level:-FATAL})
|
|
||||||
typeset -g GITSTATUS_XTRACE_$name=$xtrace
|
|
||||||
typeset -g GITSTATUS_DAEMON_LOG_$name=$daemon_log
|
|
||||||
typeset -g _GITSTATUS_FILE_PREFIX_$name=$file_prefix
|
|
||||||
typeset -gi _GITSTATUS_CLIENT_PID_$name="sysparams[pid]"
|
|
||||||
typeset -gi _GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name=dirty_max_index_size
|
|
||||||
fi
|
|
||||||
|
|
||||||
() {
|
|
||||||
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
|
|
||||||
exec {stderr_fd}>&2 || return
|
|
||||||
exec 2>>$xtrace || return
|
|
||||||
setopt xtrace
|
|
||||||
fi
|
|
||||||
|
|
||||||
setopt monitor || return
|
|
||||||
|
|
||||||
if (( ! _GITSTATUS_STATE_$name )); then
|
|
||||||
if [[ -r /proc/version && "$(</proc/version)" == *Microsoft* ]]; then
|
|
||||||
lock_fd=-1
|
|
||||||
else
|
|
||||||
print -rn >$file_prefix.lock || return
|
|
||||||
zsystem flock -f lock_fd $file_prefix.lock || return
|
|
||||||
[[ $lock_fd == <1-> ]] || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
typeset -gi _GITSTATUS_LOCK_FD_$name=lock_fd
|
|
||||||
|
|
||||||
if [[ $OSTYPE == cygwin* && -d /proc/self/fd ]]; then
|
|
||||||
# Work around bugs in Cygwin 32-bit.
|
|
||||||
#
|
|
||||||
# This hangs:
|
|
||||||
#
|
|
||||||
# emulate -L zsh
|
|
||||||
# () { exec {fd}< $1 } <(:)
|
|
||||||
# =true # hangs here
|
|
||||||
#
|
|
||||||
# This hangs:
|
|
||||||
#
|
|
||||||
# sysopen -r -u fd <(:)
|
|
||||||
local -i fd
|
|
||||||
exec {fd}< <(_gitstatus_daemon$fsuf) || return
|
|
||||||
{
|
|
||||||
[[ -r /proc/self/fd/$fd ]] || return
|
|
||||||
sysopen -r -o cloexec -u resp_fd /proc/self/fd/$fd || return
|
|
||||||
} always {
|
|
||||||
exec {fd} >&- || return
|
|
||||||
}
|
|
||||||
else
|
|
||||||
sysopen -r -o cloexec -u resp_fd <(_gitstatus_daemon$fsuf) || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
typeset -gi GITSTATUS_DAEMON_PID_$name="${sysparams[procsubstpid]:--1}"
|
|
||||||
|
|
||||||
[[ $resp_fd == <1-> ]] || return
|
|
||||||
typeset -gi _GITSTATUS_RESP_FD_$name=resp_fd
|
|
||||||
typeset -gi _GITSTATUS_STATE_$name=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( ! async )); then
|
|
||||||
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
|
|
||||||
|
|
||||||
local pgid
|
|
||||||
while (( $#pgid < 20 )); do
|
|
||||||
[[ -t $resp_fd ]]
|
|
||||||
sysread -s $((20 - $#pgid)) -t $timeout -i $resp_fd 'pgid[$#pgid+1]' || return
|
|
||||||
done
|
|
||||||
[[ $pgid == ' '#<1-> ]] || return
|
|
||||||
typeset -gi GITSTATUS_DAEMON_PID_$name=pgid
|
|
||||||
|
|
||||||
sysopen -w -o cloexec -u req_fd -- $file_prefix.fifo || return
|
|
||||||
[[ $req_fd == <1-> ]] || return
|
|
||||||
typeset -gi _GITSTATUS_REQ_FD_$name=req_fd
|
|
||||||
|
|
||||||
print -nru $req_fd -- $'}hello\x1f\x1e' || return
|
|
||||||
local expected=$'}hello\x1f0\x1e' actual
|
|
||||||
if (( $+functions[p10k] )) && [[ ! -t 1 && ! -t 0 ]]; then
|
|
||||||
local -F deadline='EPOCHREALTIME + 4'
|
|
||||||
else
|
|
||||||
local -F deadline='1'
|
|
||||||
fi
|
|
||||||
while true; do
|
|
||||||
[[ -t $resp_fd ]]
|
|
||||||
sysread -s 1 -t $timeout -i $resp_fd actual || return
|
|
||||||
[[ $expected == $actual* ]] && break
|
|
||||||
if [[ $actual != $'\1' ]]; then
|
|
||||||
[[ -t $resp_fd ]]
|
|
||||||
while sysread -t $timeout -i $resp_fd 'actual[$#actual+1]'; do
|
|
||||||
[[ -t $resp_fd ]]
|
|
||||||
done
|
|
||||||
culprit=$actual
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
(( EPOCHREALTIME < deadline )) && continue
|
|
||||||
if (( deadline > 0 )); then
|
|
||||||
deadline=0
|
|
||||||
if (( stderr_fd )); then
|
|
||||||
unsetopt xtrace
|
|
||||||
exec 2>&$stderr_fd {stderr_fd}>&-
|
|
||||||
stderr_fd=0
|
|
||||||
fi
|
|
||||||
if (( $+functions[p10k] )); then
|
|
||||||
p10k clear-instant-prompt || return
|
|
||||||
fi
|
|
||||||
if [[ $name == POWERLEVEL9K ]]; then
|
|
||||||
local label=powerlevel10k
|
|
||||||
else
|
|
||||||
local label=gitstatus
|
|
||||||
fi
|
|
||||||
if [[ -t 2 ]]; then
|
|
||||||
local spinner=($'\b%3F-%f' $'\b%3F\\%f' $'\b%3F|%f' $'\b%3F/%f')
|
|
||||||
print -Prnu2 -- "[%3F$label%f] fetching %2Fgitstatusd%f .. "
|
|
||||||
else
|
|
||||||
local spinner=('.')
|
|
||||||
print -rnu2 -- "[$label] fetching gitstatusd .."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
print -Prnu2 -- $spinner[1]
|
|
||||||
spinner=($spinner[2,-1] $spinner[1])
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( deadline == 0 )); then
|
|
||||||
if [[ -t 2 ]]; then
|
|
||||||
print -Pru2 -- $'\b[%2Fok%f]'
|
|
||||||
else
|
|
||||||
print -ru2 -- ' [ok]'
|
|
||||||
fi
|
|
||||||
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
|
|
||||||
exec {stderr_fd}>&2 || return
|
|
||||||
exec 2>>$xtrace || return
|
|
||||||
setopt xtrace
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
while (( $#actual < $#expected )); do
|
|
||||||
[[ -t $resp_fd ]]
|
|
||||||
sysread -s $(($#expected - $#actual)) -t $timeout -i $resp_fd 'actual[$#actual+1]' || return
|
|
||||||
done
|
|
||||||
[[ $actual == $expected ]] || return
|
|
||||||
|
|
||||||
function _gitstatus_process_response_$name-$fsuf() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
local pair=${${(%):-%N}#_gitstatus_process_response_}
|
|
||||||
local name=${pair%%-*}
|
|
||||||
local fsuf=${pair#*-}
|
|
||||||
[[ $name == POWERLEVEL9K && $fsuf == _p9k_ ]] && eval $__p9k_intro_base
|
|
||||||
if (( ARGC == 1 )); then
|
|
||||||
_gitstatus_process_response$fsuf $name 0 ''
|
|
||||||
else
|
|
||||||
gitstatus_stop$fsuf $name
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
if ! zle -F $resp_fd _gitstatus_process_response_$name-$fsuf; then
|
|
||||||
unfunction _gitstatus_process_response_$name-$fsuf
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
function _gitstatus_cleanup_$name-$fsuf() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
local pair=${${(%):-%N}#_gitstatus_cleanup_}
|
|
||||||
local name=${pair%%-*}
|
|
||||||
local fsuf=${pair#*-}
|
|
||||||
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
|
|
||||||
gitstatus_stop$fsuf $name
|
|
||||||
}
|
|
||||||
if ! add-zsh-hook zshexit _gitstatus_cleanup_$name-$fsuf; then
|
|
||||||
unfunction _gitstatus_cleanup_$name-$fsuf
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( lock_fd != -1 )); then
|
|
||||||
zf_rm -- $file_prefix.lock || return
|
|
||||||
zsystem flock -u $lock_fd || return
|
|
||||||
fi
|
|
||||||
unset _GITSTATUS_LOCK_FD_$name
|
|
||||||
|
|
||||||
typeset -gi _GITSTATUS_STATE_$name=2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
} always {
|
|
||||||
local -i err=$?
|
|
||||||
(( stderr_fd )) && exec 2>&$stderr_fd {stderr_fd}>&-
|
|
||||||
(( err == 0 )) && return
|
|
||||||
|
|
||||||
gitstatus_stop$fsuf $name
|
|
||||||
|
|
||||||
setopt prompt_percent no_prompt_subst no_prompt_bang
|
|
||||||
(( $+functions[p10k] )) && p10k clear-instant-prompt
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.'
|
|
||||||
print -ru2 -- ''
|
|
||||||
if [[ -n $culprit ]]; then
|
|
||||||
print -ru2 -- $culprit
|
|
||||||
return err
|
|
||||||
fi
|
|
||||||
if [[ -s $xtrace ]]; then
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- " Zsh log (%U${xtrace//\%/%%}%u):"
|
|
||||||
print -Pru2 -- '%F{yellow}'
|
|
||||||
print -lru2 -- "${(@)${(@f)$(<$xtrace)}/#/ }"
|
|
||||||
print -Pnru2 -- '%f'
|
|
||||||
fi
|
|
||||||
if [[ -s $daemon_log ]]; then
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- " Daemon log (%U${daemon_log//\%/%%}%u):"
|
|
||||||
print -Pru2 -- '%F{yellow}'
|
|
||||||
print -lru2 -- "${(@)${(@f)$(<$daemon_log)}/#/ }"
|
|
||||||
print -Pnru2 -- '%f'
|
|
||||||
fi
|
|
||||||
if [[ $GITSTATUS_LOG_LEVEL == DEBUG ]]; then
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -ru2 -- ' System information:'
|
|
||||||
print -Pru2 -- '%F{yellow}'
|
|
||||||
print -ru2 -- " zsh: $ZSH_VERSION"
|
|
||||||
print -ru2 -- " uname -a: $(command uname -a)"
|
|
||||||
print -Pru2 -- '%f'
|
|
||||||
print -ru2 -- ' If you need help, open an issue and attach this whole error message to it:'
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- ' %Uhttps://github.com/romkatv/gitstatus/issues/new%u'
|
|
||||||
else
|
|
||||||
print -ru2 -- ''
|
|
||||||
local home=~
|
|
||||||
local zshrc=${${${(q)${ZDOTDIR:-~}}/#${(q)home}/'~'}//\%/%%}/.zshrc
|
|
||||||
print -Pru2 -- " Add the following parameter to %U$zshrc%u for extra diagnostics on error:"
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- ' %BGITSTATUS_LOG_LEVEL=DEBUG%b'
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -ru2 -- ' Restart Zsh to retry gitstatus initialization:'
|
|
||||||
print -ru2 -- ''
|
|
||||||
print -Pru2 -- ' %F{green}%Uexec%u zsh%f'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stops gitstatusd if it's running.
|
|
||||||
#
|
|
||||||
# Usage: gitstatus_stop NAME.
|
|
||||||
function gitstatus_stop"${1:-}"() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
|
|
||||||
local fsuf=${${(%):-%N}#gitstatus_stop}
|
|
||||||
|
|
||||||
if (( ARGC != 1 )); then
|
|
||||||
print -ru2 -- "gitstatus_stop: exactly one positional argument is required"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local name=$1
|
|
||||||
if [[ $name != [[:IDENT:]]## ]]; then
|
|
||||||
print -ru2 -- "gitstatus_stop: invalid positional argument: $name"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local state_var=_GITSTATUS_STATE_$name
|
|
||||||
local req_fd_var=_GITSTATUS_REQ_FD_$name
|
|
||||||
local resp_fd_var=_GITSTATUS_RESP_FD_$name
|
|
||||||
local lock_fd_var=_GITSTATUS_LOCK_FD_$name
|
|
||||||
local client_pid_var=_GITSTATUS_CLIENT_PID_$name
|
|
||||||
local daemon_pid_var=GITSTATUS_DAEMON_PID_$name
|
|
||||||
local inflight_var=_GITSTATUS_NUM_INFLIGHT_$name
|
|
||||||
local file_prefix_var=_GITSTATUS_FILE_PREFIX_$name
|
|
||||||
local dirty_max_index_size_var=_GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name
|
|
||||||
|
|
||||||
local req_fd=${(P)req_fd_var}
|
|
||||||
local resp_fd=${(P)resp_fd_var}
|
|
||||||
local lock_fd=${(P)lock_fd_var}
|
|
||||||
local daemon_pid=${(P)daemon_pid_var}
|
|
||||||
local file_prefix=${(P)file_prefix_var}
|
|
||||||
|
|
||||||
local cleanup=_gitstatus_cleanup_$name-$fsuf
|
|
||||||
local process=_gitstatus_process_response_$name-$fsuf
|
|
||||||
|
|
||||||
if (( $+functions[$cleanup] )); then
|
|
||||||
add-zsh-hook -d zshexit $cleanup
|
|
||||||
unfunction -- $cleanup
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( $+functions[$process] )); then
|
|
||||||
[[ -n $resp_fd ]] && zle -F $resp_fd
|
|
||||||
unfunction -- $process
|
|
||||||
fi
|
|
||||||
|
|
||||||
[[ $daemon_pid == <1-> ]] && kill -- -$daemon_pid 2>/dev/null
|
|
||||||
[[ $file_prefix == /* ]] && zf_rm -f -- $file_prefix.lock $file_prefix.fifo
|
|
||||||
[[ $lock_fd == <1-> ]] && zsystem flock -u $lock_fd
|
|
||||||
[[ $req_fd == <1-> ]] && exec {req_fd}>&-
|
|
||||||
[[ $resp_fd == <1-> ]] && exec {resp_fd}>&-
|
|
||||||
|
|
||||||
unset $state_var $req_fd_var $lock_fd_var $resp_fd_var $client_pid_var $daemon_pid_var
|
|
||||||
unset $inflight_var $file_prefix_var $dirty_max_index_size_var
|
|
||||||
|
|
||||||
unset VCS_STATUS_RESULT
|
|
||||||
_gitstatus_clear$fsuf
|
|
||||||
}
|
|
||||||
|
|
||||||
# Usage: gitstatus_check NAME.
|
|
||||||
#
|
|
||||||
# Returns 0 if and only if `gitstatus_start NAME` has succeeded previously.
|
|
||||||
# If it returns non-zero, gitstatus_query NAME is guaranteed to return non-zero.
|
|
||||||
function gitstatus_check"${1:-}"() {
|
|
||||||
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
|
|
||||||
|
|
||||||
local fsuf=${${(%):-%N}#gitstatus_check}
|
|
||||||
|
|
||||||
if (( ARGC != 1 )); then
|
|
||||||
print -ru2 -- "gitstatus_check: exactly one positional argument is required"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local name=$1
|
|
||||||
if [[ $name != [[:IDENT:]]## ]]; then
|
|
||||||
print -ru2 -- "gitstatus_check: invalid positional argument: $name"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( _GITSTATUS_STATE_$name == 2 ))
|
|
||||||
}
|
|
||||||
|
|
||||||
(( ${#_gitstatus_opts} )) && setopt ${_gitstatus_opts[@]}
|
|
||||||
'builtin' 'unset' '_gitstatus_opts'
|
|
|
@ -1,104 +0,0 @@
|
||||||
# Simple Bash prompt with Git status.
|
|
||||||
|
|
||||||
# Source gitstatus.plugin.sh from $GITSTATUS_DIR or from the same directory
|
|
||||||
# in which the current script resides if the variable isn't set.
|
|
||||||
if [[ -n "${GITSTATUS_DIR:-}" ]]; then
|
|
||||||
source "$GITSTATUS_DIR" || return
|
|
||||||
elif [[ "${BASH_SOURCE[0]}" == */* ]]; then
|
|
||||||
source "${BASH_SOURCE[0]%/*}/gitstatus.plugin.sh" || return
|
|
||||||
else
|
|
||||||
source gitstatus.plugin.sh || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sets GITSTATUS_PROMPT to reflect the state of the current git repository.
|
|
||||||
# The value is empty if not in a git repository. Forwards all arguments to
|
|
||||||
# gitstatus_query.
|
|
||||||
#
|
|
||||||
# Example value of GITSTATUS_PROMPT: master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42
|
|
||||||
#
|
|
||||||
# master current branch
|
|
||||||
# ⇣42 local branch is 42 commits behind the remote
|
|
||||||
# ⇡42 local branch is 42 commits ahead of the remote
|
|
||||||
# ⇠42 local branch is 42 commits behind the push remote
|
|
||||||
# ⇢42 local branch is 42 commits ahead of the push remote
|
|
||||||
# *42 42 stashes
|
|
||||||
# merge merge in progress
|
|
||||||
# ~42 42 merge conflicts
|
|
||||||
# +42 42 staged changes
|
|
||||||
# !42 42 unstaged changes
|
|
||||||
# ?42 42 untracked files
|
|
||||||
function gitstatus_prompt_update() {
|
|
||||||
GITSTATUS_PROMPT=""
|
|
||||||
|
|
||||||
gitstatus_query "$@" || return 1 # error
|
|
||||||
[[ "$VCS_STATUS_RESULT" == ok-sync ]] || return 0 # not a git repo
|
|
||||||
|
|
||||||
local reset=$'\001\e[0m\002' # no color
|
|
||||||
local clean=$'\001\e[38;5;076m\002' # green foreground
|
|
||||||
local untracked=$'\001\e[38;5;014m\002' # teal foreground
|
|
||||||
local modified=$'\001\e[38;5;011m\002' # yellow foreground
|
|
||||||
local conflicted=$'\001\e[38;5;196m\002' # red foreground
|
|
||||||
|
|
||||||
local p
|
|
||||||
|
|
||||||
local where # branch name, tag or commit
|
|
||||||
if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
|
|
||||||
where="$VCS_STATUS_LOCAL_BRANCH"
|
|
||||||
elif [[ -n "$VCS_STATUS_TAG" ]]; then
|
|
||||||
p+="${reset}#"
|
|
||||||
where="$VCS_STATUS_TAG"
|
|
||||||
else
|
|
||||||
p+="${reset}@"
|
|
||||||
where="${VCS_STATUS_COMMIT:0:8}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( ${#where} > 32 )) && where="${where:0:12}…${where: -12}" # truncate long branch names and tags
|
|
||||||
p+="${clean}${where}"
|
|
||||||
|
|
||||||
# ⇣42 if behind the remote.
|
|
||||||
(( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
|
|
||||||
# ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
|
|
||||||
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
|
|
||||||
(( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
|
|
||||||
# ⇠42 if behind the push remote.
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
|
|
||||||
# ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
|
|
||||||
# *42 if have stashes.
|
|
||||||
(( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
|
|
||||||
# 'merge' if the repo is in an unusual state.
|
|
||||||
[[ -n "$VCS_STATUS_ACTION" ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
|
|
||||||
# ~42 if have merge conflicts.
|
|
||||||
(( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
|
|
||||||
# +42 if have staged changes.
|
|
||||||
(( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
|
|
||||||
# !42 if have unstaged changes.
|
|
||||||
(( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
|
|
||||||
# ?42 if have untracked files. It's really a question mark, your font isn't broken.
|
|
||||||
(( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
|
|
||||||
|
|
||||||
GITSTATUS_PROMPT="${p}${reset}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start gitstatusd in the background.
|
|
||||||
gitstatus_stop && gitstatus_start -s -1 -u -1 -c -1 -d -1
|
|
||||||
|
|
||||||
# On every prompt, fetch git status and set GITSTATUS_PROMPT.
|
|
||||||
PROMPT_COMMAND=gitstatus_prompt_update
|
|
||||||
PROMPT_DIRTRIM=3
|
|
||||||
|
|
||||||
# Enable promptvars so that ${GITSTATUS_PROMPT} in PS1 is expanded.
|
|
||||||
shopt -s promptvars
|
|
||||||
|
|
||||||
# Customize prompt. Put $GITSTATUS_PROMPT in it reflect git status.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# user@host ~/projects/skynet master ⇡42
|
|
||||||
# $ █
|
|
||||||
PS1='\[\033[01;32m\]\u@\h\[\033[00m\] ' # green user@host
|
|
||||||
PS1+='\[\033[01;34m\]\w\[\033[00m\]' # blue current working directory
|
|
||||||
PS1+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status (requires promptvars option)
|
|
||||||
PS1+='\n\[\033[01;$((31+!$?))m\]\$\[\033[00m\] ' # green/red (success/error) $/# (normal/root)
|
|
||||||
PS1+='\[\e]0;\u@\h: \w\a\]' # terminal title: user@host: dir
|
|
|
@ -1,111 +0,0 @@
|
||||||
# Simple Zsh prompt with Git status.
|
|
||||||
|
|
||||||
# Source gitstatus.plugin.zsh from $GITSTATUS_DIR or from the same directory
|
|
||||||
# in which the current script resides if the variable isn't set.
|
|
||||||
source "${GITSTATUS_DIR:-${${(%):-%x}:h}}/gitstatus.plugin.zsh" || return
|
|
||||||
|
|
||||||
# Sets GITSTATUS_PROMPT to reflect the state of the current git repository. Empty if not
|
|
||||||
# in a git repository. In addition, sets GITSTATUS_PROMPT_LEN to the number of columns
|
|
||||||
# $GITSTATUS_PROMPT will occupy when printed.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# GITSTATUS_PROMPT='master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42'
|
|
||||||
# GITSTATUS_PROMPT_LEN=39
|
|
||||||
#
|
|
||||||
# master current branch
|
|
||||||
# ⇣42 local branch is 42 commits behind the remote
|
|
||||||
# ⇡42 local branch is 42 commits ahead of the remote
|
|
||||||
# ⇠42 local branch is 42 commits behind the push remote
|
|
||||||
# ⇢42 local branch is 42 commits ahead of the push remote
|
|
||||||
# *42 42 stashes
|
|
||||||
# merge merge in progress
|
|
||||||
# ~42 42 merge conflicts
|
|
||||||
# +42 42 staged changes
|
|
||||||
# !42 42 unstaged changes
|
|
||||||
# ?42 42 untracked files
|
|
||||||
function gitstatus_prompt_update() {
|
|
||||||
emulate -L zsh
|
|
||||||
typeset -g GITSTATUS_PROMPT=''
|
|
||||||
typeset -gi GITSTATUS_PROMPT_LEN=0
|
|
||||||
|
|
||||||
# Call gitstatus_query synchronously. Note that gitstatus_query can also be called
|
|
||||||
# asynchronously; see documentation in gitstatus.plugin.zsh.
|
|
||||||
gitstatus_query 'MY' || return 1 # error
|
|
||||||
[[ $VCS_STATUS_RESULT == 'ok-sync' ]] || return 0 # not a git repo
|
|
||||||
|
|
||||||
local clean='%76F' # green foreground
|
|
||||||
local modified='%178F' # yellow foreground
|
|
||||||
local untracked='%39F' # blue foreground
|
|
||||||
local conflicted='%196F' # red foreground
|
|
||||||
|
|
||||||
local p
|
|
||||||
|
|
||||||
local where # branch name, tag or commit
|
|
||||||
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
|
|
||||||
where=$VCS_STATUS_LOCAL_BRANCH
|
|
||||||
elif [[ -n $VCS_STATUS_TAG ]]; then
|
|
||||||
p+='%f#'
|
|
||||||
where=$VCS_STATUS_TAG
|
|
||||||
else
|
|
||||||
p+='%f@'
|
|
||||||
where=${VCS_STATUS_COMMIT[1,8]}
|
|
||||||
fi
|
|
||||||
|
|
||||||
(( $#where > 32 )) && where[13,-13]="…" # truncate long branch names and tags
|
|
||||||
p+="${clean}${where//\%/%%}" # escape %
|
|
||||||
|
|
||||||
# ⇣42 if behind the remote.
|
|
||||||
(( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
|
|
||||||
# ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
|
|
||||||
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
|
|
||||||
(( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
|
|
||||||
# ⇠42 if behind the push remote.
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
|
|
||||||
# ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
|
|
||||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
|
|
||||||
# *42 if have stashes.
|
|
||||||
(( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
|
|
||||||
# 'merge' if the repo is in an unusual state.
|
|
||||||
[[ -n $VCS_STATUS_ACTION ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
|
|
||||||
# ~42 if have merge conflicts.
|
|
||||||
(( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
|
|
||||||
# +42 if have staged changes.
|
|
||||||
(( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
|
|
||||||
# !42 if have unstaged changes.
|
|
||||||
(( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
|
|
||||||
# ?42 if have untracked files. It's really a question mark, your font isn't broken.
|
|
||||||
(( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
|
|
||||||
|
|
||||||
GITSTATUS_PROMPT="${p}%f"
|
|
||||||
|
|
||||||
# The length of GITSTATUS_PROMPT after removing %f and %F.
|
|
||||||
GITSTATUS_PROMPT_LEN="${(m)#${${GITSTATUS_PROMPT//\%\%/x}//\%(f|<->F)}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start gitstatusd instance with name "MY". The same name is passed to
|
|
||||||
# gitstatus_query in gitstatus_prompt_update. The flags with -1 as values
|
|
||||||
# enable staged, unstaged, conflicted and untracked counters.
|
|
||||||
gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
|
|
||||||
|
|
||||||
# On every prompt, fetch git status and set GITSTATUS_PROMPT.
|
|
||||||
autoload -Uz add-zsh-hook
|
|
||||||
add-zsh-hook precmd gitstatus_prompt_update
|
|
||||||
|
|
||||||
# Enable/disable the right prompt options.
|
|
||||||
setopt no_prompt_bang prompt_percent prompt_subst
|
|
||||||
|
|
||||||
# Customize prompt. Put $GITSTATUS_PROMPT in it to reflect git status.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# user@host ~/projects/skynet master ⇡42
|
|
||||||
# % █
|
|
||||||
#
|
|
||||||
# The current directory gets truncated from the left if the whole prompt doesn't fit on the line.
|
|
||||||
PROMPT='%70F%n@%m%f ' # green user@host
|
|
||||||
PROMPT+='%39F%$((-GITSTATUS_PROMPT_LEN-1))<…<%~%<<%f' # blue current working directory
|
|
||||||
PROMPT+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status
|
|
||||||
PROMPT+=$'\n' # new line
|
|
||||||
PROMPT+='%F{%(?.76.196)}%#%f ' # %/# (normal/root); green/red (ok/error)
|
|
|
@ -1,476 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# This script does not have a stable API.
|
|
||||||
|
|
||||||
_gitstatus_install_daemon_found() {
|
|
||||||
local installed="$1"
|
|
||||||
shift
|
|
||||||
[ $# = 0 ] || "$@" "$daemon" "$version" "$installed"
|
|
||||||
}
|
|
||||||
|
|
||||||
_gitstatus_install_main() {
|
|
||||||
if [ -n "${ZSH_VERSION:-}" ]; then
|
|
||||||
emulate -L sh -o no_unset
|
|
||||||
else
|
|
||||||
set -u
|
|
||||||
fi
|
|
||||||
|
|
||||||
local argv1="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= e=
|
|
||||||
local opt= OPTARG= OPTIND=1
|
|
||||||
|
|
||||||
while getopts ':s:m:d:p:e:fnh' opt "$@"; do
|
|
||||||
case "$opt" in
|
|
||||||
h)
|
|
||||||
command cat <<\END
|
|
||||||
Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-e ERRFD] [-f|-n] [-- CMD [ARG]...]
|
|
||||||
|
|
||||||
If positional arguments are specified, call this on success:
|
|
||||||
|
|
||||||
CMD [ARG]... DAEMON VERSION INSTALLED
|
|
||||||
|
|
||||||
DAEMON is path to gitstatusd. VERSION is a glob pattern for the
|
|
||||||
version this daemon should support; it's supposed to be passed as
|
|
||||||
-G to gitstatusd. INSTALLED is 1 if gitstatusd has just been
|
|
||||||
downloaded and 0 otherwise.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-s KERNEL use this instead of lowercase `uname -s`
|
|
||||||
-m ARCH use this instead of lowercase `uname -m`
|
|
||||||
-d DIR use this instead of `dirname "$0"`
|
|
||||||
-p CMD eval this every second while downloading gitstatusd
|
|
||||||
-e ERRFD write error messages to this file descriptor
|
|
||||||
-f download gitstatusd even if there is one locally
|
|
||||||
-n do not download gitstatusd (fail instead)
|
|
||||||
END
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
n)
|
|
||||||
if [ -n "$no_install" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
no_install=1
|
|
||||||
;;
|
|
||||||
f)
|
|
||||||
if [ -n "$no_check" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
no_check=1
|
|
||||||
;;
|
|
||||||
d)
|
|
||||||
if [ -n "$gitstatus_dir" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
gitstatus_dir="$OPTARG"
|
|
||||||
;;
|
|
||||||
p)
|
|
||||||
if [ -n "$dl_status" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
dl_status="$OPTARG"
|
|
||||||
;;
|
|
||||||
e)
|
|
||||||
if [ -n "$e" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
e="$OPTARG"
|
|
||||||
;;
|
|
||||||
m)
|
|
||||||
if [ -n "$uname_m" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
uname_m="$OPTARG"
|
|
||||||
;;
|
|
||||||
s)
|
|
||||||
if [ -n "$uname_s" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: duplicate option: -$opt"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$OPTARG" ]; then
|
|
||||||
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
uname_s="$OPTARG"
|
|
||||||
;;
|
|
||||||
\?) >&2 echo "[gitstatus] error: invalid option: -$OPTARG" ; return 1;;
|
|
||||||
:) >&2 echo "[gitstatus] error: missing required argument: -$OPTARG"; return 1;;
|
|
||||||
*) >&2 echo "[gitstatus] internal error: unhandled option: -$opt" ; return 1;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
shift "$((OPTIND - 1))"
|
|
||||||
|
|
||||||
: "${e:=2}"
|
|
||||||
: "${gitstatus_dir:=$argv1}"
|
|
||||||
|
|
||||||
if [ -n "$no_check" -a -n "$no_install" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: incompatible options: -f, -n"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$uname_s" ]; then
|
|
||||||
uname_s="$(command uname -s)" || return
|
|
||||||
uname_s="$(printf '%s' "$uname_s" | command tr '[A-Z]' '[a-z]')" || return
|
|
||||||
fi
|
|
||||||
if [ -z "$uname_m" ]; then
|
|
||||||
uname_m="$(command uname -m)" || return
|
|
||||||
uname_m="$(printf '%s' "$uname_m" | command tr '[A-Z]' '[a-z]')" || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local daemon="${GITSTATUS_DAEMON:-}"
|
|
||||||
local cache_dir="${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"
|
|
||||||
|
|
||||||
if [ -z "$no_check" ]; then
|
|
||||||
if [ -n "${daemon##/*}" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: GITSTATUS_DAEMON is not absolute path: $daemon"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$daemon" -a -e "$gitstatus_dir"/usrbin/gitstatusd ]; then
|
|
||||||
daemon="$gitstatus_dir"/usrbin/gitstatusd
|
|
||||||
fi
|
|
||||||
if [ -n "$daemon" ]; then
|
|
||||||
local gitstatus_version= libgit2_version=
|
|
||||||
if ! . "$gitstatus_dir"/build.info; then
|
|
||||||
>&2 echo "[gitstatus] internal error: failed to source build.info"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ -z "$gitstatus_version" ]; then
|
|
||||||
>&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
local version="$gitstatus_version"
|
|
||||||
_gitstatus_install_daemon_found 0 "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
line="${line###*}"
|
|
||||||
[ -n "$line" ] || continue
|
|
||||||
|
|
||||||
local uname_s_glob= uname_m_glob= file= version= sha256=
|
|
||||||
eval "$line" || return
|
|
||||||
|
|
||||||
if [ -z "$uname_s_glob" -o \
|
|
||||||
-z "$uname_m_glob" -o \
|
|
||||||
-z "$file" -o \
|
|
||||||
-z "$version" -o \
|
|
||||||
-z "$sha256" ]; then
|
|
||||||
>&2 echo "[gitstatus] internal error: invalid install.info line: $line"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$uname_s" in
|
|
||||||
$uname_s_glob) ;;
|
|
||||||
*) continue;;
|
|
||||||
esac
|
|
||||||
case "$uname_m" in
|
|
||||||
$uname_m_glob) ;;
|
|
||||||
*) continue;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Found a match. The while loop will terminate during this iteration.
|
|
||||||
|
|
||||||
if [ -z "$no_check" ]; then
|
|
||||||
# Check if a suitable gitstatusd already exists.
|
|
||||||
local daemon="$gitstatus_dir"/usrbin/"$file"
|
|
||||||
if [ ! -e "$daemon" ]; then
|
|
||||||
daemon="$cache_dir"/"$file"
|
|
||||||
[ -e "$daemon" ] || daemon=
|
|
||||||
fi
|
|
||||||
if [ -n "$daemon" ]; then
|
|
||||||
_gitstatus_install_daemon_found 0 "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# No suitable gitstatusd exists. Need to download.
|
|
||||||
|
|
||||||
if [ -n "$no_install" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: no gitstatusd found and installation is disabled"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local daemon="$cache_dir"/"$file"
|
|
||||||
|
|
||||||
if [ -n "${cache_dir##/*}" ]; then
|
|
||||||
>&2 echo "[gitstatus] error: GITSTATUS_CACHE_DIR is not absolute: $cache_dir"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ ! -d "$cache_dir" ] && ! mkdir -p -- "$cache_dir" || [ ! -w "$cache_dir" ]; then
|
|
||||||
local dir="$cache_dir"
|
|
||||||
while true; do
|
|
||||||
if [ -e "$dir" ]; then
|
|
||||||
if [ ! -d "$dir" ]; then
|
|
||||||
>&"$e" printf 'Not a directory: \033[4;31m%s\033[0m\n' "$dir"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Delete it, then restart your shell.\n'
|
|
||||||
elif [ ! -w "$dir" ]; then
|
|
||||||
>&"$e" printf 'Directory is not writable: \033[4;31m%s\033[0m\n' "$dir"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Make it writable, then restart your shell.\n'
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if [ "$dir" = / ] || [ "$dir" = . ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
dir="$(dirname -- "$dir")"
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${TMPDIR-}" -a '(' '(' -d "${TMPDIR-}" -a -w "${TMPDIR-}" ')' -o '!' '(' -d /tmp -a -w /tmp ')' ')' ]; then
|
|
||||||
local tmp="$TMPDIR"
|
|
||||||
else
|
|
||||||
local tmp=/tmp
|
|
||||||
fi
|
|
||||||
if ! command -v mktemp >/dev/null 2>&1 ||
|
|
||||||
! tmpdir="$(command mktemp -d "$tmp"/gitstatus-install.XXXXXXXXXX)"; then
|
|
||||||
tmpdir="$tmp/gitstatus-install.tmp.$$"
|
|
||||||
if ! mkdir -p -- "$tmpdir"; then
|
|
||||||
if [ "$tmp" = /tmp ]; then
|
|
||||||
local label='directory'
|
|
||||||
else
|
|
||||||
local label='directory (\033[1mTMPDIR\033[m)'
|
|
||||||
fi
|
|
||||||
if [ ! -e "$tmp" ]; then
|
|
||||||
>&"$e" printf 'Temporary '"$label"' does not exist: \033[4;31m%s\033[0m\n' "$tmp"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Create it, then restart your shell.\n'
|
|
||||||
elif [ ! -d "$tmp" ]; then
|
|
||||||
>&"$e" printf 'Not a '"$label"': \033[4;31m%s\033[0m\n' "$tmp"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Make it a directory, then restart your shell.\n'
|
|
||||||
elif [ ! -w "$tmp" ]; then
|
|
||||||
>&"$e" printf 'Temporary '"$label"' is not writable: \033[4;31m%s\033[0m\n' "$tmp"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Make it writable, then restart your shell.\n'
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
|
|
||||||
>&"$e" printf 'Please install \033[32mcurl\033[0m or \033[32mwget\033[0m, then restart your shell.\n'
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(
|
|
||||||
run_cmd() {
|
|
||||||
command -v "$1" >/dev/null 2>/dev/null || return 127
|
|
||||||
local trapped= pid die ret
|
|
||||||
trap 'trapped=1' $sig
|
|
||||||
# The only reason for suppressing stderr is that `curl -f` cannot be silenced:
|
|
||||||
# `-s` doesn't work despite what the docs say.
|
|
||||||
command "$@" 2>/dev/null &
|
|
||||||
ret="$?"
|
|
||||||
if [ "$ret" = 0 ]; then
|
|
||||||
pid="$!"
|
|
||||||
die="trap - $sig; kill -- $pid 2>/dev/null; wait -- $pid 2>/dev/null; exit 1"
|
|
||||||
trap "$die" $sig
|
|
||||||
[ -z "$trapped" ] || eval "$die"
|
|
||||||
wait -- "$pid" 2>/dev/null
|
|
||||||
ret="$?"
|
|
||||||
fi
|
|
||||||
trap - $sig
|
|
||||||
[ -z "$trapped" ] || exit
|
|
||||||
return "$ret"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_sha256() {
|
|
||||||
local data_file="$tmpdir"/"$1".tar.gz
|
|
||||||
local hash_file="$tmpdir"/"$1".tar.gz.sha256
|
|
||||||
local hash=
|
|
||||||
{
|
|
||||||
command -v shasum >/dev/null 2>/dev/null &&
|
|
||||||
run_cmd shasum -b -a 256 -- "$data_file" >"$hash_file" </dev/null &&
|
|
||||||
IFS= read -r hash <"$hash_file" &&
|
|
||||||
hash="${hash%% *}" &&
|
|
||||||
[ ${#hash} -eq 64 ]
|
|
||||||
} || {
|
|
||||||
command -v sha256sum >/dev/null 2>/dev/null &&
|
|
||||||
run_cmd sha256sum -b -- "$data_file" >"$hash_file" </dev/null &&
|
|
||||||
IFS= read -r hash <"$hash_file" &&
|
|
||||||
hash="${hash%% *}" &&
|
|
||||||
[ ${#hash} -eq 64 ]
|
|
||||||
} || {
|
|
||||||
# Note: sha256 can be from hashalot. It's incompatible.
|
|
||||||
# Thankfully, it produces shorter output.
|
|
||||||
command -v sha256 >/dev/null 2>/dev/null &&
|
|
||||||
run_cmd sha256 -- "$data_file" >"$hash_file" </dev/null &&
|
|
||||||
IFS= read -r hash <"$hash_file" &&
|
|
||||||
hash="${hash##* }" &&
|
|
||||||
[ ${#hash} -eq 64 ]
|
|
||||||
} || {
|
|
||||||
hash=
|
|
||||||
}
|
|
||||||
[ "$1" = 1 -a -z "$hash" -o "$hash" = "$sha256" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
local url1="https://github.com/romkatv/gitstatus/releases/download/$version/$file.tar.gz"
|
|
||||||
local url2="https://gitee.com/romkatv/gitstatus/raw/release-$version/release/$file.tar.gz"
|
|
||||||
local sig='INT QUIT TERM ILL PIPE'
|
|
||||||
|
|
||||||
fetch() {
|
|
||||||
if [ "$1" != 1 ] && command -v sleep >/dev/null 2>/dev/null; then
|
|
||||||
if ! run_cmd sleep "$1"; then
|
|
||||||
echo -n >"$tmpdir"/"$1".status
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
local cmd part url ret
|
|
||||||
for cmd in 'curl -kfsSL' 'wget -qO-' 'curl -q -kfsSL' 'wget --no-config -qO-'; do
|
|
||||||
part=0
|
|
||||||
while true; do
|
|
||||||
if [ "$part" = 2 ]; then
|
|
||||||
ret=1
|
|
||||||
break
|
|
||||||
elif [ "$part" = 0 ]; then
|
|
||||||
url="$2"
|
|
||||||
else
|
|
||||||
url="$2"."$part"
|
|
||||||
fi
|
|
||||||
run_cmd $cmd -- "$url" >>"$tmpdir"/"$1".tar.gz
|
|
||||||
ret="$?"
|
|
||||||
[ "$ret" = 0 ] || break
|
|
||||||
check_sha256 "$1" && break
|
|
||||||
part=$((part+1))
|
|
||||||
done
|
|
||||||
[ "$ret" = 0 ] && break
|
|
||||||
run_cmd rm -f -- "$tmpdir"/"$1".tar.gz && continue
|
|
||||||
ret="$?"
|
|
||||||
break
|
|
||||||
done
|
|
||||||
echo -n >"$tmpdir"/"$1".status
|
|
||||||
return "$ret"
|
|
||||||
}
|
|
||||||
|
|
||||||
local trapped=
|
|
||||||
trap 'trapped=1' $sig
|
|
||||||
fetch 1 "$url1" &
|
|
||||||
local pid1="$!"
|
|
||||||
fetch 2 "$url2" &
|
|
||||||
local pid2="$!"
|
|
||||||
|
|
||||||
local die="trap - $sig; kill -- $pid1 $pid2 2>/dev/null; wait -- $pid1 $pid2 2>/dev/null; exit 1"
|
|
||||||
trap "$die" $sig
|
|
||||||
[ -z "$trapped" ] || eval "$die"
|
|
||||||
|
|
||||||
local n=
|
|
||||||
while true; do
|
|
||||||
[ -z "$dl_status" ] || eval "$dl_status" || eval "$die"
|
|
||||||
if command -v sleep >/dev/null 2>/dev/null; then
|
|
||||||
command sleep 1
|
|
||||||
elif command -v true >/dev/null 2>/dev/null; then
|
|
||||||
command true
|
|
||||||
fi
|
|
||||||
if [ -n "$pid1" -a -e "$tmpdir"/1.status ]; then
|
|
||||||
wait -- "$pid1" 2>/dev/null
|
|
||||||
local ret="$?"
|
|
||||||
pid1=
|
|
||||||
if [ "$ret" = 0 ]; then
|
|
||||||
if [ -n "$pid2" ]; then
|
|
||||||
kill -- "$pid2" 2>/dev/null
|
|
||||||
wait -- "$pid2" 2>/dev/null
|
|
||||||
fi
|
|
||||||
n=1
|
|
||||||
break
|
|
||||||
elif [ -z "$pid2" ]; then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
die="trap - $sig; kill -- $pid2 2>/dev/null; wait -- $pid2 2>/dev/null; exit 1"
|
|
||||||
trap "$die" $sig
|
|
||||||
fi
|
|
||||||
elif [ -n "$pid2" -a -e "$tmpdir"/2.status ]; then
|
|
||||||
wait -- "$pid2" 2>/dev/null
|
|
||||||
local ret="$?"
|
|
||||||
pid2=
|
|
||||||
if [ "$ret" = 0 ]; then
|
|
||||||
if [ -n "$pid1" ]; then
|
|
||||||
kill -- "$pid1" 2>/dev/null
|
|
||||||
wait -- "$pid1" 2>/dev/null
|
|
||||||
fi
|
|
||||||
n=2
|
|
||||||
break
|
|
||||||
elif [ -z "$pid1" ]; then
|
|
||||||
break
|
|
||||||
else
|
|
||||||
die="trap - $sig; kill -- $pid1 2>/dev/null; wait -- $pid1 2>/dev/null; exit 1"
|
|
||||||
trap "$die" $sig
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
trap - $sig
|
|
||||||
|
|
||||||
if [ -z "$n" ]; then
|
|
||||||
>&"$e" printf 'Failed to download \033[32m%s\033[0m from any mirror:\n' "$file"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf ' 1. \033[4m%s\033[0m\n' "$url1"
|
|
||||||
>&"$e" printf ' 2. \033[4m%s\033[0m\n' "$url2"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'Check your internet connection, then restart your shell.\n'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
command tar -C "$tmpdir" -xzf "$tmpdir"/"$n".tar.gz || exit
|
|
||||||
|
|
||||||
local tmpfile
|
|
||||||
if ! command -v mktemp >/dev/null 2>&1 ||
|
|
||||||
! tmpfile="$(command mktemp "$cache_dir"/gitstatusd.XXXXXXXXXX)"; then
|
|
||||||
tmpfile="$cache_dir"/gitstatusd.tmp.$$
|
|
||||||
fi
|
|
||||||
|
|
||||||
command mv -f -- "$tmpdir"/"$file" "$tmpfile" || exit
|
|
||||||
command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
|
|
||||||
command rm -f -- "$cache_dir"/"$file"
|
|
||||||
command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
|
|
||||||
command rm -f -- "$tmpfile"
|
|
||||||
exit 1
|
|
||||||
)
|
|
||||||
|
|
||||||
local ret="$?"
|
|
||||||
command rm -rf -- "$tmpdir"
|
|
||||||
[ "$ret" = 0 ] || return
|
|
||||||
|
|
||||||
_gitstatus_install_daemon_found 1 "$@"
|
|
||||||
return
|
|
||||||
done <"$gitstatus_dir"/install.info
|
|
||||||
|
|
||||||
>&"$e" printf 'There is no prebuilt \033[32mgitstatusd\033[0m for \033[1m%s\033[0m.\n' "$uname_s $uname_m"
|
|
||||||
>&"$e" printf '\n'
|
|
||||||
>&"$e" printf 'See: \033[4mhttps://github.com/romkatv/gitstatus#compiling\033[0m\n'
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -z "${0##*/*}" ]; then
|
|
||||||
_gitstatus_install_main "${0%/*}" "$@"
|
|
||||||
else
|
|
||||||
_gitstatus_install_main . "$@"
|
|
||||||
fi
|
|
|
@ -1,34 +0,0 @@
|
||||||
# 3
|
|
||||||
#
|
|
||||||
# This file is used by ./install and indirectly by shell bindings.
|
|
||||||
#
|
|
||||||
# The first line is read by powerlevel10k instant prompt. It must
|
|
||||||
# be updated whenever the content of this file changes. The actual
|
|
||||||
# value doesn't matter as long as it's unique. Consecutive integers
|
|
||||||
# work fine.
|
|
||||||
|
|
||||||
# Official gitstatusd binaries.
|
|
||||||
uname_s_glob="cygwin_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3";
|
|
||||||
uname_s_glob="cygwin_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
|
|
||||||
uname_s_glob="darwin"; uname_m_glob="arm64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="eae979e990ca37c56ee39fadd0c3f392cbbd0c6bdfb9a603010be60d9e48910a";
|
|
||||||
uname_s_glob="darwin"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9fd3913ec1b6b856ab6e08a99a2343f0e8e809eb6b62ca4b0963163656c668e6";
|
|
||||||
uname_s_glob="freebsd"; uname_m_glob="amd64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="8e57ad642251e5acfa430aed82cd4ffe103db0bfadae4a15ccaf462c455d0442";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="aarch64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="armv6l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="4bf5a0d0a082f544a48536ad3675930d5d2cc6a8cf906710045e0788f51192b3";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="2b9deb29f86c8209114b71b94fc2e1ed936a1658808a1bee46f4a82fd6a1f8cc";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="56d55e2e9a202d3072fa612d8fa1faa61243ffc86418a7fa64c2c9d9a82e0f64";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="1afd072c8c26ef6ec2d9ac11cef96c84cd6f10e859665a6ffcfb6112c758547e";
|
|
||||||
uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9633816e7832109e530c9e2532b11a1edae08136d63aa7e40246c0339b7db304";
|
|
||||||
uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
|
|
||||||
uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
|
|
||||||
|
|
||||||
# Fallbacks to official gitstatusd binaries.
|
|
||||||
uname_s_glob="cygwin_nt-*"; uname_m_glob="i686"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.2"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3";
|
|
||||||
uname_s_glob="cygwin_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
|
|
||||||
uname_s_glob="mingw32_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
|
|
||||||
uname_s_glob="mingw32_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
|
|
||||||
uname_s_glob="mingw64_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
|
|
||||||
uname_s_glob="mingw64_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
|
|
||||||
uname_s_glob="msys_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
|
|
||||||
uname_s_glob="msys_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue