From 55f6bd352f634200df7bea444b7cae5406e56902 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sat, 11 Oct 2025 22:43:18 +0800 Subject: [PATCH] Well-structured output --- bin/analyze.sh | 6 ++-- bin/clean.sh | 77 +++++++++++++++++++--------------------- bin/uninstall.sh | 6 +++- lib/batch_uninstall.sh | 60 ++++++++++++++++++++----------- lib/common.sh | 34 ++++++++++++++++-- lib/paginated_menu.sh | 8 +++++ lib/whitelist_manager.sh | 4 +-- 7 files changed, 126 insertions(+), 69 deletions(-) diff --git a/bin/analyze.sh b/bin/analyze.sh index 7a61e1f..c8e7fdc 100755 --- a/bin/analyze.sh +++ b/bin/analyze.sh @@ -2048,7 +2048,8 @@ interactive_drill_down() { fi echo "" - echo " ${GRAY}Press ${NC}${GREEN}ENTER${NC}${GRAY} to confirm, ${NC}${YELLOW}ESC/Q${NC}${GRAY} to cancel${NC}" + echo -e " ${PURPLE}☛${NC} Press ${GREEN}Enter${NC} to delete, ${GRAY}ESC${NC} to cancel" + echo "" # Read confirmation local confirm @@ -2087,8 +2088,7 @@ interactive_drill_down() { fi if [[ "$delete_success" == "true" ]]; then - echo " ${GREEN}✓ Deleted successfully${NC}" - echo " ${GRAY}Freed: $human_size${NC}" + echo " ${GREEN}✓ Deleted successfully (freed $human_size)${NC}" sleep 0.8 # Clear cache to force rescan diff --git a/bin/clean.sh b/bin/clean.sh index f816105..f06535f 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -363,23 +363,30 @@ start_cleanup() { if [[ -t 0 ]]; then echo "" - echo -ne "${BLUE}${ICON_SETTINGS}${NC} ${BLUE}System cleanup?${NC} ${GRAY}Enter to continue, any key to skip${NC} " + echo -ne "${PURPLE}☛${NC} System caches need sudo — ${GREEN}Enter${NC} continue, other key skip: " # Use IFS= and read without -n to allow Ctrl+C to work properly IFS= read -r -s -n 1 choice local read_status=$? - echo "" # If read was interrupted (Ctrl+C), exit cleanly if [[ $read_status -ne 0 ]]; then + echo "" exit 130 fi - # Enter or y = yes, do system cleanup - if [[ -z "$choice" ]] || [[ "$choice" == $'\n' ]] || [[ "$choice" =~ ^[Yy]$ ]]; then + if [[ "$choice" == $'\e' ]]; then + echo -e " ${GRAY}Cancelled${NC}" + exit 0 + fi + + # Enter = yes, do system cleanup + if [[ -z "$choice" ]] || [[ "$choice" == $'\n' ]]; then + printf "\r\033[K" # Clear the prompt line if request_sudo_access "System cleanup requires admin access"; then SYSTEM_CLEAN=true echo -e "${GREEN}✓${NC} Admin access granted" + echo "" # Start sudo keepalive with error handling ( local retry_count=0 @@ -404,9 +411,10 @@ start_cleanup() { echo -e "${YELLOW}Authentication failed${NC}, continuing with user-level cleanup" fi else - # Any other key = no system cleanup + # ESC or other key = no system cleanup SYSTEM_CLEAN=false - echo -e "${BLUE}${ICON_EMPTY}${NC} Skipped system cleanup, user-level only" + echo "" + echo -e "${GRAY}Skipped system cleanup, user-level only${NC}" fi else SYSTEM_CLEAN=false @@ -1350,7 +1358,7 @@ perform_cleanup() { local summary_heading="" local summary_status="success" if [[ "$DRY_RUN" == "true" ]]; then - summary_heading="Dry run complete" + summary_heading="Dry run complete - no changes made" else summary_heading="Cleanup complete" fi @@ -1362,45 +1370,31 @@ perform_cleanup() { freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}') if [[ "$DRY_RUN" == "true" ]]; then - summary_details+=("Potential reclaimable space: ${GREEN}${freed_gb}GB${NC}") - summary_details+=("No changes were made in this mode.") + # Build compact stats line for dry run + local stats="Potential space: ${GREEN}${freed_gb}GB${NC}" + [[ $files_cleaned -gt 0 ]] && stats+=" | Files: $files_cleaned" + [[ $total_items -gt 0 ]] && stats+=" | Categories: $total_items" + [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" + summary_details+=("$stats") + summary_details+=("Use ${GRAY}mo clean --whitelist${NC} to protect caches") else summary_details+=("Space freed: ${GREEN}${freed_gb}GB${NC}") - fi - summary_details+=("Free space now: $(get_free_space)") + summary_details+=("Free space now: $(get_free_space)") - if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then - local stats - if [[ "$DRY_RUN" == "true" ]]; then - stats="Files to clean: $files_cleaned | Categories: $total_items" - else - stats="Files cleaned: $files_cleaned | Categories: $total_items" + if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then + local stats="Files cleaned: $files_cleaned | Categories: $total_items" + [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" + summary_details+=("$stats") + elif [[ $files_cleaned -gt 0 ]]; then + local stats="Files cleaned: $files_cleaned" + [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" + summary_details+=("$stats") + elif [[ $total_items -gt 0 ]]; then + local stats="Categories: $total_items" + [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" + summary_details+=("$stats") fi - [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" - summary_details+=("$stats") - elif [[ $files_cleaned -gt 0 ]]; then - local stats - if [[ "$DRY_RUN" == "true" ]]; then - stats="Files to clean: $files_cleaned" - else - stats="Files cleaned: $files_cleaned" - fi - [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" - summary_details+=("$stats") - elif [[ $total_items -gt 0 ]]; then - local stats - if [[ "$DRY_RUN" == "true" ]]; then - stats="Categories to review: $total_items" - else - stats="Categories: $total_items" - fi - [[ $whitelist_skipped_count -gt 0 ]] && stats+=" | Protected: $whitelist_skipped_count" - summary_details+=("$stats") - fi - if [[ "$DRY_RUN" == "true" ]]; then - summary_details+=("Protect specific caches anytime with: mo clean --whitelist") - else if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; then local movies movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}') @@ -1420,6 +1414,7 @@ perform_cleanup() { fi print_summary_block "$summary_status" "$summary_heading" "${summary_details[@]}" + printf '\n' } diff --git a/bin/uninstall.sh b/bin/uninstall.sh index 03a0bf2..8746d4c 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -501,7 +501,6 @@ uninstall_applications() { done # Show final summary - echo "" echo -e "${PURPLE}▶ Uninstallation Summary${NC}" if [[ $total_size_freed -gt 0 ]]; then @@ -527,6 +526,11 @@ cleanup() { leave_alt_screen unset MOLE_ALT_SCREEN_ACTIVE fi + if [[ -n "${sudo_keepalive_pid:-}" ]]; then + kill "$sudo_keepalive_pid" 2>/dev/null || true + wait "$sudo_keepalive_pid" 2>/dev/null || true + sudo_keepalive_pid="" + fi show_cursor exit "${1:-0}" } diff --git a/lib/batch_uninstall.sh b/lib/batch_uninstall.sh index 1c80ecd..e81026f 100755 --- a/lib/batch_uninstall.sh +++ b/lib/batch_uninstall.sh @@ -87,23 +87,36 @@ batch_uninstall_applications() { local remaining=$((file_count - max_files)) echo -e " ${GRAY} ... and ${remaining} more files${NC}" fi - echo "" done # Show summary and get batch confirmation first (before asking for password) local app_total=${#selected_apps[@]} local app_text="app" [[ $app_total -gt 1 ]] && app_text="apps" + + echo "" + local removal_note="Remove ${app_total} ${app_text}" + [[ -n "$size_display" ]] && removal_note+=" (${size_display})" if [[ ${#running_apps[@]} -gt 0 ]]; then - echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Force quit: ${running_apps[*]} | Enter=go / ESC=q: " - else - echo -n "${BLUE}${ICON_CONFIRM}${NC} Remove ${app_total} ${app_text} | ${size_display} | Enter=go / ESC=q: " + removal_note+=" - will force quit: ${running_apps[*]}" fi + echo -ne "${PURPLE}☛${NC} ${removal_note}. Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: " + IFS= read -r -s -n1 key || key="" case "$key" in - $'\e'|q|Q) echo ""; return 0 ;; - ""|$'\n'|$'\r'|y|Y) echo "" ;; - *) echo ""; return 0 ;; + $'\e'|q|Q) + echo "" + echo "" + return 0 + ;; + ""|$'\n'|$'\r'|y|Y) + printf "\r\033[K" # Clear the prompt line + ;; + *) + echo "" + echo "" + return 0 + ;; esac # User confirmed, now request sudo access if needed @@ -117,19 +130,9 @@ batch_uninstall_applications() { fi fi (while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null) & - local sudo_keepalive_pid=$! - local _trap_cleanup_cmd="kill $sudo_keepalive_pid 2>/dev/null || true; wait $sudo_keepalive_pid 2>/dev/null || true" - for signal in EXIT INT TERM; do - local existing_trap; existing_trap=$(trap -p "$signal" | awk -F"'" '{print $2}') - if [[ -n "$existing_trap" ]]; then - trap "$existing_trap; $_trap_cleanup_cmd" "$signal" - else - trap "$_trap_cleanup_cmd" "$signal" - fi - done + sudo_keepalive_pid=$! fi - echo "" if [[ -t 1 ]]; then start_inline_spinner "Uninstalling apps..."; fi # Force quit running apps first (batch) @@ -183,8 +186,23 @@ batch_uninstall_applications() { if [[ $success_count -gt 0 ]]; then local success_list="${success_items[*]}" - summary_details+=("Removed: ${GREEN}${success_list}${NC}") - summary_details+=("Freed space: ${GREEN}${freed_display}${NC}") + local success_text="app" + [[ $success_count -gt 1 ]] && success_text="apps" + local success_line="Removed ${success_count} ${success_text}" + if [[ -n "$freed_display" ]]; then + success_line+=", freed ${GREEN}${freed_display}${NC}" + fi + if [[ -n "$success_list" ]]; then + local -a formatted_apps=() + for app_name in "${success_items[@]}"; do + formatted_apps+=("${GREEN}${app_name}${NC}") + done + if [[ ${#formatted_apps[@]} -gt 0 ]]; then + local IFS=', ' + success_line+=": ${formatted_apps[*]}" + fi + fi + summary_details+=("$success_line") fi if [[ $failed_count -gt 0 ]]; then @@ -216,6 +234,7 @@ batch_uninstall_applications() { fi print_summary_block "$summary_status" "Uninstall complete" "${summary_details[@]}" + printf '\n' if [[ ${#dock_cleanup_paths[@]} -gt 0 ]]; then remove_apps_from_dock "${dock_cleanup_paths[@]}" @@ -225,6 +244,7 @@ batch_uninstall_applications() { if [[ -n "${sudo_keepalive_pid:-}" ]]; then kill "$sudo_keepalive_pid" 2>/dev/null || true wait "$sudo_keepalive_pid" 2>/dev/null || true + sudo_keepalive_pid="" fi ((total_size_cleaned += total_size_freed)) diff --git a/lib/common.sh b/lib/common.sh index e57c5eb..360daf6 100755 --- a/lib/common.sh +++ b/lib/common.sh @@ -28,7 +28,7 @@ readonly ICON_ERROR="✗" # Error readonly ICON_EMPTY="○" # Empty state readonly ICON_LIST="-" # List item readonly ICON_MENU="▸" # Menu item -readonly ICON_SYSTEM="☯︎" # System/Architecture info +readonly ICON_SYSTEM="❤︎" # System/Architecture info readonly ICON_SETTINGS="⚙" # Settings/Configuration # Spinner character helpers (ASCII by default, overridable via env) @@ -741,7 +741,37 @@ clean_tool_cache() { } # ============================================================================ -# Confirmation prompt abstraction (Enter=confirm ESC/q=cancel) +# Unified confirmation prompt with consistent style +# ============================================================================ + +# Unified action prompt +# Usage: prompt_action "action" "cancel_text" -> returns 0 for yes, 1 for no +# Example: prompt_action "enable" "quit" -> "☛ Press Enter to enable, ESC to quit: " +prompt_action() { + local action="$1" + local cancel="${2:-cancel}" + + echo "" + echo -ne "${PURPLE}☛${NC} Press ${GREEN}Enter${NC} to ${action}, ${GRAY}ESC${NC} to ${cancel}: " + IFS= read -r -s -n1 key || key="" + + case "$key" in + $'\e') # ESC + echo "" + return 1 + ;; + ""|$'\n'|$'\r') # Enter + printf "\r\033[K" # Clear the prompt line + return 0 + ;; + *) + echo "" + return 1 + ;; + esac +} + +# Legacy confirmation prompt (kept for compatibility) # confirm_prompt "Message" -> 0 yes, 1 no confirm_prompt() { local message="$1" diff --git a/lib/paginated_menu.sh b/lib/paginated_menu.sh index 8563ec5..be5d53f 100755 --- a/lib/paginated_menu.sh +++ b/lib/paginated_menu.sh @@ -262,6 +262,14 @@ EOF fi done + if [[ ${#selected_indices[@]} -eq 0 ]]; then + local default_idx=$((top_index + cursor_pos)) + if [[ $default_idx -ge 0 && $default_idx -lt $total_items ]]; then + selected[default_idx]=true + selected_indices=("$default_idx") + fi + fi + local final_result="" if [[ ${#selected_indices[@]} -gt 0 ]]; then local IFS=',' diff --git a/lib/whitelist_manager.sh b/lib/whitelist_manager.sh index e1c3fa7..7e4a595 100755 --- a/lib/whitelist_manager.sh +++ b/lib/whitelist_manager.sh @@ -179,7 +179,6 @@ manage_whitelist_categories() { echo "" echo -e "${PURPLE}Whitelist Manager${NC}" echo "" - echo -e "${GRAY}Select caches to protect from cleanup.${NC}" echo "" # Load currently enabled patterns from both sources @@ -248,7 +247,7 @@ manage_whitelist_categories() { fi MOLE_SELECTION_RESULT="" - paginated_multi_select "Select caches to protect" "${menu_options[@]}" + paginated_multi_select "Whitelist Manager – Select caches to protect" "${menu_options[@]}" unset MOLE_PRESELECTED_INDICES local exit_code=$? @@ -283,6 +282,7 @@ manage_whitelist_categories() { print_summary_block "success" \ "Protected ${#selected_patterns[@]} cache(s)" \ "Saved to ${WHITELIST_CONFIG}" + printf '\n' }