#!/bin/bash # Check if file argument is provided if [ $# -ne 1 ]; then echo "Usage: $0 " 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