mirror of
https://github.com/tw93/Mole.git
synced 2026-02-16 18:45:17 +00:00
feat: improve mo purge UX with pagination and smart project detection
- Add pagination for project list based on terminal height - Show position indicator [5/25] when scrolling - Fix GB display for values < 1 (0.6GB instead of .6GB) - Improve project name detection by walking up to find project root - Change default editor from nano to vim
This commit is contained in:
@@ -387,6 +387,36 @@ select_purge_categories() {
|
|||||||
if [[ $total_items -eq 0 ]]; then
|
if [[ $total_items -eq 0 ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Calculate items per page based on terminal height
|
||||||
|
# Reserved: header(2) + blank(2) + footer(1) = 5 rows
|
||||||
|
_get_items_per_page() {
|
||||||
|
local term_height=24
|
||||||
|
if [[ -t 0 ]] || [[ -t 2 ]]; then
|
||||||
|
term_height=$(stty size </dev/tty 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
if [[ -z "$term_height" || $term_height -le 0 ]]; then
|
||||||
|
if command -v tput > /dev/null 2>&1; then
|
||||||
|
term_height=$(tput lines 2>/dev/null || echo "24")
|
||||||
|
else
|
||||||
|
term_height=24
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
local reserved=6
|
||||||
|
local available=$((term_height - reserved))
|
||||||
|
if [[ $available -lt 3 ]]; then
|
||||||
|
echo 3
|
||||||
|
elif [[ $available -gt 50 ]]; then
|
||||||
|
echo 50
|
||||||
|
else
|
||||||
|
echo "$available"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
local items_per_page=$(_get_items_per_page)
|
||||||
|
local cursor_pos=0
|
||||||
|
local top_index=0
|
||||||
|
|
||||||
# Initialize selection (all selected by default, except recent ones)
|
# Initialize selection (all selected by default, except recent ones)
|
||||||
local -a selected=()
|
local -a selected=()
|
||||||
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
|
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
|
||||||
@@ -398,17 +428,16 @@ select_purge_categories() {
|
|||||||
selected[i]=true
|
selected[i]=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
local cursor_pos=0
|
|
||||||
local original_stty=""
|
local original_stty=""
|
||||||
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
|
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
|
||||||
original_stty=$(stty -g 2> /dev/null || echo "")
|
original_stty=$(stty -g 2>/dev/null || echo "")
|
||||||
fi
|
fi
|
||||||
# Terminal control functions
|
# Terminal control functions
|
||||||
restore_terminal() {
|
restore_terminal() {
|
||||||
trap - EXIT INT TERM
|
trap - EXIT INT TERM
|
||||||
show_cursor
|
show_cursor
|
||||||
if [[ -n "${original_stty:-}" ]]; then
|
if [[ -n "${original_stty:-}" ]]; then
|
||||||
stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true
|
stty "${original_stty}" 2>/dev/null || stty sane 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
@@ -417,6 +446,9 @@ select_purge_categories() {
|
|||||||
exit 130
|
exit 130
|
||||||
}
|
}
|
||||||
draw_menu() {
|
draw_menu() {
|
||||||
|
# Recalculate items_per_page dynamically to handle window resize
|
||||||
|
items_per_page=$(_get_items_per_page)
|
||||||
|
|
||||||
printf "\033[H"
|
printf "\033[H"
|
||||||
# Calculate total size of selected items for header
|
# Calculate total size of selected items for header
|
||||||
local selected_size=0
|
local selected_size=0
|
||||||
@@ -429,29 +461,54 @@ select_purge_categories() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
local selected_gb
|
local selected_gb
|
||||||
selected_gb=$(echo "scale=1; $selected_size/1024/1024" | bc)
|
selected_gb=$(printf "%.1f" "$(echo "scale=2; $selected_size/1024/1024" | bc)")
|
||||||
|
|
||||||
|
# Show position indicator if scrolling is needed
|
||||||
|
local scroll_indicator=""
|
||||||
|
if [[ $total_items -gt $items_per_page ]]; then
|
||||||
|
local current_pos=$((top_index + cursor_pos + 1))
|
||||||
|
scroll_indicator=" ${GRAY}[${current_pos}/${total_items}]${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
printf "%s\n" "$clear_line"
|
printf "%s\n" "$clear_line"
|
||||||
printf "%s${PURPLE_BOLD}Select Categories to Clean${NC} ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line"
|
printf "%s${PURPLE_BOLD}Select Categories to Clean${NC}%s ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line" "$scroll_indicator"
|
||||||
printf "%s\n" "$clear_line"
|
printf "%s\n" "$clear_line"
|
||||||
|
|
||||||
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
|
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
|
||||||
for ((i = 0; i < total_items; i++)); do
|
|
||||||
|
# Calculate visible range
|
||||||
|
local visible_count=$((total_items - top_index))
|
||||||
|
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
|
||||||
|
local end_index=$((top_index + visible_count))
|
||||||
|
|
||||||
|
# Draw only visible items
|
||||||
|
for ((i = top_index; i < end_index; i++)); do
|
||||||
local checkbox="$ICON_EMPTY"
|
local checkbox="$ICON_EMPTY"
|
||||||
[[ ${selected[i]} == true ]] && checkbox="$ICON_SOLID"
|
[[ ${selected[i]} == true ]] && checkbox="$ICON_SOLID"
|
||||||
local recent_marker=""
|
local recent_marker=""
|
||||||
[[ ${recent_flags[i]:-false} == "true" ]] && recent_marker=" ${GRAY}| Recent${NC}"
|
[[ ${recent_flags[i]:-false} == "true" ]] && recent_marker=" ${GRAY}| Recent${NC}"
|
||||||
if [[ $i -eq $cursor_pos ]]; then
|
local rel_pos=$((i - top_index))
|
||||||
|
if [[ $rel_pos -eq $cursor_pos ]]; then
|
||||||
printf "%s${CYAN}${ICON_ARROW} %s %s%s${NC}\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker"
|
printf "%s${CYAN}${ICON_ARROW} %s %s%s${NC}\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker"
|
||||||
else
|
else
|
||||||
printf "%s %s %s%s\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker"
|
printf "%s %s %s%s\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Fill empty slots to clear previous content
|
||||||
|
local items_shown=$visible_count
|
||||||
|
for ((i = items_shown; i < items_per_page; i++)); do
|
||||||
printf "%s\n" "$clear_line"
|
printf "%s\n" "$clear_line"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s\n" "$clear_line"
|
||||||
|
|
||||||
printf "%s${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space Select | Enter Confirm | A All | I Invert | Q Quit${NC}\n" "$clear_line"
|
printf "%s${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space Select | Enter Confirm | A All | I Invert | Q Quit${NC}\n" "$clear_line"
|
||||||
}
|
}
|
||||||
trap restore_terminal EXIT
|
trap restore_terminal EXIT
|
||||||
trap handle_interrupt INT TERM
|
trap handle_interrupt INT TERM
|
||||||
# Preserve interrupt character for Ctrl-C
|
# Preserve interrupt character for Ctrl-C
|
||||||
stty -echo -icanon intr ^C 2> /dev/null || true
|
stty -echo -icanon intr ^C 2>/dev/null || true
|
||||||
hide_cursor
|
hide_cursor
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
clear_screen
|
clear_screen
|
||||||
@@ -470,10 +527,24 @@ select_purge_categories() {
|
|||||||
IFS= read -r -s -n1 -t 1 key3 || key3=""
|
IFS= read -r -s -n1 -t 1 key3 || key3=""
|
||||||
case "$key3" in
|
case "$key3" in
|
||||||
A) # Up arrow
|
A) # Up arrow
|
||||||
((cursor_pos > 0)) && ((cursor_pos--))
|
if [[ $cursor_pos -gt 0 ]]; then
|
||||||
|
((cursor_pos--))
|
||||||
|
elif [[ $top_index -gt 0 ]]; then
|
||||||
|
((top_index--))
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
B) # Down arrow
|
B) # Down arrow
|
||||||
((cursor_pos < total_items - 1)) && ((cursor_pos++))
|
local absolute_index=$((top_index + cursor_pos))
|
||||||
|
local last_index=$((total_items - 1))
|
||||||
|
if [[ $absolute_index -lt $last_index ]]; then
|
||||||
|
local visible_count=$((total_items - top_index))
|
||||||
|
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
|
||||||
|
if [[ $cursor_pos -lt $((visible_count - 1)) ]]; then
|
||||||
|
((cursor_pos++))
|
||||||
|
elif [[ $((top_index + visible_count)) -lt $total_items ]]; then
|
||||||
|
((top_index++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
@@ -483,10 +554,11 @@ select_purge_categories() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
" ") # Space - toggle current item
|
" ") # Space - toggle current item
|
||||||
if [[ ${selected[cursor_pos]} == true ]]; then
|
local idx=$((top_index + cursor_pos))
|
||||||
selected[cursor_pos]=false
|
if [[ ${selected[idx]} == true ]]; then
|
||||||
|
selected[idx]=false
|
||||||
else
|
else
|
||||||
selected[cursor_pos]=true
|
selected[idx]=true
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"a" | "A") # Select all
|
"a" | "A") # Select all
|
||||||
@@ -608,10 +680,31 @@ clean_project_artifacts() {
|
|||||||
local -a item_recent_flags=()
|
local -a item_recent_flags=()
|
||||||
# Helper to get project name from path
|
# Helper to get project name from path
|
||||||
# For ~/www/pake/src-tauri/target -> returns "pake"
|
# For ~/www/pake/src-tauri/target -> returns "pake"
|
||||||
# For ~/www/project/node_modules/xxx/node_modules -> returns "project"
|
# For ~/work/code/MyProject/node_modules -> returns "MyProject"
|
||||||
|
# Strategy: Find the nearest ancestor directory containing a project indicator file
|
||||||
get_project_name() {
|
get_project_name() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
# Find the project root by looking for direct child of search paths
|
local artifact_name
|
||||||
|
artifact_name=$(basename "$path")
|
||||||
|
|
||||||
|
# Start from the parent of the artifact and walk up
|
||||||
|
local current_dir
|
||||||
|
current_dir=$(dirname "$path")
|
||||||
|
|
||||||
|
while [[ "$current_dir" != "/" && "$current_dir" != "$HOME" && -n "$current_dir" ]]; do
|
||||||
|
# Check if current directory contains any project indicator
|
||||||
|
for indicator in "${PROJECT_INDICATORS[@]}"; do
|
||||||
|
if [[ -e "$current_dir/$indicator" ]]; then
|
||||||
|
# Found a project root, return its name
|
||||||
|
basename "$current_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Move up one level
|
||||||
|
current_dir=$(dirname "$current_dir")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Fallback: try the old logic (first directory under search root)
|
||||||
local search_roots=()
|
local search_roots=()
|
||||||
if [[ ${#PURGE_SEARCH_PATHS[@]} -gt 0 ]]; then
|
if [[ ${#PURGE_SEARCH_PATHS[@]} -gt 0 ]]; then
|
||||||
search_roots=("${PURGE_SEARCH_PATHS[@]}")
|
search_roots=("${PURGE_SEARCH_PATHS[@]}")
|
||||||
@@ -619,17 +712,15 @@ clean_project_artifacts() {
|
|||||||
search_roots=("$HOME/www" "$HOME/dev" "$HOME/Projects")
|
search_roots=("$HOME/www" "$HOME/dev" "$HOME/Projects")
|
||||||
fi
|
fi
|
||||||
for root in "${search_roots[@]}"; do
|
for root in "${search_roots[@]}"; do
|
||||||
# Normalize trailing slash for consistent matching
|
|
||||||
root="${root%/}"
|
root="${root%/}"
|
||||||
if [[ -n "$root" && "$path" == "$root/"* ]]; then
|
if [[ -n "$root" && "$path" == "$root/"* ]]; then
|
||||||
# Remove root prefix and get first directory component
|
|
||||||
local relative_path="${path#"$root"/}"
|
local relative_path="${path#"$root"/}"
|
||||||
# Extract first directory name
|
|
||||||
echo "$relative_path" | cut -d'/' -f1
|
echo "$relative_path" | cut -d'/' -f1
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Fallback: use grandparent directory
|
|
||||||
|
# Final fallback: use grandparent directory
|
||||||
dirname "$(dirname "$path")" | xargs basename
|
dirname "$(dirname "$path")" | xargs basename
|
||||||
}
|
}
|
||||||
# Format display with alignment (like app_selector)
|
# Format display with alignment (like app_selector)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ manage_purge_paths() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Open in editor
|
# Open in editor
|
||||||
local editor="${EDITOR:-${VISUAL:-nano}}"
|
local editor="${EDITOR:-${VISUAL:-vim}}"
|
||||||
echo -e "Opening in ${CYAN}$editor${NC}..."
|
echo -e "Opening in ${CYAN}$editor${NC}..."
|
||||||
echo -e "${GRAY}Save and exit to apply changes. Leave empty to use defaults.${NC}"
|
echo -e "${GRAY}Save and exit to apply changes. Leave empty to use defaults.${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user