From 8b1a586da07488b4ca67204a33cb5349666ca9f6 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Tue, 12 Oct 2021 10:55:02 +0800 Subject: update --- tools/init/load-settings.sh | 7 +- zsh/deer | 489 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 493 insertions(+), 3 deletions(-) create mode 100644 zsh/deer diff --git a/tools/init/load-settings.sh b/tools/init/load-settings.sh index 1b8b4b7..e12fd13 100755 --- a/tools/init/load-settings.sh +++ b/tools/init/load-settings.sh @@ -4,12 +4,13 @@ source $SETTING_DIR/alias [[ -d $SETTING_DIR/private ]] && source $SETTING_DIR/private/* # Config shell -case $0 in - zsh) +case $SHELL in + *zsh) setopt extended_glob fpath=($SETTING_DIR/zsh $fpath) + setopt extended_glob ;; - bash) + *bash) shopt -s extglob ;; esac diff --git a/zsh/deer b/zsh/deer new file mode 100644 index 0000000..4b0d883 --- /dev/null +++ b/zsh/deer @@ -0,0 +1,489 @@ +# -*- 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 . # +######################################################################### + +zstyle -s ":deer:" height DEER_HEIGHT || DEER_HEIGHT=22 +zstyle -b ":deer:" show_hidden DEER_SHOW_HIDDEN + + +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 + + if [ "$DEER_SHOW_HIDDEN" = yes ]; then + DEER_GLOBFLAGS=D + else + DEER_GLOBFLAGS="" + fi + + 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 + +zle -N deer +bindkey '\ek' deer -- cgit v1.2.3-70-g09d2