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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
152
lib/core/ui.sh
152
lib/core/ui.sh
@@ -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}..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user