mirror of
https://github.com/tw93/Mole.git
synced 2026-02-16 21:39:11 +00:00
All lists highly support terminal-based high adaptability
This commit is contained in:
BIN
bin/analyze
Executable file
BIN
bin/analyze
Executable file
Binary file not shown.
BIN
bin/analyze-go
BIN
bin/analyze-go
Binary file not shown.
BIN
bin/status-go
BIN
bin/status-go
Binary file not shown.
@@ -7,8 +7,7 @@ const (
|
|||||||
maxLargeFiles = 30
|
maxLargeFiles = 30
|
||||||
barWidth = 24
|
barWidth = 24
|
||||||
minLargeFileSize = 100 << 20 // 100 MB
|
minLargeFileSize = 100 << 20 // 100 MB
|
||||||
entryViewport = 12
|
defaultViewport = 12 // Default viewport when terminal height is unknown
|
||||||
largeViewport = 12
|
|
||||||
overviewCacheTTL = 7 * 24 * time.Hour // 7 days
|
overviewCacheTTL = 7 * 24 * time.Hour // 7 days
|
||||||
overviewCacheFile = "overview_sizes.json"
|
overviewCacheFile = "overview_sizes.json"
|
||||||
duTimeout = 60 * time.Second // Increased for large directories
|
duTimeout = 60 * time.Second // Increased for large directories
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ type model struct {
|
|||||||
overviewCurrentPath *string
|
overviewCurrentPath *string
|
||||||
overviewScanning bool
|
overviewScanning bool
|
||||||
overviewScanningSet map[string]bool // Track which paths are currently being scanned
|
overviewScanningSet map[string]bool // Track which paths are currently being scanned
|
||||||
|
width int // Terminal width
|
||||||
|
height int // Terminal height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) inOverviewMode() bool {
|
func (m model) inOverviewMode() bool {
|
||||||
@@ -379,6 +381,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
return m.updateKey(msg)
|
return m.updateKey(msg)
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
m.height = msg.Height
|
||||||
|
return m, nil
|
||||||
case deleteProgressMsg:
|
case deleteProgressMsg:
|
||||||
if msg.done {
|
if msg.done {
|
||||||
m.deleting = false
|
m.deleting = false
|
||||||
@@ -560,14 +566,16 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
if m.showLargeFiles {
|
if m.showLargeFiles {
|
||||||
if m.largeSelected < len(m.largeFiles)-1 {
|
if m.largeSelected < len(m.largeFiles)-1 {
|
||||||
m.largeSelected++
|
m.largeSelected++
|
||||||
if m.largeSelected >= m.largeOffset+largeViewport {
|
viewport := calculateViewport(m.height, true)
|
||||||
m.largeOffset = m.largeSelected - largeViewport + 1
|
if m.largeSelected >= m.largeOffset+viewport {
|
||||||
|
m.largeOffset = m.largeSelected - viewport + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if len(m.entries) > 0 && m.selected < len(m.entries)-1 {
|
} else if len(m.entries) > 0 && m.selected < len(m.entries)-1 {
|
||||||
m.selected++
|
m.selected++
|
||||||
if m.selected >= m.offset+entryViewport {
|
viewport := calculateViewport(m.height, false)
|
||||||
m.offset = m.selected - entryViewport + 1
|
if m.selected >= m.offset+viewport {
|
||||||
|
m.offset = m.selected - viewport + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "enter":
|
case "enter":
|
||||||
@@ -863,11 +871,12 @@ func (m model) View() string {
|
|||||||
if len(m.largeFiles) == 0 {
|
if len(m.largeFiles) == 0 {
|
||||||
fmt.Fprintln(&b, " No large files found (>=100MB)")
|
fmt.Fprintln(&b, " No large files found (>=100MB)")
|
||||||
} else {
|
} else {
|
||||||
|
viewport := calculateViewport(m.height, true)
|
||||||
start := m.largeOffset
|
start := m.largeOffset
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
end := start + largeViewport
|
end := start + viewport
|
||||||
if end > len(m.largeFiles) {
|
if end > len(m.largeFiles) {
|
||||||
end = len(m.largeFiles)
|
end = len(m.largeFiles)
|
||||||
}
|
}
|
||||||
@@ -994,11 +1003,12 @@ func (m model) View() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewport := calculateViewport(m.height, false)
|
||||||
start := m.offset
|
start := m.offset
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
end := start + entryViewport
|
end := start + viewport
|
||||||
if end > len(m.entries) {
|
if end > len(m.entries) {
|
||||||
end = len(m.entries)
|
end = len(m.entries)
|
||||||
}
|
}
|
||||||
@@ -1111,7 +1121,8 @@ func (m *model) clampEntrySelection() {
|
|||||||
if m.selected < 0 {
|
if m.selected < 0 {
|
||||||
m.selected = 0
|
m.selected = 0
|
||||||
}
|
}
|
||||||
maxOffset := len(m.entries) - entryViewport
|
viewport := calculateViewport(m.height, false)
|
||||||
|
maxOffset := len(m.entries) - viewport
|
||||||
if maxOffset < 0 {
|
if maxOffset < 0 {
|
||||||
maxOffset = 0
|
maxOffset = 0
|
||||||
}
|
}
|
||||||
@@ -1121,8 +1132,8 @@ func (m *model) clampEntrySelection() {
|
|||||||
if m.selected < m.offset {
|
if m.selected < m.offset {
|
||||||
m.offset = m.selected
|
m.offset = m.selected
|
||||||
}
|
}
|
||||||
if m.selected >= m.offset+entryViewport {
|
if m.selected >= m.offset+viewport {
|
||||||
m.offset = m.selected - entryViewport + 1
|
m.offset = m.selected - viewport + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1138,7 +1149,8 @@ func (m *model) clampLargeSelection() {
|
|||||||
if m.largeSelected < 0 {
|
if m.largeSelected < 0 {
|
||||||
m.largeSelected = 0
|
m.largeSelected = 0
|
||||||
}
|
}
|
||||||
maxOffset := len(m.largeFiles) - largeViewport
|
viewport := calculateViewport(m.height, true)
|
||||||
|
maxOffset := len(m.largeFiles) - viewport
|
||||||
if maxOffset < 0 {
|
if maxOffset < 0 {
|
||||||
maxOffset = 0
|
maxOffset = 0
|
||||||
}
|
}
|
||||||
@@ -1148,8 +1160,8 @@ func (m *model) clampLargeSelection() {
|
|||||||
if m.largeSelected < m.largeOffset {
|
if m.largeSelected < m.largeOffset {
|
||||||
m.largeOffset = m.largeSelected
|
m.largeOffset = m.largeSelected
|
||||||
}
|
}
|
||||||
if m.largeSelected >= m.largeOffset+largeViewport {
|
if m.largeSelected >= m.largeOffset+viewport {
|
||||||
m.largeOffset = m.largeSelected - largeViewport + 1
|
m.largeOffset = m.largeSelected - viewport + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1226,3 +1238,29 @@ func scanOverviewPathCmd(path string, index int) tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateViewport dynamically calculates the viewport size based on terminal height
|
||||||
|
func calculateViewport(termHeight int, isLargeFiles bool) int {
|
||||||
|
if termHeight <= 0 {
|
||||||
|
// Terminal height unknown, use default
|
||||||
|
return defaultViewport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate reserved space for UI elements
|
||||||
|
reserved := 6 // header (3-4 lines) + footer (2 lines)
|
||||||
|
if isLargeFiles {
|
||||||
|
reserved = 5 // Large files view has less overhead
|
||||||
|
}
|
||||||
|
|
||||||
|
available := termHeight - reserved
|
||||||
|
|
||||||
|
// Ensure minimum and maximum bounds
|
||||||
|
if available < 1 {
|
||||||
|
return 1 // Minimum 1 line for very short terminals
|
||||||
|
}
|
||||||
|
if available > 30 {
|
||||||
|
return 30 // Maximum 30 lines to avoid information overload
|
||||||
|
}
|
||||||
|
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,44 @@ leave_alt_screen() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get terminal height with fallback
|
||||||
|
_pm_get_terminal_height() {
|
||||||
|
local height=0
|
||||||
|
|
||||||
|
# Try stty size first (most reliable, real-time)
|
||||||
|
# Use </dev/tty to ensure we read from terminal even if stdin is redirected
|
||||||
|
if [[ -t 0 ]] || [[ -t 2 ]]; then
|
||||||
|
height=$(stty size </dev/tty 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback to tput
|
||||||
|
if [[ -z "$height" || $height -le 0 ]]; then
|
||||||
|
if command -v tput > /dev/null 2>&1; then
|
||||||
|
height=$(tput lines 2>/dev/null || echo "24")
|
||||||
|
else
|
||||||
|
height=24
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$height"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate dynamic items per page based on terminal height
|
||||||
|
_pm_calculate_items_per_page() {
|
||||||
|
local term_height=$(_pm_get_terminal_height)
|
||||||
|
local reserved=6 # header(2) + footer(3) + spacing(1)
|
||||||
|
local available=$((term_height - reserved))
|
||||||
|
|
||||||
|
# Ensure minimum and maximum bounds
|
||||||
|
if [[ $available -lt 1 ]]; then
|
||||||
|
echo 1
|
||||||
|
elif [[ $available -gt 50 ]]; then
|
||||||
|
echo 50
|
||||||
|
else
|
||||||
|
echo "$available"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Parse CSV into newline list (Bash 3.2)
|
# Parse CSV into newline list (Bash 3.2)
|
||||||
_pm_parse_csv_to_array() {
|
_pm_parse_csv_to_array() {
|
||||||
local csv="${1:-}"
|
local csv="${1:-}"
|
||||||
@@ -44,7 +82,7 @@ paginated_multi_select() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local total_items=${#items[@]}
|
local total_items=${#items[@]}
|
||||||
local items_per_page=12
|
local items_per_page=$(_pm_calculate_items_per_page)
|
||||||
local cursor_pos=0
|
local cursor_pos=0
|
||||||
local top_index=0
|
local top_index=0
|
||||||
local filter_query=""
|
local filter_query=""
|
||||||
@@ -336,6 +374,9 @@ paginated_multi_select() {
|
|||||||
|
|
||||||
# Draw the complete menu
|
# Draw the complete menu
|
||||||
draw_menu() {
|
draw_menu() {
|
||||||
|
# Recalculate items_per_page dynamically to handle window resize
|
||||||
|
items_per_page=$(_pm_calculate_items_per_page)
|
||||||
|
|
||||||
printf "\033[H" >&2
|
printf "\033[H" >&2
|
||||||
local clear_line="\r\033[2K"
|
local clear_line="\r\033[2K"
|
||||||
|
|
||||||
@@ -353,7 +394,7 @@ paginated_multi_select() {
|
|||||||
if [[ $visible_total -eq 0 ]]; then
|
if [[ $visible_total -eq 0 ]]; then
|
||||||
if [[ "$filter_mode" == "true" ]]; then
|
if [[ "$filter_mode" == "true" ]]; then
|
||||||
# While editing: do not show "No items available"
|
# While editing: do not show "No items available"
|
||||||
for ((i = 0; i < items_per_page + 2; i++)); do
|
for ((i = 0; i < items_per_page; i++)); do
|
||||||
printf "${clear_line}\n" >&2
|
printf "${clear_line}\n" >&2
|
||||||
done
|
done
|
||||||
printf "${clear_line}${GRAY}Type to filter | Delete | Enter | / Exit | ESC${NC}\n" >&2
|
printf "${clear_line}${GRAY}Type to filter | Delete | Enter | / Exit | ESC${NC}\n" >&2
|
||||||
@@ -362,7 +403,7 @@ paginated_multi_select() {
|
|||||||
else
|
else
|
||||||
if [[ "$searching" == "true" ]]; then
|
if [[ "$searching" == "true" ]]; then
|
||||||
printf "${clear_line}Searching…\n" >&2
|
printf "${clear_line}Searching…\n" >&2
|
||||||
for ((i = 0; i < items_per_page + 2; i++)); do
|
for ((i = 0; i < items_per_page; i++)); do
|
||||||
printf "${clear_line}\n" >&2
|
printf "${clear_line}\n" >&2
|
||||||
done
|
done
|
||||||
printf "${clear_line}${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit${NC}\n" >&2
|
printf "${clear_line}${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit${NC}\n" >&2
|
||||||
@@ -371,7 +412,7 @@ paginated_multi_select() {
|
|||||||
else
|
else
|
||||||
# Post-search: truly empty list
|
# Post-search: truly empty list
|
||||||
printf "${clear_line}No items available\n" >&2
|
printf "${clear_line}No items available\n" >&2
|
||||||
for ((i = 0; i < items_per_page + 2; i++)); do
|
for ((i = 0; i < items_per_page; i++)); do
|
||||||
printf "${clear_line}\n" >&2
|
printf "${clear_line}\n" >&2
|
||||||
done
|
done
|
||||||
printf "${clear_line}${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit${NC}\n" >&2
|
printf "${clear_line}${GRAY}${ICON_NAV_UP}${ICON_NAV_DOWN} | Space | Enter | / Filter | Q Exit${NC}\n" >&2
|
||||||
|
|||||||
@@ -7,6 +7,45 @@ set -euo pipefail
|
|||||||
enter_alt_screen() { tput smcup 2> /dev/null || true; }
|
enter_alt_screen() { tput smcup 2> /dev/null || true; }
|
||||||
leave_alt_screen() { tput rmcup 2> /dev/null || true; }
|
leave_alt_screen() { tput rmcup 2> /dev/null || true; }
|
||||||
|
|
||||||
|
# Get terminal height with fallback
|
||||||
|
_ms_get_terminal_height() {
|
||||||
|
local height=0
|
||||||
|
|
||||||
|
# Try stty size first (most reliable, real-time)
|
||||||
|
# Use </dev/tty to ensure we read from terminal even if stdin is redirected
|
||||||
|
if [[ -t 0 ]] || [[ -t 2 ]]; then
|
||||||
|
height=$(stty size </dev/tty 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback to tput
|
||||||
|
if [[ -z "$height" || $height -le 0 ]]; then
|
||||||
|
if command -v tput > /dev/null 2>&1; then
|
||||||
|
height=$(tput lines 2>/dev/null || echo "24")
|
||||||
|
else
|
||||||
|
height=24
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$height"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate dynamic items per page based on terminal height
|
||||||
|
_ms_calculate_items_per_page() {
|
||||||
|
local term_height=$(_ms_get_terminal_height)
|
||||||
|
# Layout: header(1) + spacing(1) + items + spacing(1) + footer(1) + clear(1) = 5 fixed lines
|
||||||
|
local reserved=6 # Increased to prevent header from being overwritten
|
||||||
|
local available=$((term_height - reserved))
|
||||||
|
|
||||||
|
# Ensure minimum and maximum bounds
|
||||||
|
if [[ $available -lt 1 ]]; then
|
||||||
|
echo 1
|
||||||
|
elif [[ $available -gt 50 ]]; then
|
||||||
|
echo 50
|
||||||
|
else
|
||||||
|
echo "$available"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Main paginated multi-select menu function
|
# Main paginated multi-select menu function
|
||||||
paginated_multi_select() {
|
paginated_multi_select() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
@@ -24,7 +63,7 @@ paginated_multi_select() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local total_items=${#items[@]}
|
local total_items=${#items[@]}
|
||||||
local items_per_page=12
|
local items_per_page=$(_ms_calculate_items_per_page)
|
||||||
local cursor_pos=0
|
local cursor_pos=0
|
||||||
local top_index=0
|
local top_index=0
|
||||||
local -a selected=()
|
local -a selected=()
|
||||||
@@ -106,6 +145,9 @@ paginated_multi_select() {
|
|||||||
|
|
||||||
# Draw the complete menu
|
# Draw the complete menu
|
||||||
draw_menu() {
|
draw_menu() {
|
||||||
|
# Recalculate items_per_page dynamically to handle window resize
|
||||||
|
items_per_page=$(_ms_calculate_items_per_page)
|
||||||
|
|
||||||
# Move to home position without clearing (reduces flicker)
|
# Move to home position without clearing (reduces flicker)
|
||||||
printf "\033[H" >&2
|
printf "\033[H" >&2
|
||||||
|
|
||||||
|
|||||||
@@ -202,12 +202,6 @@ manage_whitelist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
manage_whitelist_categories() {
|
manage_whitelist_categories() {
|
||||||
clear
|
|
||||||
echo ""
|
|
||||||
echo -e "${PURPLE}Whitelist Manager${NC}"
|
|
||||||
echo ""
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Load currently enabled patterns from both sources
|
# Load currently enabled patterns from both sources
|
||||||
load_whitelist
|
load_whitelist
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user