From 73f3d43308e4207d83652fa220e3a7a1f98ac81c Mon Sep 17 00:00:00 2001 From: Tw93 Date: Tue, 30 Dec 2025 14:38:16 +0800 Subject: [PATCH] feat: improve mo purge UX with pagination and smart project detection - Add pagination for project list based on terminal height - Show position indicator [5/25] when scrolling - Fix GB display for values < 1 (0.6GB instead of .6GB) - Improve project name detection by walking up to find project root - Change default editor from nano to vim --- lib/clean/project.sh | 129 ++++++++++++++++++++++++++++++++------ lib/manage/purge_paths.sh | 2 +- 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/lib/clean/project.sh b/lib/clean/project.sh index 1663b2a..6e5be63 100644 --- a/lib/clean/project.sh +++ b/lib/clean/project.sh @@ -387,6 +387,36 @@ select_purge_categories() { if [[ $total_items -eq 0 ]]; then return 1 fi + + # Calculate items per page based on terminal height + # Reserved: header(2) + blank(2) + footer(1) = 5 rows + _get_items_per_page() { + local term_height=24 + if [[ -t 0 ]] || [[ -t 2 ]]; then + term_height=$(stty size /dev/null | awk '{print $1}') + fi + if [[ -z "$term_height" || $term_height -le 0 ]]; then + if command -v tput > /dev/null 2>&1; then + term_height=$(tput lines 2>/dev/null || echo "24") + else + term_height=24 + fi + fi + local reserved=6 + local available=$((term_height - reserved)) + if [[ $available -lt 3 ]]; then + echo 3 + elif [[ $available -gt 50 ]]; then + echo 50 + else + echo "$available" + fi + } + + local items_per_page=$(_get_items_per_page) + local cursor_pos=0 + local top_index=0 + # Initialize selection (all selected by default, except recent ones) local -a selected=() IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}" @@ -398,17 +428,16 @@ select_purge_categories() { selected[i]=true fi done - local cursor_pos=0 local original_stty="" if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then - original_stty=$(stty -g 2> /dev/null || echo "") + original_stty=$(stty -g 2>/dev/null || echo "") fi # Terminal control functions restore_terminal() { trap - EXIT INT TERM show_cursor if [[ -n "${original_stty:-}" ]]; then - stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true + stty "${original_stty}" 2>/dev/null || stty sane 2>/dev/null || true fi } # shellcheck disable=SC2329 @@ -417,6 +446,9 @@ select_purge_categories() { exit 130 } draw_menu() { + # Recalculate items_per_page dynamically to handle window resize + items_per_page=$(_get_items_per_page) + printf "\033[H" # Calculate total size of selected items for header local selected_size=0 @@ -429,29 +461,54 @@ select_purge_categories() { fi done local selected_gb - selected_gb=$(echo "scale=1; $selected_size/1024/1024" | bc) + selected_gb=$(printf "%.1f" "$(echo "scale=2; $selected_size/1024/1024" | bc)") + + # Show position indicator if scrolling is needed + local scroll_indicator="" + if [[ $total_items -gt $items_per_page ]]; then + local current_pos=$((top_index + cursor_pos + 1)) + scroll_indicator=" ${GRAY}[${current_pos}/${total_items}]${NC}" + fi + printf "%s\n" "$clear_line" - printf "%s${PURPLE_BOLD}Select Categories to Clean${NC} ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line" + printf "%s${PURPLE_BOLD}Select Categories to Clean${NC}%s ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line" "$scroll_indicator" printf "%s\n" "$clear_line" + IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}" - for ((i = 0; i < total_items; i++)); do + + # Calculate visible range + local visible_count=$((total_items - top_index)) + [[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page + local end_index=$((top_index + visible_count)) + + # Draw only visible items + for ((i = top_index; i < end_index; i++)); do local checkbox="$ICON_EMPTY" [[ ${selected[i]} == true ]] && checkbox="$ICON_SOLID" local recent_marker="" [[ ${recent_flags[i]:-false} == "true" ]] && recent_marker=" ${GRAY}| Recent${NC}" - if [[ $i -eq $cursor_pos ]]; then + local rel_pos=$((i - top_index)) + if [[ $rel_pos -eq $cursor_pos ]]; then printf "%s${CYAN}${ICON_ARROW} %s %s%s${NC}\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker" else printf "%s %s %s%s\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker" fi done + + # Fill empty slots to clear previous content + local items_shown=$visible_count + for ((i = items_shown; i < items_per_page; i++)); do + printf "%s\n" "$clear_line" + done + printf "%s\n" "$clear_line" + printf "%s${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space Select | Enter Confirm | A All | I Invert | Q Quit${NC}\n" "$clear_line" } trap restore_terminal EXIT trap handle_interrupt INT TERM # Preserve interrupt character for Ctrl-C - stty -echo -icanon intr ^C 2> /dev/null || true + stty -echo -icanon intr ^C 2>/dev/null || true hide_cursor if [[ -t 1 ]]; then clear_screen @@ -470,10 +527,24 @@ select_purge_categories() { IFS= read -r -s -n1 -t 1 key3 || key3="" case "$key3" in A) # Up arrow - ((cursor_pos > 0)) && ((cursor_pos--)) + if [[ $cursor_pos -gt 0 ]]; then + ((cursor_pos--)) + elif [[ $top_index -gt 0 ]]; then + ((top_index--)) + fi ;; B) # Down arrow - ((cursor_pos < total_items - 1)) && ((cursor_pos++)) + local absolute_index=$((top_index + cursor_pos)) + local last_index=$((total_items - 1)) + if [[ $absolute_index -lt $last_index ]]; then + local visible_count=$((total_items - top_index)) + [[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page + if [[ $cursor_pos -lt $((visible_count - 1)) ]]; then + ((cursor_pos++)) + elif [[ $((top_index + visible_count)) -lt $total_items ]]; then + ((top_index++)) + fi + fi ;; esac else @@ -483,10 +554,11 @@ select_purge_categories() { fi ;; " ") # Space - toggle current item - if [[ ${selected[cursor_pos]} == true ]]; then - selected[cursor_pos]=false + local idx=$((top_index + cursor_pos)) + if [[ ${selected[idx]} == true ]]; then + selected[idx]=false else - selected[cursor_pos]=true + selected[idx]=true fi ;; "a" | "A") # Select all @@ -608,10 +680,31 @@ clean_project_artifacts() { local -a item_recent_flags=() # Helper to get project name from path # For ~/www/pake/src-tauri/target -> returns "pake" - # For ~/www/project/node_modules/xxx/node_modules -> returns "project" + # For ~/work/code/MyProject/node_modules -> returns "MyProject" + # Strategy: Find the nearest ancestor directory containing a project indicator file get_project_name() { local path="$1" - # Find the project root by looking for direct child of search paths + local artifact_name + artifact_name=$(basename "$path") + + # Start from the parent of the artifact and walk up + local current_dir + current_dir=$(dirname "$path") + + while [[ "$current_dir" != "/" && "$current_dir" != "$HOME" && -n "$current_dir" ]]; do + # Check if current directory contains any project indicator + for indicator in "${PROJECT_INDICATORS[@]}"; do + if [[ -e "$current_dir/$indicator" ]]; then + # Found a project root, return its name + basename "$current_dir" + return 0 + fi + done + # Move up one level + current_dir=$(dirname "$current_dir") + done + + # Fallback: try the old logic (first directory under search root) local search_roots=() if [[ ${#PURGE_SEARCH_PATHS[@]} -gt 0 ]]; then search_roots=("${PURGE_SEARCH_PATHS[@]}") @@ -619,17 +712,15 @@ clean_project_artifacts() { search_roots=("$HOME/www" "$HOME/dev" "$HOME/Projects") fi for root in "${search_roots[@]}"; do - # Normalize trailing slash for consistent matching root="${root%/}" if [[ -n "$root" && "$path" == "$root/"* ]]; then - # Remove root prefix and get first directory component local relative_path="${path#"$root"/}" - # Extract first directory name echo "$relative_path" | cut -d'/' -f1 return 0 fi done - # Fallback: use grandparent directory + + # Final fallback: use grandparent directory dirname "$(dirname "$path")" | xargs basename } # Format display with alignment (like app_selector) diff --git a/lib/manage/purge_paths.sh b/lib/manage/purge_paths.sh index 66ae9a7..bd163bd 100644 --- a/lib/manage/purge_paths.sh +++ b/lib/manage/purge_paths.sh @@ -92,7 +92,7 @@ manage_purge_paths() { echo "" # Open in editor - local editor="${EDITOR:-${VISUAL:-nano}}" + local editor="${EDITOR:-${VISUAL:-vim}}" echo -e "Opening in ${CYAN}$editor${NC}..." echo -e "${GRAY}Save and exit to apply changes. Leave empty to use defaults.${NC}" echo ""