mirror of
https://github.com/elkowar/dots-of-war.git
synced 2024-11-06 11:22:23 +00:00
483 lines
15 KiB
Bash
483 lines
15 KiB
Bash
|
# -*- 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
|
|||
|
~
|