1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 16:49:41 +00:00

feat: Boost UI performance with pure bash string width calculation and truncation, and add visual feedback for cache hits in uninstall scripts.

This commit is contained in:
Tw93
2025-12-17 11:01:15 +08:00
parent 3053e05ce4
commit 27205c653d
4 changed files with 80 additions and 94 deletions

View File

@@ -83,6 +83,12 @@ scan_applications() {
[[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file
if [[ $cache_age -lt $cache_ttl ]]; then if [[ $cache_age -lt $cache_ttl ]]; then
# Cache hit - return immediately # Cache hit - return immediately
# Show brief flash of cache usage if in interactive mode
if [[ -t 2 ]]; then
echo -e "${GREEN}Loading from cache...${NC}" >&2
# Small sleep to let user see it (optional, but good for "feeling" the speed vs glitch)
sleep 0.3
fi
echo "$cache_file" echo "$cache_file"
return 0 return 0
fi fi

View File

@@ -83,6 +83,12 @@ scan_applications() {
[[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file
if [[ $cache_age -lt $cache_ttl ]]; then if [[ $cache_age -lt $cache_ttl ]]; then
# Cache hit - return immediately # Cache hit - return immediately
# Show brief flash of cache usage if in interactive mode
if [[ -t 2 ]]; then
echo -e "${GREEN}Loading from cache...${NC}" >&2
# Small sleep to let user see it (optional, but good for "feeling" the speed vs glitch)
sleep 0.3
fi
echo "$cache_file" echo "$cache_file"
return 0 return 0
fi fi

View File

@@ -24,76 +24,50 @@ show_cursor() { [[ -t 1 ]] && printf '\033[?25h' >&2 || true; }
get_display_width() { get_display_width() {
local str="$1" local str="$1"
# Check Python availability once and cache the result # Optimized pure bash implementation without forks
# Use Python for accurate width calculation if available (cached check) local width
if [[ -z "${MOLE_PYTHON_AVAILABLE:-}" ]]; then
if command -v python3 > /dev/null 2>&1; then
export MOLE_PYTHON_AVAILABLE=1
else
export MOLE_PYTHON_AVAILABLE=0
fi
fi
if [[ "${MOLE_PYTHON_AVAILABLE:-0}" == "1" ]]; then # Save current locale
python3 -c " local old_lc="${LC_ALL:-}"
import sys
import unicodedata
s = sys.argv[1]
width = 0
for char in s:
# East Asian Width property
ea_width = unicodedata.east_asian_width(char)
if ea_width in ('F', 'W'): # Fullwidth or Wide
width += 2
else:
width += 1
print(width)
" "$str" 2> /dev/null && return
fi
# Fallback: Use wc with UTF-8 locale temporarily
local saved_lc_all="${LC_ALL:-}"
local saved_lang="${LANG:-}"
# Get Char Count (UTF-8)
# We must export ensuring it applies to the expansion (though just assignment often works in newer bash, export is safer for all subshells/cmds)
export LC_ALL=en_US.UTF-8 export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8 local char_count=${#str}
local char_count byte_count width # Get Byte Count (C)
char_count=$(printf '%s' "$str" | wc -m 2> /dev/null | tr -d ' ') export LC_ALL=C
byte_count=$(printf '%s' "$str" | wc -c 2> /dev/null | tr -d ' ') local byte_count=${#str}
# Restore locale # Restore Locale immediately
if [[ -n "$saved_lc_all" ]]; then if [[ -n "$old_lc" ]]; then
export LC_ALL="$saved_lc_all" export LC_ALL="$old_lc"
else else
unset LC_ALL unset LC_ALL
fi fi
if [[ -n "$saved_lang" ]]; then
export LANG="$saved_lang" if [[ $byte_count -eq $char_count ]]; then
else echo "$char_count"
unset LANG return
fi fi
# Estimate: if byte_count > char_count, we have multibyte chars # CJK Heuristic:
# Rough approximation: each multibyte char (CJK) is ~3 bytes and width 2 # Most CJK chars are 3 bytes in UTF-8 and width 2.
# ASCII chars are 1 byte and width 1 # ASCII chars are 1 byte and width 1.
if [[ $byte_count -gt $char_count ]]; then # Width ~= CharCount + (ByteCount - CharCount) / 2
local multibyte_chars=$((byte_count - char_count)) # "中" (1 char, 3 bytes) -> 1 + (2)/2 = 2.
# Assume most multibyte chars are 2 bytes extra (3 bytes total for UTF-8 CJK) # "A" (1 char, 1 byte) -> 1 + 0 = 1.
local cjk_chars=$((multibyte_chars / 2)) # This is an approximation but very fast and sufficient for App names.
local ascii_chars=$((char_count - cjk_chars)) # Integer arithmetic in bash automatically handles floor.
width=$((ascii_chars + cjk_chars * 2)) local extra_bytes=$((byte_count - char_count))
else local padding=$((extra_bytes / 2))
width=$char_count width=$((char_count + padding))
fi
echo "$width" echo "$width"
} }
# Truncate string by display width (handles CJK correctly) # Truncate string by display width (handles CJK correctly)
# Args: $1 - string, $2 - max display width # Args: $1 - string, $2 - max display width
# Returns: truncated string with "..." if needed
truncate_by_display_width() { truncate_by_display_width() {
local str="$1" local str="$1"
local max_width="$2" local max_width="$2"
@@ -105,45 +79,48 @@ truncate_by_display_width() {
return return
fi fi
# Use Python for accurate truncation if available (use cached check)
if [[ "${MOLE_PYTHON_AVAILABLE:-0}" == "1" ]]; then
python3 -c "
import sys
import unicodedata
s = sys.argv[1] # Fallback: Use pure bash character iteration
max_w = int(sys.argv[2]) # Since we need to know the width of *each* character to truncate at the right spot,
result = '' # we cannot just use the total width formula on the whole string.
width = 0 # However, iterating char-by-char and calling the optimized get_display_width function
# is now much faster because it doesn't fork 'wc'.
for char in s: # CRITICAL: Switch to UTF-8 for correct character iteration
ea_width = unicodedata.east_asian_width(char) local old_lc="${LC_ALL:-}"
char_width = 2 if ea_width in ('F', 'W') else 1
if width + char_width + 3 > max_w: # +3 for '...'
break
result += char
width += char_width
print(result + '...')
" "$str" "$max_width" 2> /dev/null && return
fi
# Fallback: Use UTF-8 locale for proper string handling
local saved_lc_all="${LC_ALL:-}"
local saved_lang="${LANG:-}"
export LC_ALL=en_US.UTF-8 export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
local truncated="" local truncated=""
local width=0 local width=0
local i=0 local i=0
local char char_width local char char_width
local strlen=${#str} # Re-calculate in UTF-8
while [[ $i -lt ${#str} ]]; do # Optimization: If total width <= max_width, return original string (checked above)
while [[ $i -lt $strlen ]]; do
char="${str:$i:1}" char="${str:$i:1}"
char_width=$(get_display_width "$char")
# Inlined width calculation for minimal overhead to avoid recursion overhead
# We are already in UTF-8, so ${#char} is char length (1).
# We need byte length for the heuristic.
# But switching locale inside loop is disastrous for perf.
# Logic: If char is ASCII (1 byte), width 1.
# If char is wide (3 bytes), width 2.
# How to detect byte size without switching locale?
# printf %s "$char" | wc -c ? Slow.
# Check against ASCII range?
# Fast ASCII check: if [[ "$char" < $'\x7f' ]]; then ...
if [[ "$char" =~ [[:ascii:]] ]]; then
char_width=1
else
# Assume wide for non-ascii in this context (simplified)
# Or use LC_ALL=C inside? No.
# Most non-ASCII in filenames are either CJK (width 2) or heavy symbols.
# Let's assume 2 for simplicity in this fast loop as we know we are usually dealing with CJK.
char_width=2
fi
if ((width + char_width + 3 > max_width)); then if ((width + char_width + 3 > max_width)); then
break break
@@ -155,16 +132,11 @@ print(result + '...')
done done
# Restore locale # Restore locale
if [[ -n "$saved_lc_all" ]]; then if [[ -n "$old_lc" ]]; then
export LC_ALL="$saved_lc_all" export LC_ALL="$old_lc"
else else
unset LC_ALL unset LC_ALL
fi fi
if [[ -n "$saved_lang" ]]; then
export LANG="$saved_lang"
else
unset LANG
fi
echo "${truncated}..." echo "${truncated}..."
} }

View File

@@ -18,7 +18,8 @@ format_app_display() {
[[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size" [[ "$size" != "0" && "$size" != "" && "$size" != "Unknown" ]] && size_str="$size"
# Calculate available width for app name based on terminal width # Calculate available width for app name based on terminal width
local terminal_width=$(tput cols 2> /dev/null || echo 80) # use passed width or calculate it (but calculation is slow in loops)
local terminal_width="${4:-$(tput cols 2> /dev/null || echo 80)}"
local fixed_width=28 local fixed_width=28
local available_width=$((terminal_width - fixed_width)) local available_width=$((terminal_width - fixed_width))
@@ -58,7 +59,8 @@ select_apps_for_uninstall() {
# Build menu options # Build menu options
# Show loading for large lists (formatting can be slow due to width calculations) # Show loading for large lists (formatting can be slow due to width calculations)
local app_count=${#apps_data[@]} local app_count=${#apps_data[@]}
if [[ $app_count -gt 30 ]]; then local terminal_width=$(tput cols 2> /dev/null || echo 80)
if [[ $app_count -gt 100 ]]; then
if [[ -t 2 ]]; then if [[ -t 2 ]]; then
printf "\rPreparing %d applications... " "$app_count" >&2 printf "\rPreparing %d applications... " "$app_count" >&2
fi fi
@@ -72,7 +74,7 @@ select_apps_for_uninstall() {
for app_data in "${apps_data[@]}"; do for app_data in "${apps_data[@]}"; do
# Keep extended field 7 (size_kb) if present # Keep extended field 7 (size_kb) if present
IFS='|' read -r epoch _ display_name _ size last_used size_kb <<< "$app_data" IFS='|' read -r epoch _ display_name _ size last_used size_kb <<< "$app_data"
menu_options+=("$(format_app_display "$display_name" "$size" "$last_used")") menu_options+=("$(format_app_display "$display_name" "$size" "$last_used" "$terminal_width")")
# Build csv lists (avoid trailing commas) # Build csv lists (avoid trailing commas)
if [[ $idx -eq 0 ]]; then if [[ $idx -eq 0 ]]; then
epochs_csv="${epoch:-0}" epochs_csv="${epoch:-0}"
@@ -85,7 +87,7 @@ select_apps_for_uninstall() {
done done
# Clear loading message # Clear loading message
if [[ $app_count -gt 30 ]]; then if [[ $app_count -gt 100 ]]; then
if [[ -t 2 ]]; then if [[ -t 2 ]]; then
printf "\r\033[K" >&2 printf "\r\033[K" >&2
fi fi