import sys import os CHECKED = "[x]" UNCHECKED = "[ ]" if os.name == "nt": import msvcrt def getch(): ch = msvcrt.getwch() if ch in ("\x00", "\xe0"): # special key prefix return "\x1b" + msvcrt.getwch() return ch else: import tty, termios def getch(): fd = sys.stdin.fileno() old = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) if ch == "\x1b": sys.stdin.read(1) # [ return "\x1b" + sys.stdin.read(1) return ch finally: termios.tcsetattr(fd, termios.TCSADRAIN, old) def clear(): os.system("cls" if os.name == "nt" else "clear") def render(items, checked, cursor): clear() print(" Checklist (↑↓ move, Space toggle, + add, A all, N none, q quit)\n") for i, item in enumerate(items): mark = CHECKED if checked[i] else UNCHECKED pointer = ">" if i == cursor else " " print(f" {pointer} {mark} {item}") done = sum(checked) print(f"\n {done}/{len(items)} done") def prompt_new_task(): print("\n New task (Enter to confirm, Esc to cancel): ", end="", flush=True) buf = [] while True: if os.name == "nt": ch = msvcrt.getwch() else: import tty, termios fd = sys.stdin.fileno() old = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old) if ch in ("\r", "\n"): print() return "".join(buf).strip() or None elif ch == "\x1b": print() return None elif ch in ("\x08", "\x7f"): # backspace if buf: buf.pop() sys.stdout.write("\b \b") sys.stdout.flush() elif ch == "\x03": raise KeyboardInterrupt elif ch >= " ": buf.append(ch) sys.stdout.write(ch) sys.stdout.flush() def run(items): items = list(items) checked = [False] * len(items) cursor = 0 # Windows arrow key codes via msvcrt: up=H, down=P UP = "\x1bH" if os.name == "nt" else "\x1bA" DOWN = "\x1bP" if os.name == "nt" else "\x1bB" while True: render(items, checked, cursor) ch = getch() if ch == UP: cursor = (cursor - 1) % len(items) elif ch == DOWN: cursor = (cursor + 1) % len(items) elif ch == " ": checked[cursor] = not checked[cursor] elif ch in ("+", "i"): task = prompt_new_task() if task: items.append(task) checked.append(False) cursor = len(items) - 1 elif ch == "A": checked = [True] * len(items) elif ch == "N": checked = [False] * len(items) elif ch in ("q", "\x03", "\x1b"): break clear() print("Final checklist:") for i, item in enumerate(items): mark = CHECKED if checked[i] else UNCHECKED print(f" {mark} {item}") DEFAULT_ITEMS = [ "Buy groceries", "Write unit tests", "Review pull request", "Update documentation", "Deploy to staging", ] if __name__ == "__main__": items = sys.argv[1:] if len(sys.argv) > 1 else DEFAULT_ITEMS run(items)