mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 14:26:46 +00:00
feat: localize app names based on system language and improve UI display width calculation for CJK characters with loading indicator
This commit is contained in:
164
bin/uninstall.sh
164
bin/uninstall.sh
@@ -88,7 +88,13 @@ scan_applications() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cache miss - show scanning feedback below
|
||||
# Cache miss - prepare for scanning
|
||||
local inline_loading=false
|
||||
if [[ -t 1 && -t 2 ]]; then
|
||||
inline_loading=true
|
||||
# Clear screen for inline loading
|
||||
printf "\033[2J\033[H" >&2
|
||||
fi
|
||||
|
||||
local temp_file
|
||||
temp_file=$(create_temp_file)
|
||||
@@ -97,11 +103,7 @@ scan_applications() {
|
||||
local current_epoch
|
||||
current_epoch=$(date "+%s")
|
||||
|
||||
# Spinner for scanning feedback (simple ASCII for compatibility)
|
||||
local spinner_chars="|/-\\"
|
||||
local spinner_idx=0
|
||||
|
||||
# First pass: quickly collect all valid app paths and bundle IDs
|
||||
# First pass: quickly collect all valid app paths and bundle IDs (NO mdls calls)
|
||||
local -a app_data_tuples=()
|
||||
while IFS= read -r -d '' app_path; do
|
||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||
@@ -118,78 +120,19 @@ scan_applications() {
|
||||
continue
|
||||
fi
|
||||
|
||||
# Try to get English name from bundle info, fallback to folder name
|
||||
# Get bundle ID only (fast, no mdls calls in first pass)
|
||||
local bundle_id="unknown"
|
||||
local display_name="$app_name"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "unknown")
|
||||
|
||||
# Try to get English name from bundle info
|
||||
local bundle_executable
|
||||
bundle_executable=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null)
|
||||
|
||||
# Smart display name selection - prefer descriptive names over generic ones
|
||||
local candidates=()
|
||||
|
||||
# Get all potential names
|
||||
local bundle_display_name
|
||||
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
local bundle_name
|
||||
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
|
||||
# Check if executable name is generic/technical (should be avoided)
|
||||
local is_generic_executable=false
|
||||
if [[ -n "$bundle_executable" ]]; then
|
||||
case "$bundle_executable" in
|
||||
"pake" | "Electron" | "electron" | "nwjs" | "node" | "helper" | "main" | "app" | "binary")
|
||||
is_generic_executable=true
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Priority order for name selection:
|
||||
# 1. App folder name (if ASCII and descriptive) - often the most complete name
|
||||
if [[ "$app_name" =~ ^[A-Za-z0-9\ ._-]+$ && ${#app_name} -gt 3 ]]; then
|
||||
candidates+=("$app_name")
|
||||
fi
|
||||
|
||||
# 2. CFBundleDisplayName (if meaningful and ASCII)
|
||||
if [[ -n "$bundle_display_name" && "$bundle_display_name" =~ ^[A-Za-z0-9\ ._-]+$ ]]; then
|
||||
candidates+=("$bundle_display_name")
|
||||
fi
|
||||
|
||||
# 3. CFBundleName (if meaningful and ASCII)
|
||||
if [[ -n "$bundle_name" && "$bundle_name" =~ ^[A-Za-z0-9\ ._-]+$ && "$bundle_name" != "$bundle_display_name" ]]; then
|
||||
candidates+=("$bundle_name")
|
||||
fi
|
||||
|
||||
# 4. CFBundleExecutable (only if not generic and ASCII)
|
||||
if [[ -n "$bundle_executable" && "$bundle_executable" =~ ^[A-Za-z0-9._-]+$ && "$is_generic_executable" == false ]]; then
|
||||
candidates+=("$bundle_executable")
|
||||
fi
|
||||
|
||||
# 5. Fallback to non-ASCII names if no ASCII found
|
||||
if [[ ${#candidates[@]} -eq 0 ]]; then
|
||||
[[ -n "$bundle_display_name" ]] && candidates+=("$bundle_display_name")
|
||||
[[ -n "$bundle_name" && "$bundle_name" != "$bundle_display_name" ]] && candidates+=("$bundle_name")
|
||||
candidates+=("$app_name")
|
||||
fi
|
||||
|
||||
# Select the first (best) candidate
|
||||
display_name="${candidates[0]:-$app_name}"
|
||||
|
||||
# Apply brand name mapping from common.sh
|
||||
display_name="$(get_brand_name "$display_name")"
|
||||
fi
|
||||
|
||||
# Skip system critical apps (input methods, system components)
|
||||
# Note: Paid apps like CleanMyMac, 1Password are NOT protected here - users can uninstall them
|
||||
if should_protect_from_uninstall "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Store tuple: app_path|app_name|bundle_id|display_name
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}|${display_name}")
|
||||
# Store tuple: app_path|app_name|bundle_id (display_name will be resolved in parallel later)
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}")
|
||||
done < <(
|
||||
# Scan both system and user application directories
|
||||
# Using maxdepth 3 to find apps in subdirectories (e.g., Adobe apps in /Applications/Adobe X/)
|
||||
@@ -209,11 +152,7 @@ scan_applications() {
|
||||
max_parallel=32
|
||||
fi
|
||||
local pids=()
|
||||
local inline_loading=false
|
||||
if [[ "${MOLE_INLINE_LOADING:-}" == "1" || "${MOLE_INLINE_LOADING:-}" == "true" ]]; then
|
||||
inline_loading=true
|
||||
printf "\033[H" >&2 # Position cursor at top of screen
|
||||
fi
|
||||
# inline_loading variable already set above (line ~92)
|
||||
|
||||
# Process app metadata extraction function
|
||||
process_app_metadata() {
|
||||
@@ -221,7 +160,35 @@ scan_applications() {
|
||||
local output_file="$2"
|
||||
local current_epoch="$3"
|
||||
|
||||
IFS='|' read -r app_path app_name bundle_id display_name <<< "$app_data_tuple"
|
||||
IFS='|' read -r app_path app_name bundle_id <<< "$app_data_tuple"
|
||||
|
||||
# Get localized display name (moved from first pass for better performance)
|
||||
local display_name="$app_name"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
# Try to get localized name from system metadata (best for i18n)
|
||||
local md_display_name
|
||||
md_display_name=$(run_with_timeout 0.05 mdls -name kMDItemDisplayName -raw "$app_path" 2> /dev/null || echo "")
|
||||
|
||||
# Get bundle names
|
||||
local bundle_display_name
|
||||
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
local bundle_name
|
||||
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
|
||||
# Priority order for name selection (prefer localized names):
|
||||
# 1. System metadata display name (kMDItemDisplayName) - respects system language
|
||||
# 2. CFBundleDisplayName - usually localized
|
||||
# 3. CFBundleName - fallback
|
||||
# 4. App folder name - last resort
|
||||
|
||||
if [[ -n "$md_display_name" && "$md_display_name" != "(null)" && "$md_display_name" != "$app_name" ]]; then
|
||||
display_name="$md_display_name"
|
||||
elif [[ -n "$bundle_display_name" && "$bundle_display_name" != "(null)" ]]; then
|
||||
display_name="$bundle_display_name"
|
||||
elif [[ -n "$bundle_name" && "$bundle_name" != "(null)" ]]; then
|
||||
display_name="$bundle_name"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parallel size calculation
|
||||
local app_size="N/A"
|
||||
@@ -293,9 +260,9 @@ scan_applications() {
|
||||
local completed=$(cat "$progress_file" 2> /dev/null || echo 0)
|
||||
local c="${spinner_chars:$((i % 4)):1}"
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
printf "\033[H\033[2K%s Scanning applications... %d/%d\n" "$c" "$completed" "$total_apps" >&2
|
||||
else
|
||||
echo -ne "\r\033[K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
printf "\r\033[K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
fi
|
||||
((i++))
|
||||
sleep 0.1 2> /dev/null || sleep 1
|
||||
@@ -346,12 +313,30 @@ scan_applications() {
|
||||
fi
|
||||
|
||||
# Sort by last used (oldest first) and cache the result
|
||||
# Show brief processing message for large app lists
|
||||
if [[ $total_apps -gt 50 ]]; then
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2KProcessing %d applications...\n" "$total_apps" >&2
|
||||
else
|
||||
printf "\rProcessing %d applications... " "$total_apps" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
sort -t'|' -k1,1n "$temp_file" > "${temp_file}.sorted" || {
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
}
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Clear processing message
|
||||
if [[ $total_apps -gt 50 ]]; then
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2K" >&2
|
||||
else
|
||||
printf "\r\033[K" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Save to cache (simplified - no metadata)
|
||||
cp "${temp_file}.sorted" "$cache_file" 2> /dev/null || true
|
||||
|
||||
@@ -555,18 +540,22 @@ main() {
|
||||
# Show selected apps with clean alignment
|
||||
echo -e "${BLUE}${ICON_CONFIRM}${NC} Selected ${selection_count} app(s):"
|
||||
local -a summary_rows=()
|
||||
local max_name_width=0
|
||||
local max_name_display_width=0
|
||||
local max_size_width=0
|
||||
local name_trunc_limit=30
|
||||
|
||||
for selected_app in "${selected_apps[@]}"; do
|
||||
IFS='|' read -r epoch app_path app_name bundle_id size last_used size_kb <<< "$selected_app"
|
||||
|
||||
local display_name="$app_name"
|
||||
if [[ ${#display_name} -gt $name_trunc_limit ]]; then
|
||||
display_name="${display_name:0:$((name_trunc_limit - 3))}..."
|
||||
fi
|
||||
[[ ${#display_name} -gt $max_name_width ]] && max_name_width=${#display_name}
|
||||
# Truncate by display width if needed
|
||||
local display_name
|
||||
display_name=$(truncate_by_display_width "$app_name" "$name_trunc_limit")
|
||||
|
||||
# Get actual display width
|
||||
local current_width
|
||||
current_width=$(get_display_width "$display_name")
|
||||
|
||||
[[ $current_width -gt $max_name_display_width ]] && max_name_display_width=$current_width
|
||||
|
||||
local size_display="$size"
|
||||
if [[ -z "$size_display" || "$size_display" == "0" || "$size_display" == "N/A" ]]; then
|
||||
@@ -580,13 +569,20 @@ main() {
|
||||
summary_rows+=("$display_name|$size_display|$last_display")
|
||||
done
|
||||
|
||||
((max_name_width < 16)) && max_name_width=16
|
||||
((max_name_display_width < 16)) && max_name_display_width=16
|
||||
((max_size_width < 5)) && max_size_width=5
|
||||
|
||||
local index=1
|
||||
for row in "${summary_rows[@]}"; do
|
||||
IFS='|' read -r name_cell size_cell last_cell <<< "$row"
|
||||
printf "%d. %-*s %*s | Last: %s\n" "$index" "$max_name_width" "$name_cell" "$max_size_width" "$size_cell" "$last_cell"
|
||||
# Calculate printf width based on actual display width
|
||||
local name_display_width
|
||||
name_display_width=$(get_display_width "$name_cell")
|
||||
local name_char_count=${#name_cell}
|
||||
local padding_needed=$((max_name_display_width - name_display_width))
|
||||
local printf_name_width=$((name_char_count + padding_needed))
|
||||
|
||||
printf "%d. %-*s %*s | Last: %s\n" "$index" "$printf_name_width" "$name_cell" "$max_size_width" "$size_cell" "$last_cell"
|
||||
((index++))
|
||||
done
|
||||
|
||||
|
||||
@@ -88,7 +88,13 @@ scan_applications() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cache miss - show scanning feedback below
|
||||
# Cache miss - prepare for scanning
|
||||
local inline_loading=false
|
||||
if [[ -t 1 && -t 2 ]]; then
|
||||
inline_loading=true
|
||||
# Clear screen for inline loading
|
||||
printf "\033[2J\033[H" >&2
|
||||
fi
|
||||
|
||||
local temp_file
|
||||
temp_file=$(create_temp_file)
|
||||
@@ -97,11 +103,7 @@ scan_applications() {
|
||||
local current_epoch
|
||||
current_epoch=$(date "+%s")
|
||||
|
||||
# Spinner for scanning feedback (simple ASCII for compatibility)
|
||||
local spinner_chars="|/-\\"
|
||||
local spinner_idx=0
|
||||
|
||||
# First pass: quickly collect all valid app paths and bundle IDs
|
||||
# First pass: quickly collect all valid app paths and bundle IDs (NO mdls calls)
|
||||
local -a app_data_tuples=()
|
||||
while IFS= read -r -d '' app_path; do
|
||||
if [[ ! -e "$app_path" ]]; then continue; fi
|
||||
@@ -118,78 +120,19 @@ scan_applications() {
|
||||
continue
|
||||
fi
|
||||
|
||||
# Try to get English name from bundle info, fallback to folder name
|
||||
# Get bundle ID only (fast, no mdls calls in first pass)
|
||||
local bundle_id="unknown"
|
||||
local display_name="$app_name"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "unknown")
|
||||
|
||||
# Try to get English name from bundle info
|
||||
local bundle_executable
|
||||
bundle_executable=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null)
|
||||
|
||||
# Smart display name selection - prefer descriptive names over generic ones
|
||||
local candidates=()
|
||||
|
||||
# Get all potential names
|
||||
local bundle_display_name
|
||||
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
local bundle_name
|
||||
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
|
||||
# Check if executable name is generic/technical (should be avoided)
|
||||
local is_generic_executable=false
|
||||
if [[ -n "$bundle_executable" ]]; then
|
||||
case "$bundle_executable" in
|
||||
"pake" | "Electron" | "electron" | "nwjs" | "node" | "helper" | "main" | "app" | "binary")
|
||||
is_generic_executable=true
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Priority order for name selection:
|
||||
# 1. App folder name (if ASCII and descriptive) - often the most complete name
|
||||
if [[ "$app_name" =~ ^[A-Za-z0-9\ ._-]+$ && ${#app_name} -gt 3 ]]; then
|
||||
candidates+=("$app_name")
|
||||
fi
|
||||
|
||||
# 2. CFBundleDisplayName (if meaningful and ASCII)
|
||||
if [[ -n "$bundle_display_name" && "$bundle_display_name" =~ ^[A-Za-z0-9\ ._-]+$ ]]; then
|
||||
candidates+=("$bundle_display_name")
|
||||
fi
|
||||
|
||||
# 3. CFBundleName (if meaningful and ASCII)
|
||||
if [[ -n "$bundle_name" && "$bundle_name" =~ ^[A-Za-z0-9\ ._-]+$ && "$bundle_name" != "$bundle_display_name" ]]; then
|
||||
candidates+=("$bundle_name")
|
||||
fi
|
||||
|
||||
# 4. CFBundleExecutable (only if not generic and ASCII)
|
||||
if [[ -n "$bundle_executable" && "$bundle_executable" =~ ^[A-Za-z0-9._-]+$ && "$is_generic_executable" == false ]]; then
|
||||
candidates+=("$bundle_executable")
|
||||
fi
|
||||
|
||||
# 5. Fallback to non-ASCII names if no ASCII found
|
||||
if [[ ${#candidates[@]} -eq 0 ]]; then
|
||||
[[ -n "$bundle_display_name" ]] && candidates+=("$bundle_display_name")
|
||||
[[ -n "$bundle_name" && "$bundle_name" != "$bundle_display_name" ]] && candidates+=("$bundle_name")
|
||||
candidates+=("$app_name")
|
||||
fi
|
||||
|
||||
# Select the first (best) candidate
|
||||
display_name="${candidates[0]:-$app_name}"
|
||||
|
||||
# Apply brand name mapping from common.sh
|
||||
display_name="$(get_brand_name "$display_name")"
|
||||
fi
|
||||
|
||||
# Skip system critical apps (input methods, system components)
|
||||
# Note: Paid apps like CleanMyMac, 1Password are NOT protected here - users can uninstall them
|
||||
if should_protect_from_uninstall "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Store tuple: app_path|app_name|bundle_id|display_name
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}|${display_name}")
|
||||
# Store tuple: app_path|app_name|bundle_id (display_name will be resolved in parallel later)
|
||||
app_data_tuples+=("${app_path}|${app_name}|${bundle_id}")
|
||||
done < <(
|
||||
# Scan both system and user application directories
|
||||
# Using maxdepth 3 to find apps in subdirectories (e.g., Adobe apps in /Applications/Adobe X/)
|
||||
@@ -209,11 +152,7 @@ scan_applications() {
|
||||
max_parallel=32
|
||||
fi
|
||||
local pids=()
|
||||
local inline_loading=false
|
||||
if [[ "${MOLE_INLINE_LOADING:-}" == "1" || "${MOLE_INLINE_LOADING:-}" == "true" ]]; then
|
||||
inline_loading=true
|
||||
printf "\033[H" >&2 # Position cursor at top of screen
|
||||
fi
|
||||
# inline_loading variable already set above (line ~92)
|
||||
|
||||
# Process app metadata extraction function
|
||||
process_app_metadata() {
|
||||
@@ -221,7 +160,35 @@ scan_applications() {
|
||||
local output_file="$2"
|
||||
local current_epoch="$3"
|
||||
|
||||
IFS='|' read -r app_path app_name bundle_id display_name <<< "$app_data_tuple"
|
||||
IFS='|' read -r app_path app_name bundle_id <<< "$app_data_tuple"
|
||||
|
||||
# Get localized display name (moved from first pass for better performance)
|
||||
local display_name="$app_name"
|
||||
if [[ -f "$app_path/Contents/Info.plist" ]]; then
|
||||
# Try to get localized name from system metadata (best for i18n)
|
||||
local md_display_name
|
||||
md_display_name=$(run_with_timeout 0.05 mdls -name kMDItemDisplayName -raw "$app_path" 2> /dev/null || echo "")
|
||||
|
||||
# Get bundle names
|
||||
local bundle_display_name
|
||||
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
local bundle_name
|
||||
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null)
|
||||
|
||||
# Priority order for name selection (prefer localized names):
|
||||
# 1. System metadata display name (kMDItemDisplayName) - respects system language
|
||||
# 2. CFBundleDisplayName - usually localized
|
||||
# 3. CFBundleName - fallback
|
||||
# 4. App folder name - last resort
|
||||
|
||||
if [[ -n "$md_display_name" && "$md_display_name" != "(null)" && "$md_display_name" != "$app_name" ]]; then
|
||||
display_name="$md_display_name"
|
||||
elif [[ -n "$bundle_display_name" && "$bundle_display_name" != "(null)" ]]; then
|
||||
display_name="$bundle_display_name"
|
||||
elif [[ -n "$bundle_name" && "$bundle_name" != "(null)" ]]; then
|
||||
display_name="$bundle_name"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parallel size calculation
|
||||
local app_size="N/A"
|
||||
@@ -293,9 +260,9 @@ scan_applications() {
|
||||
local completed=$(cat "$progress_file" 2> /dev/null || echo 0)
|
||||
local c="${spinner_chars:$((i % 4)):1}"
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
printf "\033[H\033[2K%s Scanning applications... %d/%d\n" "$c" "$completed" "$total_apps" >&2
|
||||
else
|
||||
echo -ne "\r\033[K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
printf "\r\033[K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
|
||||
fi
|
||||
((i++))
|
||||
sleep 0.1 2> /dev/null || sleep 1
|
||||
@@ -346,12 +313,30 @@ scan_applications() {
|
||||
fi
|
||||
|
||||
# Sort by last used (oldest first) and cache the result
|
||||
# Show brief processing message for large app lists
|
||||
if [[ $total_apps -gt 50 ]]; then
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2KProcessing %d applications...\n" "$total_apps" >&2
|
||||
else
|
||||
printf "\rProcessing %d applications... " "$total_apps" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
sort -t'|' -k1,1n "$temp_file" > "${temp_file}.sorted" || {
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
}
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Clear processing message
|
||||
if [[ $total_apps -gt 50 ]]; then
|
||||
if [[ $inline_loading == true ]]; then
|
||||
printf "\033[H\033[2K" >&2
|
||||
else
|
||||
printf "\r\033[K" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Save to cache (simplified - no metadata)
|
||||
cp "${temp_file}.sorted" "$cache_file" 2> /dev/null || true
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
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"
|
||||
@@ -20,18 +22,26 @@ format_app_display() {
|
||||
local fixed_width=28
|
||||
local available_width=$((terminal_width - fixed_width))
|
||||
|
||||
# Set reasonable bounds for name width: 24-35 chars
|
||||
# Set reasonable bounds for name width: 24-35 display width
|
||||
[[ $available_width -lt 24 ]] && available_width=24
|
||||
[[ $available_width -gt 35 ]] && available_width=35
|
||||
|
||||
# Truncate long names if needed
|
||||
local truncated_name="$display_name"
|
||||
if [[ ${#display_name} -gt $available_width ]]; then
|
||||
truncated_name="${display_name:0:$((available_width - 3))}..."
|
||||
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")
|
||||
|
||||
# Use dynamic column width for better readability
|
||||
printf "%-*s %9s | %s" "$available_width" "$truncated_name" "$size_str" "$compact_last_used"
|
||||
# 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)
|
||||
@@ -46,6 +56,14 @@ select_apps_for_uninstall() {
|
||||
fi
|
||||
|
||||
# Build menu options
|
||||
# Show loading for large lists (formatting can be slow due to width calculations)
|
||||
local app_count=${#apps_data[@]}
|
||||
if [[ $app_count -gt 30 ]]; then
|
||||
if [[ -t 2 ]]; then
|
||||
printf "\rPreparing %d applications... " "$app_count" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
local -a menu_options=()
|
||||
# Prepare metadata (comma-separated) for sorting/filtering inside the menu
|
||||
local epochs_csv=""
|
||||
@@ -66,6 +84,13 @@ select_apps_for_uninstall() {
|
||||
((idx++))
|
||||
done
|
||||
|
||||
# Clear loading message
|
||||
if [[ $app_count -gt 30 ]]; 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
|
||||
|
||||
Reference in New Issue
Block a user