Skip to content

Instantly share code, notes, and snippets.

@UndeadDemidov
Forked from jacksluong/cdp.md
Created September 4, 2024 07:18
Show Gist options
  • Select an option

  • Save UndeadDemidov/8dfcd27d05fc4a75563ca86939b6ff8f to your computer and use it in GitHub Desktop.

Select an option

Save UndeadDemidov/8dfcd27d05fc4a75563ca86939b6ff8f to your computer and use it in GitHub Desktop.
An interactive way to navigate directories in a terminal

cdp: Change Directory Pro

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 (Zsh) or .profile (Bash)!

Demo

recording

Just copy the code below into your .zshrc (Zsh) or .profile (Bash)!

# `cdp` to activate an interactive way to navigate directories
function cdp {
# derived from: https://unix.stackexchange.com/a/415155 (converted to zsh)
# tput usage: https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux/20983251#20983251
# 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