Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,25 @@ Commands
`n`: Next page.
`p`: Previous page.
`b`: Move selection back to parent directory. (For `sd` only.)
`Enter`: Navigate to selected directory (For `sd` only.)

`Enter`: Navigate to selected directory (For `sd` only.)

Customization
----
You can customize the selection keys by editing the config file at `~/.config/shunpo/config`:

```bash
# Selection keys (exactly 10 characters, one per menu item)
SHUNPO_SELECTION_KEYS="asdfghjkl;"
```

**Rules:**
- Must be exactly 10 characters (one for each menu item).
- Cannot contain `n`, `p`, or `b` (reserved for navigation).
- No duplicate characters allowed.
- Invalid configs fall back to the default `0123456789`.

**Note:** CLI arguments (e.g., `sg 3`, `sj 1`) always use numeric indices 0-9, regardless of your custom keys.

Uninstalling
----
Run `uninstall.sh`
Expand Down
19 changes: 18 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ BASHRC="$HOME/.bashrc"
# File containing command definitions.
SHUNPO_CMD="$INSTALL_DIR/shunpo_cmd"

# Config file path.
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/shunpo"
CONFIG_FILE="$CONFIG_DIR/config"

setup() {
mkdir -p "$INSTALL_DIR"
mkdir -p "$SCRIPT_DIR"
Expand Down Expand Up @@ -39,7 +43,7 @@ install() {
cp src/* "$SCRIPT_DIR"

# Add sourcing for shunpo_cmd (overwrite).
source_rc_line="source $SHUNPO_CMD"
source_rc_line="source \"$SHUNPO_CMD\""
temp_file=$(mktemp)
sed '/^source.*\shunpo_cmd/d' "$BASHRC" >"$temp_file"
mv "$temp_file" "$BASHRC"
Expand All @@ -55,6 +59,19 @@ install() {
echo "Added to BASHRC: $install_dir_line"

add_commands

# Create default config file if it doesn't exist.
if [ ! -f "$CONFIG_FILE" ]; then
mkdir -p "$CONFIG_DIR"
cat >"$CONFIG_FILE" <<EOF
# Shunpo Configuration
# Selection keys (exactly 10 characters, one per menu item)
# Reserved keys that cannot be used: n, p, b
# Note: CLI arguments (e.g., sg 3, sj 1) always use numeric indices 0-9
SHUNPO_SELECTION_KEYS="0123456789"
EOF
echo "Created config: $CONFIG_FILE"
fi
}

# Install.
Expand Down
21 changes: 18 additions & 3 deletions nix/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
default = self.packages.${pkgs.system}.shunpo;
shunpo = pkgs.stdenv.mkDerivation {
pname = "shunpo";
version = "1.0.4";

version = "1.0.5";
src = builtins.path { path = ../src; };
buildInputs = [ pkgs.bash pkgs.shfmt ];

Expand Down Expand Up @@ -50,7 +49,23 @@ EOF

# Write initialization file that must be sourced.
SHUNPO_INIT=$INSTALL_DIR/shunpo_init
echo "source $SHUNPO_CMD" > "$SHUNPO_INIT"
cat > "$SHUNPO_INIT" <<'INIT_EOF'
# Create default config file if it doesn't exist.
SHUNPO_CONFIG_DIR=''${XDG_CONFIG_HOME:-$HOME/.config}/shunpo
SHUNPO_CONFIG_FILE=$SHUNPO_CONFIG_DIR/config
if [ ! -f "$SHUNPO_CONFIG_FILE" ]; then
mkdir -p "$SHUNPO_CONFIG_DIR"
cat >"$SHUNPO_CONFIG_FILE" <<'CONFIG_EOF'
# Shunpo Configuration
# Selection keys (exactly 10 characters, one per menu item)
# Reserved keys that cannot be used: n, p, b
# Note: CLI arguments (e.g., sg 3, sj 1) always use numeric indices 0-9
SHUNPO_SELECTION_KEYS="0123456789"
CONFIG_EOF
fi
unset SHUNPO_CONFIG_DIR SHUNPO_CONFIG_FILE
INIT_EOF
echo "source $SHUNPO_CMD" >> "$SHUNPO_INIT"
chmod +x $SHUNPO_INIT # not necessary, but keep for auto-complete.
'';

Expand Down
106 changes: 88 additions & 18 deletions src/functions.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,65 @@
#!/usr/bin/env bash

# Configuration Path.
SHUNPO_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/shunpo"
SHUNPO_CONFIG_FILE="$SHUNPO_CONFIG_DIR/config"

# Default selection keys (can be overridden in config).
SHUNPO_SELECTION_KEYS="0123456789"

# Load and validate user configuration.
function shunpo_load_config() {
if [ -f "$SHUNPO_CONFIG_FILE" ]; then
local user_keys
# Source the config to get SHUNPO_SELECTION_KEYS
source "$SHUNPO_CONFIG_FILE"
user_keys="$SHUNPO_SELECTION_KEYS"

# Validate: exactly 10 characters
if [ ${#user_keys} -ne 10 ]; then
echo -e "${SHUNPO_BOLD}${SHUNPO_ORANGE}Warning: SHUNPO_SELECTION_KEYS must be exactly 10 characters. Using defaults.${SHUNPO_RESET}"
SHUNPO_SELECTION_KEYS="0123456789"
return
fi

# Validate: no reserved keys (n, p, b)
if [[ $user_keys == *n* ]] || [[ $user_keys == *p* ]] || [[ $user_keys == *b* ]]; then
echo -e "${SHUNPO_BOLD}${SHUNPO_ORANGE}Warning: SHUNPO_SELECTION_KEYS cannot contain 'n', 'p', or 'b'. Using defaults.${SHUNPO_RESET}"
SHUNPO_SELECTION_KEYS="0123456789"
return
fi

# Validate: no duplicate characters
local seen=""
for ((i = 0; i < ${#user_keys}; i++)); do
local char="${user_keys:i:1}"
if [[ $seen == *"$char"* ]]; then
echo -e "${SHUNPO_BOLD}${SHUNPO_ORANGE}Warning: SHUNPO_SELECTION_KEYS cannot contain duplicates. Using defaults.${SHUNPO_RESET}"
SHUNPO_SELECTION_KEYS="0123456789"
return
fi
seen+="$char"
done

SHUNPO_SELECTION_KEYS="$user_keys"
fi
}

# Get the index (0-9) for a given key, or return 1 if not found.
function shunpo_get_key_index() {
local key="$1"
for ((i = 0; i < ${#SHUNPO_SELECTION_KEYS}; i++)); do
if [[ ${SHUNPO_SELECTION_KEYS:i:1} == "$key" ]]; then
echo "$i"
return 0
fi
done
return 1
}

# Load configuration on source.
shunpo_load_config

# Default Bookmarks Path.
SHUNPO_BOOKMARKS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/shunpo/"
if [ ! -d "$SHUNPO_BOOKMARKS_DIR" ]; then
Expand Down Expand Up @@ -80,7 +140,7 @@ function shunpo_interact_bookmarks() {

# Display bookmarks for the current page.
for ((i = start_index; i < end_index; i++)); do
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}$((i - start_index))${SHUNPO_RESET}] ${bookmarks[i]}"
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}${SHUNPO_SELECTION_KEYS:$((i - start_index)):1}${SHUNPO_RESET}] ${bookmarks[i]}"
done

if [ $last_page -gt 1 ]; then
Expand All @@ -101,9 +161,9 @@ function shunpo_interact_bookmarks() {
fi
shunpo_clear_output

elif [[ $input =~ ^[0-9]+$ ]] && [ "$input" -ge 0 ] && [ "$input" -lt $max_per_page ]; then
elif key_index=$(shunpo_get_key_index "$input"); then
# Process bookmark selection input.
shunpo_selected_bookmark_index=$((current_page * max_per_page + input))
shunpo_selected_bookmark_index=$((current_page * max_per_page + key_index))
if [[ $shunpo_selected_bookmark_index -lt $total_bookmarks ]]; then
shunpo_selected_dir="${bookmarks[$shunpo_selected_bookmark_index]}"
shunpo_clear_output
Expand Down Expand Up @@ -201,9 +261,9 @@ function shunpo_jump_to_parent_dir() {
# Display the current page of parent directories.
for ((i = start_index; i < end_index; i++)); do
if [[ $i -eq $((end_index - 1)) && $current_page -eq $((last_page - 1)) ]]; then
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}$((i - start_index))${SHUNPO_RESET}] $(basename "${parent_dirs[i]}")"
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}${SHUNPO_SELECTION_KEYS:$((i - start_index)):1}${SHUNPO_RESET}] $(basename "${parent_dirs[i]}")"
else
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}$((i - start_index))${SHUNPO_RESET}] /$(basename "${parent_dirs[i]}")"
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}${SHUNPO_SELECTION_KEYS:$((i - start_index)):1}${SHUNPO_RESET}] /$(basename "${parent_dirs[i]}")"
fi
done

Expand All @@ -225,17 +285,16 @@ function shunpo_jump_to_parent_dir() {
fi
shunpo_clear_output

elif [[ $input =~ ^[0-9]+$ ]] && [ "$input" -ge 0 ] && [ "$input" -lt "$max_per_page" ]; then
selected_index=$((start_index + input))
if [[ $selected_index -lt $total_parents ]]; then
elif key_index=$(shunpo_get_key_index "$input"); then
selected_index=$((start_index + key_index))
if [[ $selected_index -ge 0 ]] && [[ $selected_index -lt $total_parents ]]; then
shunpo_clear_output
tput cnorm
cd "${parent_dirs[$selected_index]}" || return 1
echo -e "${SHUNPO_GREEN}${SHUNPO_BOLD}Changed to:${SHUNPO_RESET} ${parent_dirs[$selected_index]}"
return 0
else
shunpo_clear_output
fi
shunpo_clear_output
else
shunpo_clear_output
tput cnorm
Expand Down Expand Up @@ -283,7 +342,8 @@ function shunpo_jump_to_child_dir() {
# Attempt to retrieve from cache.
if cache_index=$(shunpo_is_cached "$selected_path"); then
# Use cached value.
IFS='|' read -r -a child_dirs <<<"${cache_values[$cache_index]}"
local IFS='|'
read -r -a child_dirs <<<"${cache_values[$cache_index]}"
else
# Collect directories if not cached.
child_dirs=()
Expand Down Expand Up @@ -345,7 +405,7 @@ function shunpo_jump_to_child_dir() {
else
# Print child directories.
for ((i = start_index; i < end_index; i++)); do
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}$((i - start_index))${SHUNPO_RESET}] ${child_dirs[i]#$selected_path}"
echo -e "[${SHUNPO_BOLD}${SHUNPO_ORANGE}${SHUNPO_SELECTION_KEYS:$((i - start_index)):1}${SHUNPO_RESET}] ${child_dirs[i]#"$selected_path"}"
done

if [ $last_page -gt 1 ]; then
Expand Down Expand Up @@ -391,8 +451,8 @@ function shunpo_jump_to_child_dir() {
fi
break

elif [[ $input =~ ^[0-9]+$ ]] && [[ $input -ge 0 ]] && [[ $input -lt $max_per_page ]]; then
selected_index=$((start_index + input))
elif key_index=$(shunpo_get_key_index "$input"); then
selected_index=$((start_index + key_index))
if [[ $selected_index -lt $total_child_dirs ]]; then
selected_path="${child_dirs[selected_index]}"
is_start_dir=0
Expand Down Expand Up @@ -446,11 +506,22 @@ function shunpo_assert_bookmarks_exist() {

function shunpo_cleanup() {
# Clean up to avoid namespace pollution.
#
# Restore terminal state.
[ -t 0 ] && tput cnorm 2>/dev/null
trap - SIGINT

# Clean up variables.
unset SHUNPO_BOOKMARKS_FILE
unset SHUNPO_BOOKMARKS_DIR
unset IFS
unset SHUNPO_CONFIG_DIR
unset SHUNPO_CONFIG_FILE
unset SHUNPO_SELECTION_KEYS
IFS=$' \t\n' # Restore IFS to default.
unset shunpo_selected_dir
unset shunpo_selected_bookmark_index

# Clean up functions.
unset -f shunpo_interact_bookmarks
unset -f shunpo_add_space
unset -f shunpo_clear_output
Expand All @@ -459,8 +530,7 @@ function shunpo_cleanup() {
unset -f shunpo_jump_to_child_dir
unset -f shunpo_is_cached
unset -f shunpo_handle_kill
unset -f shunpo_load_config
unset -f shunpo_get_key_index
unset -f shunpo_cleanup
tput cnorm
stty echo
trap - SIGINT
}
18 changes: 10 additions & 8 deletions tests/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ fi

function setup_env {
HOME=${SHUNPO_TEST_DIR}/home
mkdir -p $HOME
mkdir -p "$HOME"
XDG_DATA_HOME=${SHUNPO_TEST_DIR}/home/.local/share
mkdir -p $XDG_DATA_HOME
mkdir -p "$XDG_DATA_HOME"
XDG_CONFIG_HOME=${SHUNPO_TEST_DIR}/home/.config
mkdir -p "$XDG_CONFIG_HOME"
}

function cleanup_env {
rm $SHUNPO_TEST_DIR/home/.bashrc
rm $SHUNPO_TEST_DIR/home/.bashrc$
rm "$SHUNPO_TEST_DIR/home/.bashrc"
rm "$SHUNPO_TEST_DIR/home/.bashrc$"

if [ -d "${SHUNPO_TEST_DIR}/home" ]; then
rmdir ${SHUNPO_TEST_DIR}/home/
rmdir "${SHUNPO_TEST_DIR}/home/"
fi

if [ -d "${SHUNPO_TEST_DIR}" ]; then
find ${SHUNPO_TEST_DIR} -type d -empty -delete
rmdir $SHUNPO_TEST_DIR
find "${SHUNPO_TEST_DIR}" -type d -empty -delete
rmdir "$SHUNPO_TEST_DIR"
fi
}

Expand All @@ -47,5 +49,5 @@ make_directories() {

get_num_bookmarks() {
SHUNPO_BOOKMARKS_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/shunpo/.shunpo_bookmarks"
echo $(wc -l <$SHUNPO_BOOKMARKS_FILE | tr -d '[:space:]')
echo $(wc -l <"$SHUNPO_BOOKMARKS_FILE" | tr -d '[:space:]')
}
Loading