1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 17:55:08 +00:00

fix(purge): rewrite spinner for /dev/tty reliability and fix cursor position

- Capture terminal width in parent before forking; avoids unreliable
  tput calls inside the background subshell
- Write spinner output directly to /dev/tty to prevent stdout state
  contention between parent and background processes
- Use \033[2K (erase entire line) instead of \033[K (erase to EOL)
- Add handle_interrupt() so Ctrl-C cleanly stops spinner, restores
  cursor, and exits 130
- cleanup_monitor now writes \r\033[2K\n so subsequent output starts
  on a clean line rather than on the cleared spinner line
This commit is contained in:
tw93
2026-02-26 19:42:34 +08:00
parent e17f35eb57
commit 837df390a5

View File

@@ -50,7 +50,6 @@ start_purge() {
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
printf '\033[2J\033[H' printf '\033[2J\033[H'
fi fi
printf '\n'
# Initialize stats file in user cache directory # Initialize stats file in user cache directory
local stats_dir="${XDG_CACHE_HOME:-$HOME/.cache}/mole" local stats_dir="${XDG_CACHE_HOME:-$HOME/.cache}/mole"
@@ -83,87 +82,89 @@ perform_purge() {
wait "$monitor_pid" 2> /dev/null || true wait "$monitor_pid" 2> /dev/null || true
fi fi
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
printf '\r\033[K\n\033[K\033[A' printf '\r\033[2K\n' > /dev/tty 2> /dev/null || true
fi fi
} }
# Set up trap for cleanup # Ensure Ctrl-C/TERM always stops spinner(s) and exits immediately.
trap cleanup_monitor INT TERM handle_interrupt() {
cleanup_monitor
stop_inline_spinner 2> /dev/null || true
show_cursor 2> /dev/null || true
printf '\n' >&2
exit 130
}
# Show scanning with spinner on same line as title # Set up trap for cleanup + abort
trap handle_interrupt INT TERM
# Show scanning with spinner below the title line
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
# Print title first # Print title ONCE with newline; spinner occupies the line below
printf '%s' "${PURPLE_BOLD}Purge Project Artifacts${NC} " printf '%s\n' "${PURPLE_BOLD}Purge Project Artifacts${NC}"
# Start background monitor with ASCII spinner # Capture terminal width in parent (most reliable before forking)
local _parent_cols=80
local _stty_out
if _stty_out=$(stty size < /dev/tty 2> /dev/null); then
_parent_cols="${_stty_out##* }" # "rows cols" -> take cols
else
_parent_cols=$(tput cols 2> /dev/null || echo 80)
fi
[[ "$_parent_cols" =~ ^[0-9]+$ && $_parent_cols -gt 0 ]] || _parent_cols=80
# Start background monitor: writes directly to /dev/tty to avoid stdout state issues
( (
local spinner_chars="|/-\\" local spinner_chars="|/-\\"
local spinner_idx=0 local spinner_idx=0
local last_path="" local last_path=""
# Use parent-captured width; never refresh inside the loop (avoids unreliable tput in bg)
local term_cols="$_parent_cols"
# Visible prefix "| Scanning " = 11 chars; reserve 25 total for safety margin
local max_path_len=$((term_cols - 25))
((max_path_len < 5)) && max_path_len=5
# Set up trap to exit cleanly # Set up trap to exit cleanly (erase the spinner line via /dev/tty)
trap 'exit 0' INT TERM trap 'printf "\r\033[2K" >/dev/tty 2>/dev/null; exit 0' INT TERM
# Function to truncate path in the middle # Truncate path to guaranteed fit
truncate_path() { truncate_path() {
local path="$1" local path="$1"
local term_cols if [[ ${#path} -le $max_path_len ]]; then
term_cols=$(tput cols 2> /dev/null || echo 80)
# Reserve some space for the spinner and text (approx 20 chars)
local max_len=$((term_cols - 20))
# Ensure a reasonable minimum width
if ((max_len < 40)); then
max_len=40
fi
if [[ ${#path} -le $max_len ]]; then
echo "$path" echo "$path"
return return
fi fi
local side_len=$(((max_path_len - 3) / 2))
# Calculate how much to show on each side echo "${path:0:$side_len}...${path: -$side_len}"
local side_len=$(((max_len - 3) / 2))
local start="${path:0:$side_len}"
local end="${path: -$side_len}"
echo "${start}...${end}"
} }
while [[ -f "$stats_dir/purge_scanning" ]]; do while [[ -f "$stats_dir/purge_scanning" ]]; do
local current_path=$(cat "$stats_dir/purge_scanning" 2> /dev/null || echo "") local current_path
local display_path="" current_path=$(cat "$stats_dir/purge_scanning" 2> /dev/null || echo "")
if [[ -n "$current_path" ]]; then if [[ -n "$current_path" ]]; then
display_path="${current_path/#$HOME/~}" local display_path="${current_path/#$HOME/~}"
display_path=$(truncate_path "$display_path") display_path=$(truncate_path "$display_path")
last_path="$display_path" last_path="$display_path"
elif [[ -n "$last_path" ]]; then
display_path="$last_path"
fi fi
# Get current spinner character
local spin_char="${spinner_chars:$spinner_idx:1}" local spin_char="${spinner_chars:$spinner_idx:1}"
spinner_idx=$(((spinner_idx + 1) % ${#spinner_chars})) spinner_idx=$(((spinner_idx + 1) % ${#spinner_chars}))
# Show title on first line, spinner and scanning info on second line # Write directly to /dev/tty: \033[2K clears entire current line, \r goes to start
if [[ -n "$display_path" ]]; then if [[ -n "$last_path" ]]; then
# Line 1: Move to start, clear, print title printf '\r\033[2K%s %sScanning %s%s' \
printf '\r\033[K%s\n' "${PURPLE_BOLD}Purge Project Artifacts${NC}"
# Line 2: Move to start, clear, print scanning info
printf '\r\033[K%s %sScanning %s' \
"${BLUE}${spin_char}${NC}" \ "${BLUE}${spin_char}${NC}" \
"${GRAY}" "$display_path" "${GRAY}" "$last_path" "${NC}" > /dev/tty 2> /dev/null
# Move up THEN to start (important order!)
printf '\033[A\r'
else else
printf '\r\033[K%s\n' "${PURPLE_BOLD}Purge Project Artifacts${NC}" printf '\r\033[2K%s %sScanning...%s' \
printf '\r\033[K%s %sScanning...' \
"${BLUE}${spin_char}${NC}" \ "${BLUE}${spin_char}${NC}" \
"${GRAY}" "${GRAY}" "${NC}" > /dev/tty 2> /dev/null
printf '\033[A\r'
fi fi
sleep 0.05 sleep 0.05
done done
printf '\r\033[2K' > /dev/tty 2> /dev/null
exit 0 exit 0
) & ) &
monitor_pid=$! monitor_pid=$!
@@ -178,10 +179,6 @@ perform_purge() {
trap - INT TERM trap - INT TERM
cleanup_monitor cleanup_monitor
if [[ -t 1 ]]; then
echo -e "${PURPLE_BOLD}Purge Project Artifacts${NC}"
fi
# Exit codes: # Exit codes:
# 0 = success, show summary # 0 = success, show summary
# 1 = user cancelled # 1 = user cancelled
@@ -212,15 +209,13 @@ perform_purge() {
local freed_gb local freed_gb
freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}') freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
summary_details+=("Space freed: ${GREEN}${freed_gb}GB${NC}") local summary_line="Space freed: ${GREEN}${freed_gb}GB${NC}"
summary_details+=("Free space now: $(get_free_space)") [[ $total_items_cleaned -gt 0 ]] && summary_line+=" | Items: $total_items_cleaned"
summary_line+=" | Free: $(get_free_space)"
if [[ $total_items_cleaned -gt 0 ]]; then summary_details+=("$summary_line")
summary_details+=("Items cleaned: $total_items_cleaned")
fi
else else
summary_details+=("No old project artifacts to clean.") summary_details+=("No old project artifacts to clean.")
summary_details+=("Free space now: $(get_free_space)") summary_details+=("Free space: $(get_free_space)")
fi fi
# Log session end # Log session end