|
# `cdp` to activate an interactive way to navigate directories |
|
function cdp { |
|
# helpers for terminal print control, key input, and other things |
|
ESC=$( printf "\033" ) |
|
cursor_blink_on() { printf "${ESC}[?25h"; } |
|
cursor_blink_off() { printf "${ESC}[?25l"; } |
|
cursor_to() { printf "${ESC}[$1;${2:-1}H"; } |
|
print_option() { printf " $1 $(tput el)"; } |
|
print_selected() { printf "${ESC}[7m $1 ${ESC}[27m$(tput el)"; } |
|
get_cursor_row() { IFS=';' read -sdR $'?\E[6n' ROW COL; echo ${ROW#*\[}; } |
|
|
|
key_input() { read -sk1 'key?' |
|
case $key in |
|
$'\n') echo enter;; |
|
' ') echo space;; |
|
0) echo 10;; |
|
1|2|3|4|5|6|7|8|9) echo $key;; |
|
"${ESC}") |
|
read -sk2 key |
|
if [[ $key = "[A" ]]; then echo up |
|
elif [[ $key = "[B" ]]; then echo down |
|
elif [[ $key = "[C" ]]; then echo right |
|
elif [[ $key = "[D" ]]; then echo left |
|
fi;; |
|
esac } |
|
min() { printf "%s\n" "$@" | sort -g | head -n1 } |
|
max() { printf "%s\n" "$@" | sort -gr | head -n1 } |
|
|
|
|
|
# initialize persistent variables |
|
MAX_SIZE=10 |
|
local prev_dir='' |
|
local orig_dir=$PWD |
|
|
|
while true; do |
|
# determine the options |
|
local subdirs=$((ls -1d */) 2> /dev/null) |
|
local opts=("$(tput smul)Change directory to $(tput bold)$(pwd)$(tput sgr0)$(tput el)") |
|
[[ ! -z $subdirs ]] && opts+=("${(f)subdirs}") |
|
opts+=('../') |
|
local num_opts=${#opts[@]} |
|
|
|
# initialize interaction area and screen positions of first and last row (used for overwriting) |
|
# note: indices are all 1-indexed with row 1 being the line displaying the working directory |
|
local num_opt_rows=$(min $(tput lines) $MAX_SIZE $((num_opts - 1))) |
|
for _ in {1..$num_opt_rows}; do echo; done |
|
local last_row=$(get_cursor_row) |
|
local first_row=$((last_row - num_opt_rows)) |
|
cursor_to $first_row |
|
echo " $opts[1]" |
|
|
|
# ensure cursor and input echoing back on upon a ctrl+c during read |
|
trap "cd $orig_dir; cursor_blink_on; stty echo; printf '\n'; return" 2 |
|
cursor_blink_off |
|
|
|
# jump to previous dir option (if left arrow key was pressed) |
|
local selected=2 |
|
for i in {2..$#opts}; do [[ $opts[$i] = $prev_dir ]] && selected=$i; done |
|
local inp='' |
|
|
|
local top_row=$(max $((selected - num_opt_rows + 2)) 2) |
|
while true; do |
|
# determine "scroll" position |
|
if [[ $selected -lt $top_row ]]; then top_row=$selected |
|
elif [[ $selected -ge $((top_row + num_opt_rows)) ]]; then top_row=$((selected - num_opt_rows + 1)); fi |
|
|
|
# print options by overwriting the last lines |
|
for opt_index in {$top_row..$((top_row + num_opt_rows - 1))}; do |
|
cursor_to $((first_row + opt_index - top_row + 1)) |
|
printf " [%i] " $(((opt_index - top_row + 1) % 10)) |
|
if [[ $opt_index -eq $selected ]]; then print_selected $opts[$((opt_index))] |
|
else print_option $opts[$((opt_index))]; fi |
|
done |
|
|
|
# user key control |
|
inp=$(key_input) |
|
case $inp in |
|
left) [[ $PWD != $HOME ]] && break;; |
|
up) ((selected--)); [[ $selected -lt 2 ]] && selected=$num_opts;; |
|
down) ((selected++)); [[ $selected -gt $num_opts ]] && selected=2;; |
|
right|enter|space|1|2|3|4|5|6|7|8|9|10) break;; |
|
esac |
|
done |
|
|
|
# reset interaction area |
|
cursor_to $first_row |
|
for row_offset in {1..$num_opt_rows}; do cursor_to $((first_row + row_offset)); echo -n $(tput el); done |
|
cursor_to $first_row |
|
|
|
# cd accordingly |
|
case $inp in |
|
right) cd $opts[$selected]; prev_dir='';; |
|
enter) break;; |
|
space) cd; prev_dir='';; |
|
left) prev_dir=$(printf '%s/' "${PWD##*/}"); cd ..;; |
|
*) local dest=$opts[$((top_row + inp - 1))] |
|
[[ $dest == '../' ]] && prev_dir=$(printf '%s/' "${PWD##*/}") |
|
cd $dest;; |
|
esac |
|
done |
|
cursor_blink_on |
|
} |