Skip to content

Instantly share code, notes, and snippets.

@UndeadDemidov
Forked from jacksluong/cdp.md
Created September 4, 2024 07:18
Show Gist options
  • Save UndeadDemidov/5cf52c1a6c4004574c74ed48adae2ee4 to your computer and use it in GitHub Desktop.
Save UndeadDemidov/5cf52c1a6c4004574c74ed48adae2ee4 to your computer and use it in GitHub Desktop.
An interactive way to navigate directories in a terminal

cdp: Change Directory Pro

recording

How do you navigate in your terminal? Do you chain sequences of cd commands together, or copy a file path and paste it? Sometimes to a destination directory that's not adjacent to your current one?

cdp might be the solution for you. It's a command that activates an interactive way to navigate directories smoothly and easily.

Controls

  • cdp to activate
  • ↑/↓ to change the selected subdirectory
  • ← to move into the parent subdirectory (disallowed when in $HOME)
  • → to make the selected subdirectory the active one (i.e. cd into it)
  • ↵ (return) to exit navigation in the new active directory
  • ⎵ (space) to make $HOME the active directory
  • [0-9] to make active the subdirectory is listed next to that number
  • Ctrl+C to cancel navigation, staying in the initial directory

Installation

Just copy the code below into your .zshrc! (Alternatively, place it in a different file and source that file in .zshrc to keep those files from getting bloated.)

Not yet tested for Bash or PowerShell.

# `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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment