mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +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
|
||||
return 1
|
||||
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)
|
||||
local -a selected=()
|
||||
IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}"
|
||||
@@ -398,17 +428,16 @@ select_purge_categories() {
|
||||
selected[i]=true
|
||||
fi
|
||||
done
|
||||
local cursor_pos=0
|
||||
local original_stty=""
|
||||
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
|
||||
# Terminal control functions
|
||||
restore_terminal() {
|
||||
trap - EXIT INT TERM
|
||||
show_cursor
|
||||
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
|
||||
}
|
||||
# shellcheck disable=SC2329
|
||||
@@ -417,6 +446,9 @@ select_purge_categories() {
|
||||
exit 130
|
||||
}
|
||||
draw_menu() {
|
||||
# Recalculate items_per_page dynamically to handle window resize
|
||||
items_per_page=$(_get_items_per_page)
|
||||
|
||||
printf "\033[H"
|
||||
# Calculate total size of selected items for header
|
||||
local selected_size=0
|
||||
@@ -429,29 +461,54 @@ select_purge_categories() {
|
||||
fi
|
||||
done
|
||||
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${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"
|
||||
|
||||
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"
|
||||
[[ ${selected[i]} == true ]] && checkbox="$ICON_SOLID"
|
||||
local recent_marker=""
|
||||
[[ ${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"
|
||||
else
|
||||
printf "%s %s %s%s\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker"
|
||||
fi
|
||||
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"
|
||||
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"
|
||||
}
|
||||
trap restore_terminal EXIT
|
||||
trap handle_interrupt INT TERM
|
||||
# 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
|
||||
if [[ -t 1 ]]; then
|
||||
clear_screen
|
||||
@@ -470,10 +527,24 @@ select_purge_categories() {
|
||||
IFS= read -r -s -n1 -t 1 key3 || key3=""
|
||||
case "$key3" in
|
||||
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
|
||||
((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
|
||||
else
|
||||
@@ -483,10 +554,11 @@ select_purge_categories() {
|
||||
fi
|
||||
;;
|
||||
" ") # Space - toggle current item
|
||||
if [[ ${selected[cursor_pos]} == true ]]; then
|
||||
selected[cursor_pos]=false
|
||||
local idx=$((top_index + cursor_pos))
|
||||
if [[ ${selected[idx]} == true ]]; then
|
||||
selected[idx]=false
|
||||
else
|
||||
selected[cursor_pos]=true
|
||||
selected[idx]=true
|
||||
fi
|
||||
;;
|
||||
"a" | "A") # Select all
|
||||
@@ -608,10 +680,31 @@ clean_project_artifacts() {
|
||||
local -a item_recent_flags=()
|
||||
# Helper to get project name from path
|
||||
# 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() {
|
||||
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=()
|
||||
if [[ ${#PURGE_SEARCH_PATHS[@]} -gt 0 ]]; then
|
||||
search_roots=("${PURGE_SEARCH_PATHS[@]}")
|
||||
@@ -619,17 +712,15 @@ clean_project_artifacts() {
|
||||
search_roots=("$HOME/www" "$HOME/dev" "$HOME/Projects")
|
||||
fi
|
||||
for root in "${search_roots[@]}"; do
|
||||
# Normalize trailing slash for consistent matching
|
||||
root="${root%/}"
|
||||
if [[ -n "$root" && "$path" == "$root/"* ]]; then
|
||||
# Remove root prefix and get first directory component
|
||||
local relative_path="${path#"$root"/}"
|
||||
# Extract first directory name
|
||||
echo "$relative_path" | cut -d'/' -f1
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
# Fallback: use grandparent directory
|
||||
|
||||
# Final fallback: use grandparent directory
|
||||
dirname "$(dirname "$path")" | xargs basename
|
||||
}
|
||||
# Format display with alignment (like app_selector)
|
||||
|
||||
Reference in New Issue
Block a user