1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 18:30:08 +00:00

fix: show full purge paths during confirmation

Fixes #576
This commit is contained in:
Tw93
2026-03-17 19:20:15 +08:00
parent 13274154d4
commit 25892594b0
3 changed files with 187 additions and 22 deletions

View File

@@ -40,6 +40,53 @@ note_activity() {
fi
}
# Keep the most specific tail of a long purge path visible on the live scan line.
compact_purge_scan_path() {
local path="$1"
local max_path_len="${2:-0}"
if ! [[ "$max_path_len" =~ ^[0-9]+$ ]] || [[ "$max_path_len" -lt 4 ]]; then
max_path_len=4
fi
if [[ ${#path} -le $max_path_len ]]; then
echo "$path"
return
fi
local suffix_len=$((max_path_len - 3))
local suffix="${path: -$suffix_len}"
local path_tail=""
local remainder="$path"
while [[ "$remainder" == */* ]]; do
local segment="/${remainder##*/}"
remainder="${remainder%/*}"
if [[ -z "$path_tail" ]]; then
if [[ ${#segment} -le $suffix_len ]]; then
path_tail="$segment"
else
break
fi
continue
fi
if [[ $((${#segment} + ${#path_tail})) -le $suffix_len ]]; then
path_tail="${segment}${path_tail}"
else
break
fi
done
if [[ -n "$path_tail" ]]; then
echo "...${path_tail}"
return
fi
echo "...$suffix"
}
# Main purge function
start_purge() {
# Set current command for operation logging
@@ -127,24 +174,13 @@ perform_purge() {
# Set up trap to exit cleanly (erase the spinner line via /dev/tty)
trap 'printf "\r\033[2K" >/dev/tty 2>/dev/null; exit 0' INT TERM
# Truncate path to guaranteed fit
truncate_path() {
local path="$1"
if [[ ${#path} -le $max_path_len ]]; then
echo "$path"
return
fi
local side_len=$(((max_path_len - 3) / 2))
echo "${path:0:$side_len}...${path: -$side_len}"
}
while [[ -f "$stats_dir/purge_scanning" ]]; do
local current_path
current_path=$(cat "$stats_dir/purge_scanning" 2> /dev/null || echo "")
if [[ -n "$current_path" ]]; then
local display_path="${current_path/#$HOME/~}"
display_path=$(truncate_path "$display_path")
display_path=$(compact_purge_scan_path "$display_path" "$max_path_len")
last_path="$display_path"
fi
@@ -291,4 +327,12 @@ main() {
show_cursor
}
if [[ "${MOLE_SKIP_MAIN:-0}" == "1" ]]; then
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
return 0
else
exit 0
fi
fi
main "$@"

View File

@@ -26,6 +26,7 @@ readonly PURGE_CONFIG_FILE="$HOME/.config/mole/purge_paths"
# Resolved search paths.
PURGE_SEARCH_PATHS=()
PURGE_CATEGORY_FULL_PATHS_ARRAY=()
# Project indicators for container detection.
# Monorepo indicators (higher priority)
@@ -155,6 +156,53 @@ load_purge_config() {
# Initialize paths on script load.
load_purge_config
format_purge_target_path() {
local path="$1"
echo "${path/#$HOME/~}"
}
compact_purge_menu_path() {
local path="$1"
local max_width="${2:-0}"
if ! [[ "$max_width" =~ ^[0-9]+$ ]] || [[ "$max_width" -lt 4 ]]; then
max_width=4
fi
local path_width
path_width=$(get_display_width "$path")
if [[ $path_width -le $max_width ]]; then
echo "$path"
return
fi
local tail=""
local remainder="$path"
local prefix_width=3
while [[ "$remainder" == */* ]]; do
local segment="/${remainder##*/}"
remainder="${remainder%/*}"
local candidate="${segment}${tail}"
local candidate_width
candidate_width=$(get_display_width "$candidate")
if [[ $((candidate_width + prefix_width)) -le $max_width ]]; then
tail="$candidate"
else
break
fi
done
if [[ -n "$tail" ]]; then
echo "...${tail}"
return
fi
local suffix_len=$((max_width - 3))
echo "...${path: -$suffix_len}"
}
# Args: $1 - directory path
# Determine whether a directory is a project root.
# This is used to safely allow cleaning direct-child artifacts when
@@ -542,7 +590,7 @@ select_purge_categories() {
term_height=24
fi
fi
local reserved=6
local reserved=8
local available=$((term_height - reserved))
if [[ $available -lt 3 ]]; then
echo 3
@@ -680,6 +728,13 @@ select_purge_categories() {
# Keep one blank line between the list and footer tips.
printf "%s\n" "$clear_line"
local current_index=$((top_index + cursor_pos))
local current_full_path="${PURGE_CATEGORY_FULL_PATHS_ARRAY[current_index]:-}"
if [[ -n "$current_full_path" ]]; then
printf "%s${GRAY}Full path:${NC} %s\n" "$clear_line" "$current_full_path"
printf "%s\n" "$clear_line"
fi
# Adaptive footer hints — mirrors menu_paginated.sh pattern
local _term_w
_term_w=$(tput cols 2> /dev/null || echo 80)
@@ -821,6 +876,7 @@ confirm_purge_cleanup() {
local item_count="${1:-0}"
local total_size_kb="${2:-0}"
local unknown_count="${3:-0}"
local -a selected_paths=("${@:4}")
[[ "$item_count" =~ ^[0-9]+$ ]] || item_count=0
[[ "$total_size_kb" =~ ^[0-9]+$ ]] || total_size_kb=0
@@ -839,6 +895,15 @@ confirm_purge_cleanup() {
unknown_hint=", ${unknown_count} ${unknown_text}"
fi
if [[ ${#selected_paths[@]} -gt 0 ]]; then
echo ""
echo -e "${GRAY}Selected paths:${NC}"
local selected_path=""
for selected_path in "${selected_paths[@]}"; do
echo " $selected_path"
done
fi
echo -ne "${PURPLE}${ICON_ARROW}${NC} Remove ${item_count} ${item_text}, ${size_display}${unknown_hint} ${GREEN}Enter${NC} confirm, ${GRAY}ESC${NC} cancel: "
drain_pending_input
local key=""
@@ -1179,7 +1244,7 @@ clean_project_artifacts() {
# Truncate project path if needed
local truncated_path
truncated_path=$(truncate_by_display_width "$project_path" "$available_width")
truncated_path=$(compact_purge_menu_path "$project_path" "$available_width")
local current_width
current_width=$(get_display_width "$truncated_path")
local char_count=${#truncated_path}
@@ -1193,6 +1258,7 @@ clean_project_artifacts() {
# Sizes are read from pre-computed results (parallel du calls launched above).
local -a raw_project_paths=()
local -a raw_artifact_types=()
local -a item_display_paths=()
local _sz_idx=0
for item in "${safe_to_clean[@]}"; do
local project_path
@@ -1232,6 +1298,7 @@ clean_project_artifacts() {
raw_project_paths+=("$project_path")
raw_artifact_types+=("$artifact_type")
item_paths+=("$item")
item_display_paths+=("$(format_purge_target_path "$item")")
item_sizes+=("$size_kb")
item_size_unknown_flags+=("$size_unknown")
item_recent_flags+=("$is_recent")
@@ -1349,8 +1416,10 @@ clean_project_artifacts() {
)
# Interactive selection (only if terminal is available)
PURGE_SELECTION_RESULT=""
PURGE_CATEGORY_FULL_PATHS_ARRAY=("${item_display_paths[@]}")
if [[ -t 0 ]]; then
if ! select_purge_categories "${menu_options[@]}"; then
PURGE_CATEGORY_FULL_PATHS_ARRAY=()
unset PURGE_CATEGORY_SIZES PURGE_RECENT_CATEGORIES PURGE_SELECTION_RESULT
return 1
fi
@@ -1367,12 +1436,14 @@ clean_project_artifacts() {
echo ""
echo -e "${GRAY}No items selected${NC}"
printf '\n'
PURGE_CATEGORY_FULL_PATHS_ARRAY=()
unset PURGE_CATEGORY_SIZES PURGE_RECENT_CATEGORIES PURGE_SELECTION_RESULT
return 0
fi
IFS=',' read -r -a selected_indices <<< "$PURGE_SELECTION_RESULT"
local selected_total_kb=0
local selected_unknown_count=0
local -a selected_display_paths=()
for idx in "${selected_indices[@]}"; do
local selected_size_kb="${item_sizes[idx]:-0}"
[[ "$selected_size_kb" =~ ^[0-9]+$ ]] || selected_size_kb=0
@@ -1380,16 +1451,19 @@ clean_project_artifacts() {
if [[ "${item_size_unknown_flags[idx]:-false}" == "true" ]]; then
selected_unknown_count=$((selected_unknown_count + 1))
fi
selected_display_paths+=("${item_display_paths[idx]}")
done
if [[ -t 0 ]]; then
if ! confirm_purge_cleanup "${#selected_indices[@]}" "$selected_total_kb" "$selected_unknown_count"; then
if ! confirm_purge_cleanup "${#selected_indices[@]}" "$selected_total_kb" "$selected_unknown_count" "${selected_display_paths[@]}"; then
echo -e "${GRAY}Purge cancelled${NC}"
printf '\n'
PURGE_CATEGORY_FULL_PATHS_ARRAY=()
unset PURGE_CATEGORY_SIZES PURGE_RECENT_CATEGORIES PURGE_SELECTION_RESULT
return 1
fi
fi
PURGE_CATEGORY_FULL_PATHS_ARRAY=()
# Clean selected items
echo ""
@@ -1398,8 +1472,8 @@ clean_project_artifacts() {
local dry_run_mode="${MOLE_DRY_RUN:-0}"
for idx in "${selected_indices[@]}"; do
local item_path="${item_paths[idx]}"
local artifact_type=$(basename "$item_path")
local project_path=$(get_project_path "$item_path")
local display_item_path
display_item_path=$(format_purge_target_path "$item_path")
local size_kb="${item_sizes[idx]}"
local size_unknown="${item_size_unknown_flags[idx]:-false}"
local size_human
@@ -1413,7 +1487,7 @@ clean_project_artifacts() {
continue
fi
if [[ -t 1 ]]; then
start_inline_spinner "Cleaning $project_path/$artifact_type..."
start_inline_spinner "Cleaning $display_item_path..."
fi
local removal_recorded=false
if [[ -e "$item_path" ]]; then
@@ -1431,9 +1505,9 @@ clean_project_artifacts() {
stop_inline_spinner
if [[ "$removal_recorded" == "true" ]]; then
if [[ "$dry_run_mode" == "1" ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} [DRY RUN] $project_path, $artifact_type${NC}, ${GREEN}$size_human${NC}"
echo -e "${GREEN}${ICON_SUCCESS}${NC} [DRY RUN] $display_item_path${NC}, ${GREEN}$size_human${NC}"
else
echo -e "${GREEN}${ICON_SUCCESS}${NC} $project_path, $artifact_type${NC}, ${GREEN}$size_human${NC}"
echo -e "${GREEN}${ICON_SUCCESS}${NC} $display_item_path${NC}, ${GREEN}$size_human${NC}"
fi
fi
fi

View File

@@ -109,6 +109,39 @@ setup() {
[[ "$result" == "ALLOWED" ]]
}
@test "compact_purge_scan_path keeps the tail of long purge paths visible" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_SKIP_MAIN=1 bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/bin/purge.sh"
compact_purge_scan_path "$HOME/projects/team/service/very/deep/component/node_modules" 32
EOF
[ "$status" -eq 0 ]
[[ "$output" == ".../deep/component/node_modules" ]]
}
@test "compact_purge_menu_path keeps the project tail visible" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/project.sh"
compact_purge_menu_path "$HOME/projects/team/service/very/deep/component/node_modules" 32
EOF
[ "$status" -eq 0 ]
[[ "$output" == ".../deep/component/node_modules" ]]
}
@test "format_purge_target_path rewrites home with tilde" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/project.sh"
format_purge_target_path "$HOME/www/app/node_modules"
EOF
[ "$status" -eq 0 ]
[[ "$output" == "~/www/app/node_modules" ]]
}
@test "filter_nested_artifacts: removes nested node_modules" {
mkdir -p "$HOME/www/project/node_modules/package/node_modules"
@@ -357,6 +390,20 @@ EOF
[ "$status" -eq 0 ]
}
@test "confirm_purge_cleanup shows selected paths" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/project.sh"
drain_pending_input() { :; }
confirm_purge_cleanup 2 1024 0 "~/www/app/node_modules" "~/www/app/dist" <<< ''
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Selected paths:"* ]]
[[ "$output" == *"~/www/app/node_modules"* ]]
[[ "$output" == *"~/www/app/dist"* ]]
}
@test "confirm_purge_cleanup cancels on ESC" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail