mirror of
https://github.com/tw93/Mole.git
synced 2026-02-05 06:13:49 +00:00
Implement spinner stack management and ANSI terminal capability checks
This commit is contained in:
274
lib/core/base.sh
274
lib/core/base.sh
@@ -30,16 +30,15 @@ readonly NC="${ESC}[0m"
|
||||
readonly ICON_CONFIRM="◎"
|
||||
readonly ICON_ADMIN="⚙"
|
||||
readonly ICON_SUCCESS="✓"
|
||||
readonly ICON_ERROR="☻"
|
||||
readonly ICON_ERROR="☹︎"
|
||||
readonly ICON_WARNING="☺︎"
|
||||
readonly ICON_EMPTY="○"
|
||||
readonly ICON_SOLID="●"
|
||||
readonly ICON_LIST="•"
|
||||
readonly ICON_ARROW="➤"
|
||||
readonly ICON_WARNING="☻"
|
||||
readonly ICON_DRY_RUN="→"
|
||||
readonly ICON_NAV_UP="↑"
|
||||
readonly ICON_NAV_DOWN="↓"
|
||||
readonly ICON_NAV_LEFT="←"
|
||||
readonly ICON_NAV_RIGHT="→"
|
||||
|
||||
# ============================================================================
|
||||
# Global Configuration Constants
|
||||
@@ -53,6 +52,8 @@ readonly MOLE_LOG_AGE_DAYS=7 # Log retention (days)
|
||||
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
||||
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # Saved state retention (days)
|
||||
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
||||
readonly MOLE_MAX_DS_STORE_FILES=500 # Max .DS_Store files to clean per scan
|
||||
readonly MOLE_MAX_ORPHAN_ITERATIONS=100 # Max iterations for orphaned app data scan
|
||||
|
||||
# ============================================================================
|
||||
# Seasonal Functions
|
||||
@@ -566,3 +567,268 @@ note_activity() {
|
||||
SECTION_ACTIVITY=1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start a section spinner with optional message
|
||||
# Usage: start_section_spinner "message"
|
||||
start_section_spinner() {
|
||||
local message="${1:-Scanning...}"
|
||||
stop_inline_spinner 2> /dev/null || true
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop spinner and clear the line
|
||||
# Usage: stop_section_spinner
|
||||
stop_section_spinner() {
|
||||
stop_inline_spinner 2> /dev/null || true
|
||||
if [[ -t 1 ]]; then
|
||||
echo -ne "\r\033[K" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Safe terminal line clearing with terminal type detection
|
||||
# Usage: safe_clear_lines <num_lines> [tty_device]
|
||||
# Returns: 0 on success, 1 if terminal doesn't support ANSI
|
||||
safe_clear_lines() {
|
||||
local lines="${1:-1}"
|
||||
local tty_device="${2:-/dev/tty}"
|
||||
|
||||
# Use centralized ANSI support check (defined below)
|
||||
# Note: This forward reference works because functions are parsed before execution
|
||||
is_ansi_supported 2>/dev/null || return 1
|
||||
|
||||
# Clear lines one by one (more reliable than multi-line sequences)
|
||||
local i
|
||||
for ((i=0; i<lines; i++)); do
|
||||
printf "\033[1A\r\033[K" > "$tty_device" 2>/dev/null || return 1
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Safe single line clear with fallback
|
||||
# Usage: safe_clear_line [tty_device]
|
||||
safe_clear_line() {
|
||||
local tty_device="${1:-/dev/tty}"
|
||||
|
||||
# Use centralized ANSI support check
|
||||
is_ansi_supported 2>/dev/null || return 1
|
||||
|
||||
printf "\r\033[K" > "$tty_device" 2>/dev/null || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Update progress spinner if enough time has elapsed
|
||||
# Usage: update_progress_if_needed <completed> <total> <last_update_time_var> [interval]
|
||||
# Example: update_progress_if_needed "$completed" "$total" last_progress_update 2
|
||||
# Returns: 0 if updated, 1 if skipped
|
||||
update_progress_if_needed() {
|
||||
local completed="$1"
|
||||
local total="$2"
|
||||
local last_update_var="$3" # Name of variable holding last update time
|
||||
local interval="${4:-2}" # Default: update every 2 seconds
|
||||
|
||||
# Get current time
|
||||
local current_time=$(date +%s)
|
||||
|
||||
# Get last update time from variable
|
||||
local last_time
|
||||
eval "last_time=\${$last_update_var:-0}"
|
||||
|
||||
# Check if enough time has elapsed
|
||||
if [[ $((current_time - last_time)) -ge $interval ]]; then
|
||||
# Update the spinner with progress
|
||||
stop_section_spinner
|
||||
start_section_spinner "Scanning items... ($completed/$total)"
|
||||
|
||||
# Update the last_update_time variable
|
||||
eval "$last_update_var=$current_time"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Spinner Stack Management (prevents nesting issues)
|
||||
# ============================================================================
|
||||
|
||||
# Global spinner stack
|
||||
declare -a MOLE_SPINNER_STACK=()
|
||||
|
||||
# Push current spinner state onto stack
|
||||
# Usage: push_spinner_state
|
||||
push_spinner_state() {
|
||||
local current_state=""
|
||||
|
||||
# Save current spinner PID if running
|
||||
if [[ -n "${MOLE_SPINNER_PID:-}" ]] && kill -0 "$MOLE_SPINNER_PID" 2>/dev/null; then
|
||||
current_state="running:$MOLE_SPINNER_PID"
|
||||
else
|
||||
current_state="stopped"
|
||||
fi
|
||||
|
||||
MOLE_SPINNER_STACK+=("$current_state")
|
||||
debug_log "Pushed spinner state: $current_state (stack depth: ${#MOLE_SPINNER_STACK[@]})"
|
||||
}
|
||||
|
||||
# Pop and restore spinner state from stack
|
||||
# Usage: pop_spinner_state
|
||||
pop_spinner_state() {
|
||||
if [[ ${#MOLE_SPINNER_STACK[@]} -eq 0 ]]; then
|
||||
debug_log "Warning: Attempted to pop from empty spinner stack"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Stack depth safety check
|
||||
if [[ ${#MOLE_SPINNER_STACK[@]} -gt 10 ]]; then
|
||||
debug_log "Warning: Spinner stack depth excessive (${#MOLE_SPINNER_STACK[@]}), possible leak"
|
||||
fi
|
||||
|
||||
local last_idx=$((${#MOLE_SPINNER_STACK[@]} - 1))
|
||||
local state="${MOLE_SPINNER_STACK[$last_idx]}"
|
||||
|
||||
# Remove from stack (Bash 3.2 compatible way)
|
||||
# Instead of unset, rebuild array without last element
|
||||
local -a new_stack=()
|
||||
local i
|
||||
for ((i=0; i<last_idx; i++)); do
|
||||
new_stack+=("${MOLE_SPINNER_STACK[$i]}")
|
||||
done
|
||||
MOLE_SPINNER_STACK=("${new_stack[@]}")
|
||||
|
||||
debug_log "Popped spinner state: $state (remaining depth: ${#MOLE_SPINNER_STACK[@]})"
|
||||
|
||||
# Restore state if needed
|
||||
if [[ "$state" == running:* ]]; then
|
||||
# Previous spinner was running - we don't restart it automatically
|
||||
# This is intentional to avoid UI conflicts
|
||||
:
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Safe spinner start with stack management
|
||||
# Usage: safe_start_spinner <message>
|
||||
safe_start_spinner() {
|
||||
local message="${1:-Working...}"
|
||||
|
||||
# Push current state
|
||||
push_spinner_state
|
||||
|
||||
# Stop any existing spinner
|
||||
stop_section_spinner 2>/dev/null || true
|
||||
|
||||
# Start new spinner
|
||||
start_section_spinner "$message"
|
||||
}
|
||||
|
||||
# Safe spinner stop with stack management
|
||||
# Usage: safe_stop_spinner
|
||||
safe_stop_spinner() {
|
||||
# Stop current spinner
|
||||
stop_section_spinner 2>/dev/null || true
|
||||
|
||||
# Pop previous state
|
||||
pop_spinner_state || true
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Terminal Compatibility Checks
|
||||
# ============================================================================
|
||||
|
||||
# Check if terminal supports ANSI escape codes
|
||||
# Usage: is_ansi_supported
|
||||
# Returns: 0 if supported, 1 if not
|
||||
is_ansi_supported() {
|
||||
# Check if running in interactive terminal
|
||||
[[ -t 1 ]] || return 1
|
||||
|
||||
# Check TERM variable
|
||||
[[ -n "${TERM:-}" ]] || return 1
|
||||
|
||||
# Check for known ANSI-compatible terminals
|
||||
case "$TERM" in
|
||||
xterm*|vt100|vt220|screen*|tmux*|ansi|linux|rxvt*|konsole*)
|
||||
return 0
|
||||
;;
|
||||
dumb|unknown)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
# Check terminfo database if available
|
||||
if command -v tput >/dev/null 2>&1; then
|
||||
# Test if terminal supports colors (good proxy for ANSI support)
|
||||
local colors=$(tput colors 2>/dev/null || echo "0")
|
||||
[[ "$colors" -ge 8 ]] && return 0
|
||||
fi
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Get terminal capability info
|
||||
# Usage: get_terminal_info
|
||||
get_terminal_info() {
|
||||
local info="Terminal: ${TERM:-unknown}"
|
||||
|
||||
if is_ansi_supported; then
|
||||
info+=" (ANSI supported)"
|
||||
|
||||
if command -v tput >/dev/null 2>&1; then
|
||||
local cols=$(tput cols 2>/dev/null || echo "?")
|
||||
local lines=$(tput lines 2>/dev/null || echo "?")
|
||||
local colors=$(tput colors 2>/dev/null || echo "?")
|
||||
info+=" ${cols}x${lines}, ${colors} colors"
|
||||
fi
|
||||
else
|
||||
info+=" (ANSI not supported)"
|
||||
fi
|
||||
|
||||
echo "$info"
|
||||
}
|
||||
|
||||
# Validate terminal environment before running
|
||||
# Usage: validate_terminal_environment
|
||||
# Returns: 0 if OK, 1 with warning if issues detected
|
||||
validate_terminal_environment() {
|
||||
local warnings=0
|
||||
|
||||
# Check if TERM is set
|
||||
if [[ -z "${TERM:-}" ]]; then
|
||||
log_warning "TERM environment variable not set"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
# Check if running in a known problematic terminal
|
||||
case "${TERM:-}" in
|
||||
dumb)
|
||||
log_warning "Running in 'dumb' terminal - limited functionality"
|
||||
((warnings++))
|
||||
;;
|
||||
unknown)
|
||||
log_warning "Terminal type unknown - may have display issues"
|
||||
((warnings++))
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check terminal size if available
|
||||
if command -v tput >/dev/null 2>&1; then
|
||||
local cols=$(tput cols 2>/dev/null || echo "80")
|
||||
if [[ "$cols" -lt 60 ]]; then
|
||||
log_warning "Terminal width ($cols cols) is narrow - output may wrap"
|
||||
((warnings++))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Report compatibility
|
||||
if [[ $warnings -eq 0 ]]; then
|
||||
debug_log "Terminal environment validated: $(get_terminal_info)"
|
||||
return 0
|
||||
else
|
||||
debug_log "Terminal compatibility warnings: $warnings"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user