212 lines
5.4 KiB
Bash
Executable File
212 lines
5.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Check if file argument is provided
|
|
if [ $# -ne 1 ]; then
|
|
echo "Usage: $0 <path_to_file>"
|
|
exit 1
|
|
fi
|
|
|
|
FILE="$1"
|
|
|
|
# Check if file exists
|
|
if [ ! -f "$FILE" ]; then
|
|
echo "Error: File '$FILE' not found"
|
|
exit 1
|
|
fi
|
|
|
|
# Terminal control functions
|
|
hide_cursor() {
|
|
printf '\033[?25l'
|
|
}
|
|
|
|
show_cursor() {
|
|
printf '\033[?25h'
|
|
}
|
|
|
|
move_cursor() {
|
|
printf '\033[%d;%dH' "$1" "$2"
|
|
}
|
|
|
|
clear_screen() {
|
|
printf '\033[2J'
|
|
printf '\033[H'
|
|
}
|
|
|
|
cleanup() {
|
|
show_cursor
|
|
clear_screen
|
|
echo "Goodbye!"
|
|
exit 0
|
|
}
|
|
|
|
# Trap to ensure cursor is restored on exit
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# Hide cursor at start
|
|
hide_cursor
|
|
|
|
# Parse the file and store tasks
|
|
declare -a categories=()
|
|
declare -A tasks=()
|
|
declare -A checked=()
|
|
declare -a display_items=() # Store display items in order
|
|
current_category=""
|
|
task_counter=0
|
|
|
|
while IFS= read -r line; do
|
|
if [[ $line =~ ^@(.+)$ ]]; then
|
|
current_category="${BASH_REMATCH[1]}"
|
|
categories+=("$current_category")
|
|
display_items+=("CATEGORY:$current_category")
|
|
elif [[ -n "$line" && -n "$current_category" ]]; then
|
|
task_key="${current_category}_${task_counter}"
|
|
tasks["$task_key"]="$line"
|
|
checked["$task_key"]=false
|
|
display_items+=("TASK:$task_key")
|
|
((task_counter++))
|
|
fi
|
|
done < "$FILE"
|
|
|
|
# Current selection
|
|
current_selection=0
|
|
total_items=${#display_items[@]}
|
|
header_lines=3
|
|
|
|
# Initial display
|
|
initial_display() {
|
|
clear_screen
|
|
echo "Checklist - Use ↑/↓ to navigate, SPACE to toggle, q to quit"
|
|
echo "File: $FILE"
|
|
echo "=================================================="
|
|
|
|
for i in "${!display_items[@]}"; do
|
|
item="${display_items[$i]}"
|
|
if [[ $item =~ ^CATEGORY:(.+)$ ]]; then
|
|
category="${BASH_REMATCH[1]}"
|
|
echo "@$category"
|
|
elif [[ $item =~ ^TASK:(.+)$ ]]; then
|
|
task_key="${BASH_REMATCH[1]}"
|
|
checkbox="[ ]"
|
|
if [ "${checked[$task_key]}" = true ]; then
|
|
checkbox="[✓]"
|
|
fi
|
|
echo " $checkbox ${tasks[$task_key]}"
|
|
fi
|
|
echo # Empty line after each item
|
|
done
|
|
}
|
|
|
|
# Update single line
|
|
update_line() {
|
|
local line_num=$((header_lines + 1 + ($1 * 2))) # *2 because of empty lines
|
|
move_cursor $line_num 1
|
|
printf '\033[K' # Clear line
|
|
|
|
item="${display_items[$1]}"
|
|
if [[ $item =~ ^CATEGORY:(.+)$ ]]; then
|
|
category="${BASH_REMATCH[1]}"
|
|
if [ $1 -eq $current_selection ]; then
|
|
printf '\033[7m@%s\033[0m' "$category"
|
|
else
|
|
printf '@%s' "$category"
|
|
fi
|
|
elif [[ $item =~ ^TASK:(.+)$ ]]; then
|
|
task_key="${BASH_REMATCH[1]}"
|
|
checkbox="[ ]"
|
|
if [ "${checked[$task_key]}" = true ]; then
|
|
checkbox="[✓]"
|
|
fi
|
|
task_line=" $checkbox ${tasks[$task_key]}"
|
|
if [ $1 -eq $current_selection ]; then
|
|
printf '\033[7m%s\033[0m' "$task_line"
|
|
else
|
|
printf '%s' "$task_line"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Update only the checkbox part of a task line
|
|
update_checkbox() {
|
|
local line_num=$((header_lines + 1 + ($1 * 2)))
|
|
local task_key="${BASH_REMATCH[1]}"
|
|
|
|
item="${display_items[$1]}"
|
|
if [[ $item =~ ^TASK:(.+)$ ]]; then
|
|
task_key="${BASH_REMATCH[1]}"
|
|
checkbox="[ ]"
|
|
if [ "${checked[$task_key]}" = true ]; then
|
|
checkbox="[✓]"
|
|
fi
|
|
|
|
# Move to checkbox position (column 3) and update just the checkbox
|
|
move_cursor $line_num 3
|
|
if [ $1 -eq $current_selection ]; then
|
|
printf '\033[7m%s\033[0m' "$checkbox"
|
|
else
|
|
printf '%s' "$checkbox"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Get the task key at current selection
|
|
get_current_task_key() {
|
|
item="${display_items[$current_selection]}"
|
|
if [[ $item =~ ^TASK:(.+)$ ]]; then
|
|
echo "${BASH_REMATCH[1]}"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Show initial display
|
|
initial_display
|
|
|
|
# Highlight first item
|
|
update_line 0
|
|
|
|
# Main loop
|
|
prev_selection=0
|
|
while true; do
|
|
# Read single character
|
|
IFS= read -rsn1 input
|
|
|
|
# Handle different input cases
|
|
if [[ $input == $'\x1b' ]]; then
|
|
# ESC sequence - read next two characters
|
|
IFS= read -rsn2 input
|
|
case $input in
|
|
'[A') # Up arrow
|
|
if [ $current_selection -gt 0 ]; then
|
|
prev_selection=$current_selection
|
|
((current_selection--))
|
|
update_line $prev_selection
|
|
update_line $current_selection
|
|
fi
|
|
;;
|
|
'[B') # Down arrow
|
|
if [ $current_selection -lt $((total_items - 1)) ]; then
|
|
prev_selection=$current_selection
|
|
((current_selection++))
|
|
update_line $prev_selection
|
|
update_line $current_selection
|
|
fi
|
|
;;
|
|
esac
|
|
elif [[ $input == ' ' ]]; then
|
|
# Space bar - toggle checkbox
|
|
task_key=$(get_current_task_key)
|
|
if [ -n "$task_key" ]; then
|
|
if [ "${checked[$task_key]}" = true ]; then
|
|
checked["$task_key"]=false
|
|
else
|
|
checked["$task_key"]=true
|
|
fi
|
|
# Only update the checkbox, not the entire line
|
|
update_checkbox $current_selection
|
|
fi
|
|
elif [[ $input == 'q' ]] || [[ $input == 'Q' ]]; then
|
|
# Quit - cleanup will be called by trap
|
|
exit 0
|
|
fi
|
|
done
|