dots-of-war/files/nix-stuff/nixpkgs/config/deer.zsh

483 lines
15 KiB
Bash
Raw Normal View History

2020-07-03 22:47:14 +00:00
# -*- mode: shell-script -*-
# vim: set ft=zsh :
#########################################################################
# Copyright (C) 2014-2015 Wojciech Siewierski #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
#########################################################################
zstyle -s ":deer:" height DEER_HEIGHT || DEER_HEIGHT=22
typeset -Ag DEER_KEYS
function ()
{
while [ -n "$2" ]; do
DEER_KEYS[$1]=${DEER_KEYS[$1]:-$2}
shift 2
done
} down j \
page_down J \
up k \
page_up K \
enter l \
leave h \
next_parent ']' \
prev_parent '[' \
search / \
filter f \
toggle_hidden H \
quit q \
append_path a \
append_abs_path A \
insert_path i \
insert_abs_path I \
multi_insert_dwim s \
multi_insert_abs S \
chdir c \
chdir_selected C \
rifle r \
edit e \
# Select the Nth next file. Pass a negative argument for the previous file.
deer-move()
{
local FILES MOVEMENT INDEX
MOVEMENT=$1
FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t)
$DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t))
INDEX=${(k)FILES[(re)$DEER_BASENAME[$DEER_DIRNAME]]}
if (( INDEX+MOVEMENT <= 0 )); then
DEER_BASENAME[$DEER_DIRNAME]=$FILES[1]
elif (( INDEX+MOVEMENT > $#FILES )); then
DEER_BASENAME[$DEER_DIRNAME]=$FILES[$#FILES]
else
DEER_BASENAME[$DEER_DIRNAME]=$FILES[$INDEX+$MOVEMENT]
fi
}
# Select the first visible directory (or file if there are no
# directories) in the current directory. Useful when changing the file
# filter.
deer-refocus()
{
local TMP
TMP=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t)
$DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t))
DEER_BASENAME[$DEER_DIRNAME]=$TMP[1]
[ -n "$DEER_BASENAME[$DEER_DIRNAME]" ] # Return if there were any files at all.
}
# Enter the selected directory
deer-enter()
{
# Abort if there is no file focused at all or if it is not a
# directory.
[ -n "$DEER_BASENAME[$DEER_DIRNAME]" -a \
-d "$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]" ] || return
DEER_DIRNAME=${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME]
if [ -z $DEER_BASENAME[$DEER_DIRNAME] ]; then
deer-refocus
fi
}
# Move to the parent directory
deer-leave()
{
[ $DEER_DIRNAME = / ] && return
DEER_BASENAME[$DEER_DIRNAME:h]=$DEER_DIRNAME:t
DEER_DIRNAME=$DEER_DIRNAME:h
}
# Display a given prompt, read a string and save it into $BUFFER.
deer-prompt()
{
BUFFER=""
PREDISPLAY="$1/ "
POSTDISPLAY=""
local region_highlight
region_highlight=("P0 $#1 fg=green")
zle recursive-edit
}
# Read a pattern and select the first matching file.
deer-search()
{
deer-prompt "search"
local TMP
TMP=($DEER_DIRNAME/${~BUFFER}${DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-:t))
[ -n "$TMP[1]" ] && DEER_BASENAME[$DEER_DIRNAME]=$TMP[1]
}
# Read a pattern and use it as a new filter.
deer-filter()
{
deer-prompt "filter"
if [ -n "$BUFFER" ] && [[ ! $BUFFER == *\** ]]; then
BUFFER=*$BUFFER*
fi
deer-apply-filter $BUFFER || deer-apply-filter
}
deer-apply-filter()
{
DEER_FILTER[$DEER_DIRNAME]=$1
deer-refocus
}
# Draw an arrow pointing to the selected file.
deer-mark-file-list()
{
local MARKED=$1
shift
print -l -- "$@" \
| grep -Fx -B5 -A$DEER_HEIGHT -- "$MARKED" \
| perl -pe 'BEGIN{$name = shift}
if ($name."\n" eq $_) {
$_="-> $_"
} else {
$_=" $_"
}' -- "$MARKED"
}
# Draw the file lists in the form of Miller columns.
deer-refresh()
{
local FILES PREVIEW PARENTFILES OUTPUT REL_DIRNAME
local SEPARATOR="------"
PREDISPLAY=$OLD_LBUFFER
REL_DIRNAME=${${DEER_DIRNAME%/}#$DEER_STARTDIR}/
[ -n "$DEER_STARTDIR" ] && REL_DIRNAME=${REL_DIRNAME#/}
LBUFFER=$REL_DIRNAME$DEER_BASENAME[$DEER_DIRNAME]
RBUFFER=""
local TMP_FILTER
TMP_FILTER=${DEER_FILTER[$DEER_DIRNAME]}
POSTDISPLAY=${TMP_FILTER:+ filt:$TMP_FILTER}
region_highlight=("P0 $#PREDISPLAY fg=black,bold"
"0 $#REL_DIRNAME fg=blue,bold"
"$#BUFFER $[$#BUFFER+$#POSTDISPLAY] fg=yellow,bold")
FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t)
$SEPARATOR
$DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t))
PARENTFILES=($DEER_DIRNAME:h/${~DEER_FILTER[$DEER_DIRNAME:h]:-'*'}(N$DEER_GLOBFLAGS-/:t))
local IFS=$'\n'
FILES=($(deer-mark-file-list "$DEER_BASENAME[$DEER_DIRNAME]" $FILES))
PARENTFILES=($(deer-mark-file-list "$DEER_DIRNAME:t" $PARENTFILES))
unset IFS
FILES=(${(F)FILES[1,$DEER_HEIGHT]})
PARENTFILES=(${(F)PARENTFILES[1,$DEER_HEIGHT]})
if [ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]; then
if file $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | grep -Fq text; then
PREVIEW="--- Preview: ---"$'\n'$(head -n$DEER_HEIGHT $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME])
# Replace '/' with '' (division slash, U+2215) to allow using it as a
# paste(1)/column(1) separator.
PREVIEW=${PREVIEW//\//}
else
PREVIEW="--- Binary file, preview unavailable ---"
fi
else
# I'm really sorry about what you see below.
# It basically means: PREVIEW=(directories separator files)
PREVIEW=($DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-/:t)
$SEPARATOR
$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-^/:t))
PREVIEW=${(F)PREVIEW[1,$DEER_HEIGHT]}
fi
OUTPUT="$(paste -d/ <(<<< $PARENTFILES \
| awk '{print substr($0,1,16)}') \
<(<<< $FILES) \
<(<<< $PREVIEW) \
| sed 's,/, / ,g' \
| column -t -s/ 2> /dev/null \
| awk -v width=$COLUMNS '{print substr($0,1,width-1)}')"
zle -M -- $OUTPUT
zle -R
}
# Run `deer-add' with the same arguments, restore the shell state and
# then exit.
deer-restore()
{
deer-add "$@"
PREDISPLAY=""
POSTDISPLAY=""
region_highlight=()
LBUFFER=$OLD_LBUFFER
RBUFFER=$OLD_RBUFFER
zle reset-prompt
zle -M ""
}
# Add the given string before or after the cursor.
deer-add()
{
case $1 in
--append)
OLD_LBUFFER+=$2
shift 2
;;
--insert)
OLD_RBUFFER=$2$OLD_RBUFFER
shift 2
;;
esac
}
# Get the quoted relative path from the absolute unquoted path.
deer-get-relative()
{
local TMP
TMP=${1:-${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME]}
TMP="`python -c '
import sys, os
print(os.path.relpath(sys.argv[1], sys.argv[2]))
' $TMP ${DEER_STARTDIR:-$PWD}`"
print -R $TMP:q
}
# Tries to guess a directory to start in from the current argument.
deer-set-initial-directory()
{
autoload -U split-shell-arguments modify-current-argument
local REPLY REPLY2 reply
local DIRECTORY
((--CURSOR))
split-shell-arguments
((++CURSOR))
# Find the longest existing directory path in the current argument.
DEER_STARTDIR=${(Q)${${reply[$REPLY]%%[[:space:]]#}:a}%/}
while [ -n "$DEER_STARTDIR" -a \
! -d "$DEER_STARTDIR" ]; do
DEER_STARTDIR=${DEER_STARTDIR%/*}
done
DEER_DIRNAME=${DEER_STARTDIR:-$PWD}
}
# The main entry function.
deer-launch()
{
emulate -L zsh
setopt extended_glob
local DEER_DIRNAME DEER_STARTDIR DEER_GLOBFLAGS
local -A DEER_FILTER DEER_BASENAME
local REPLY OLD_LBUFFER OLD_RBUFFER
local GREP_OPTIONS
GREP_OPTIONS=""
OLD_LBUFFER=$LBUFFER
OLD_RBUFFER=$RBUFFER
deer-set-initial-directory
DEER_GLOBFLAGS=""
if [ -n "$NUMERIC" ]; then
for i in {1..$NUMERIC}; do
deer-leave
done
else
# Don't change cwd but initialize the variables.
deer-leave
deer-enter
fi
deer-refresh
while read -k; do
case $REPLY in
# Movement
$DEER_KEYS[up])
deer-move -1
deer-refresh
;;
$DEER_KEYS[page_up])
deer-move -5
deer-refresh
;;
$DEER_KEYS[down])
deer-move 1
deer-refresh
;;
$DEER_KEYS[page_down])
deer-move 5
deer-refresh
;;
$DEER_KEYS[enter])
deer-enter
deer-refresh
;;
$DEER_KEYS[leave])
deer-leave
deer-refresh
;;
$DEER_KEYS[next_parent])
deer-leave
deer-move 1
deer-enter
deer-refresh
;;
$DEER_KEYS[prev_parent])
deer-leave
deer-move -1
deer-enter
deer-refresh
;;
# Search
$DEER_KEYS[search])
deer-search
deer-refresh
;;
# Filter
$DEER_KEYS[filter])
deer-filter
deer-refresh
;;
$DEER_KEYS[toggle_hidden])
if [ -z $DEER_GLOBFLAGS ]; then
DEER_GLOBFLAGS="D" # show hidden files
else
DEER_GLOBFLAGS=""
fi
# make sure the focus is on a visible file
DEER_BASENAME[$DEER_DIRNAME]=
deer-leave
deer-enter
deer-refresh
;;
# Quit
$DEER_KEYS[quit])
deer-restore
break
;;
# Insert the path and quit.
$DEER_KEYS[append_path])
deer-restore --append "`deer-get-relative` "
break
;;
$DEER_KEYS[append_abs_path])
deer-restore --append "${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q} "
break
;;
$DEER_KEYS[insert_path])
deer-restore --insert " `deer-get-relative`"
break
;;
$DEER_KEYS[insert_abs_path])
deer-restore --insert " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}"
break
;;
# Insert the path and don't quit yet.
$DEER_KEYS[multi_insert_dwim])
if [ "$OLD_LBUFFER[-1]" = "/" ]; then
OLD_LBUFFER+="{"
fi
# replacement used to insert ',' instead of '{' as a separator in {foo,bar,...} lists
deer-add --append "`deer-get-relative`"${${OLD_LBUFFER[-1]/\{/,}:- }
deer-move 1
deer-refresh
;;
# Insert the absolute path and don't quit yet.
$DEER_KEYS[multi_insert_abs])
deer-add --append " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}"
deer-move 1
deer-refresh
;;
# Quit and change the shell's current directory to the selected one.
$DEER_KEYS[chdir])
deer-leave
;&
$DEER_KEYS[chdir_selected])
if [[ -d $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] && \
-x $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then
cd -- $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]
deer-restore
break
fi
;;
$DEER_KEYS[edit])
if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then
"${EDITOR:-vim}" $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]
fi
;;
# See rifle(1) manpage (included with ranger(1)).
$DEER_KEYS[rifle])
if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then
rifle $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]
fi
;;
# Arrow keys
$'\e')
read -k
case $REPLY in
'[')
read -k
case $REPLY in
'A')
deer-move -1
deer-refresh
;;
'B')
deer-move 1
deer-refresh
;;
'C')
deer-enter
deer-refresh
;;
'D')
deer-leave
deer-refresh
;;
esac
;;
esac
;;
esac
done
}
if zle; then
deer-launch
else
deer()
{
deer-launch "$@"
}
fi
~