mimimum zsh plugins

This commit is contained in:
Christoph J. Scherr 2023-09-13 22:17:29 +02:00
parent dfd0ebf8c7
commit 28bae624a4
68 changed files with 6484 additions and 0 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@
!.zsh/** !.zsh/**
!.tmux.conf !.tmux.conf
!.gitconfig !.gitconfig
!.local
!.local/**

View File

@ -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

View File

@ -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'
}

View File

@ -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

View File

@ -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'
}

51
.local/share/nnn/plugins/.cbcp Executable file
View File

@ -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

View File

@ -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

180
.local/share/nnn/plugins/.nmv Executable file
View File

@ -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

View File

@ -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 $?
}

22
.local/share/nnn/plugins/.ntfy Executable file
View File

@ -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

View File

@ -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_.

View 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

50
.local/share/nnn/plugins/boom Executable file
View File

@ -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

View File

@ -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"

56
.local/share/nnn/plugins/cdpath Executable file
View File

@ -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

75
.local/share/nnn/plugins/chksum Executable file
View File

@ -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

80
.local/share/nnn/plugins/cmusq Executable file
View File

@ -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

62
.local/share/nnn/plugins/diffs Executable file
View File

@ -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

View File

@ -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

70
.local/share/nnn/plugins/dups Executable file
View File

@ -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 _

89
.local/share/nnn/plugins/finder Executable file
View File

@ -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

View File

@ -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

89
.local/share/nnn/plugins/fzcd Executable file
View File

@ -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

40
.local/share/nnn/plugins/fzhist Executable file
View File

@ -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

83
.local/share/nnn/plugins/fzopen Executable file
View File

@ -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

59
.local/share/nnn/plugins/fzplug Executable file
View File

@ -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

View File

@ -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

View File

@ -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

28
.local/share/nnn/plugins/gpgd Executable file
View File

@ -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

44
.local/share/nnn/plugins/gpge Executable file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

597
.local/share/nnn/plugins/imgur Executable file
View File

@ -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 _

113
.local/share/nnn/plugins/imgview Executable file
View File

@ -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

13
.local/share/nnn/plugins/ipinfo Executable file
View File

@ -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 _

View File

@ -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

42
.local/share/nnn/plugins/launch Executable file
View File

@ -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

View File

@ -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"

View File

@ -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

89
.local/share/nnn/plugins/mocq Executable file
View File

@ -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

View File

@ -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

View File

@ -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

75
.local/share/nnn/plugins/nbak Executable file
View File

@ -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"

55
.local/share/nnn/plugins/nmount Executable file
View File

@ -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

555
.local/share/nnn/plugins/nuke Executable file
View File

@ -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

View File

@ -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 _

View File

@ -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

View File

@ -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 "$@"

View File

@ -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

View File

@ -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

View File

@ -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

35
.local/share/nnn/plugins/pskill Executable file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

16
.local/share/nnn/plugins/suedit Executable file
View File

@ -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

View File

@ -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

View File

@ -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

45
.local/share/nnn/plugins/upload Executable file
View File

@ -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 _

View File

@ -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

62
.local/share/nnn/plugins/x2sel Executable file
View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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"'

View File

@ -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
}
}

View File

@ -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