#!/bin/bash # App selection functionality set -euo pipefail # Note: get_display_width() is now defined in lib/core/ui.sh # Format app info for display format_app_display() { local display_name="$1" size="$2" last_used="$3" # Use common function from ui.sh to format last used time local compact_last_used compact_last_used=$(format_last_used_summary "$last_used") # Format size local size_str="Unknown" [[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size" # Calculate available width for app name based on terminal width # Accept pre-calculated max_name_width (5th param) to avoid recalculation in loops local terminal_width="${4:-$(tput cols 2> /dev/null || echo 80)}" local max_name_width="${5:-}" local available_width if [[ -n "$max_name_width" ]]; then # Use pre-calculated width from caller available_width=$max_name_width else # Fallback: calculate it (slower, but works for standalone calls) # Fixed elements: " ○ " (4) + " " (1) + size (9) + " | " (3) + max_last (7) = 24 local fixed_width=24 available_width=$((terminal_width - fixed_width)) # Dynamic minimum for better spacing on wide terminals local min_width=18 if [[ $terminal_width -ge 120 ]]; then min_width=48 elif [[ $terminal_width -ge 100 ]]; then min_width=38 elif [[ $terminal_width -ge 80 ]]; then min_width=25 fi [[ $available_width -lt $min_width ]] && available_width=$min_width [[ $available_width -gt 60 ]] && available_width=60 fi # Truncate long names if needed (based on display width, not char count) local truncated_name truncated_name=$(truncate_by_display_width "$display_name" "$available_width") # Get actual display width after truncation local current_display_width current_display_width=$(get_display_width "$truncated_name") # Calculate padding needed # Formula: char_count + (available_width - display_width) = padding to add local char_count=${#truncated_name} local padding_needed=$((available_width - current_display_width)) local printf_width=$((char_count + padding_needed)) # Use dynamic column width with corrected padding printf "%-*s %9s | %s" "$printf_width" "$truncated_name" "$size_str" "$compact_last_used" } # Global variable to store selection result (bash 3.2 compatible) MOLE_SELECTION_RESULT="" # Main app selection function # shellcheck disable=SC2154 # apps_data is set by caller select_apps_for_uninstall() { if [[ ${#apps_data[@]} -eq 0 ]]; then log_warning "No applications available for uninstallation" return 1 fi # Build menu options # Show loading for large lists (formatting can be slow due to width calculations) local app_count=${#apps_data[@]} local terminal_width=$(tput cols 2> /dev/null || echo 80) if [[ $app_count -gt 100 ]]; then if [[ -t 2 ]]; then printf "\rPreparing %d applications... " "$app_count" >&2 fi fi # Pre-scan to get actual max name width local max_name_width=0 for app_data in "${apps_data[@]}"; do IFS='|' read -r _ _ display_name _ _ _ _ <<< "$app_data" local name_width=$(get_display_width "$display_name") [[ $name_width -gt $max_name_width ]] && max_name_width=$name_width done # Constrain based on terminal width: fixed=24, min varies by terminal width, max=60 local fixed_width=24 local available=$((terminal_width - fixed_width)) # Dynamic minimum: wider terminals get larger minimum for better spacing local min_width=18 if [[ $terminal_width -ge 120 ]]; then min_width=48 # Wide terminals: very generous spacing elif [[ $terminal_width -ge 100 ]]; then min_width=38 # Medium-wide terminals: generous spacing elif [[ $terminal_width -ge 80 ]]; then min_width=25 # Standard terminals fi [[ $max_name_width -lt $min_width ]] && max_name_width=$min_width [[ $available -lt $max_name_width ]] && max_name_width=$available [[ $max_name_width -gt 60 ]] && max_name_width=60 local -a menu_options=() # Prepare metadata (comma-separated) for sorting/filtering inside the menu local epochs_csv="" local sizekb_csv="" local idx=0 for app_data in "${apps_data[@]}"; do # Keep extended field 7 (size_kb) if present IFS='|' read -r epoch _ display_name _ size last_used size_kb <<< "$app_data" menu_options+=("$(format_app_display "$display_name" "$size" "$last_used" "$terminal_width" "$max_name_width")") # Build csv lists (avoid trailing commas) if [[ $idx -eq 0 ]]; then epochs_csv="${epoch:-0}" sizekb_csv="${size_kb:-0}" else epochs_csv+=",${epoch:-0}" sizekb_csv+=",${size_kb:-0}" fi ((idx++)) done # Clear loading message if [[ $app_count -gt 100 ]]; then if [[ -t 2 ]]; then printf "\r\033[K" >&2 fi fi # Expose metadata for the paginated menu (optional inputs) # - MOLE_MENU_META_EPOCHS: numeric last_used_epoch per item # - MOLE_MENU_META_SIZEKB: numeric size in KB per item # The menu will gracefully fallback if these are unset or malformed. export MOLE_MENU_META_EPOCHS="$epochs_csv" export MOLE_MENU_META_SIZEKB="$sizekb_csv" # Optional: allow default sort override via env (date|name|size) # export MOLE_MENU_SORT_DEFAULT="${MOLE_MENU_SORT_DEFAULT:-date}" # Use paginated menu - result will be stored in MOLE_SELECTION_RESULT # Note: paginated_multi_select enters alternate screen and handles clearing MOLE_SELECTION_RESULT="" paginated_multi_select "Select Apps to Remove" "${menu_options[@]}" local exit_code=$? # Clean env leakage for safety unset MOLE_MENU_META_EPOCHS MOLE_MENU_META_SIZEKB # leave MOLE_MENU_SORT_DEFAULT untouched if user set it globally # Refresh signal handling if [[ $exit_code -eq 10 ]]; then return 10 fi if [[ $exit_code -ne 0 ]]; then return 1 fi if [[ -z "$MOLE_SELECTION_RESULT" ]]; then echo "No apps selected" return 1 fi # Build selected apps array (global variable in bin/uninstall.sh) selected_apps=() # Parse indices and build selected apps array IFS=',' read -r -a indices_array <<< "$MOLE_SELECTION_RESULT" for idx in "${indices_array[@]}"; do if [[ "$idx" =~ ^[0-9]+$ ]] && [[ $idx -ge 0 ]] && [[ $idx -lt ${#apps_data[@]} ]]; then selected_apps+=("${apps_data[idx]}") fi done return 0 } # Export function for external use if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then echo "This is a library file. Source it from other scripts." >&2 exit 1 fi