|
18 | 18 | import json |
19 | 19 | import glob |
20 | 20 | import re |
21 | | -import tty |
22 | | -import termios |
23 | 21 | from datetime import datetime |
24 | 22 | from pathlib import Path |
25 | 23 | from typing import List, Dict, Optional, Tuple, Set |
26 | 24 |
|
| 25 | +# Platform-specific terminal handling |
| 26 | +_IS_WINDOWS = sys.platform == 'win32' |
| 27 | +if not _IS_WINDOWS: |
| 28 | + import tty |
| 29 | + import termios |
| 30 | + |
27 | 31 | # ────────────────────────────────────────────────────────────── |
28 | 32 | # Terminal Styling |
29 | 33 | # ────────────────────────────────────────────────────────────── |
@@ -771,27 +775,46 @@ def _cap_text(text: str) -> str: |
771 | 775 | # ────────────────────────────────────────────────────────────── |
772 | 776 | # Keyboard Input (raw terminal, single keypress) |
773 | 777 | # ────────────────────────────────────────────────────────────── |
| 778 | +def _clear_screen(): |
| 779 | + """Cross-platform screen clear.""" |
| 780 | + os.system('cls' if _IS_WINDOWS else 'clear') |
| 781 | + |
774 | 782 | def read_key() -> str: |
775 | | - fd = sys.stdin.fileno() |
776 | | - old = termios.tcgetattr(fd) |
777 | | - try: |
778 | | - tty.setraw(fd) |
779 | | - ch = sys.stdin.read(1) |
780 | | - if ch == '\x1b': |
781 | | - ch2 = sys.stdin.read(1) |
782 | | - if ch2 == '[': |
783 | | - ch3 = sys.stdin.read(1) |
784 | | - return {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}.get(ch3, '') |
785 | | - return 'ESC' |
| 783 | + """Read a single keypress cross-platform.""" |
| 784 | + if _IS_WINDOWS: |
| 785 | + import msvcrt |
| 786 | + ch = msvcrt.getwch() |
786 | 787 | if ch in ('\r', '\n'): |
787 | 788 | return 'ENTER' |
788 | 789 | if ch == ' ': |
789 | 790 | return 'SPACE' |
790 | 791 | if ch == '\x03': |
791 | 792 | raise KeyboardInterrupt |
| 793 | + if ch == '\xe0' or ch == '\x00': # special key prefix on Windows |
| 794 | + ext = msvcrt.getwch() |
| 795 | + return {'H': 'UP', 'P': 'DOWN', 'M': 'RIGHT', 'K': 'LEFT'}.get(ext, '') |
792 | 796 | return ch.upper() |
793 | | - finally: |
794 | | - termios.tcsetattr(fd, termios.TCSADRAIN, old) |
| 797 | + else: |
| 798 | + fd = sys.stdin.fileno() |
| 799 | + old = termios.tcgetattr(fd) |
| 800 | + try: |
| 801 | + tty.setraw(fd) |
| 802 | + ch = sys.stdin.read(1) |
| 803 | + if ch == '\x1b': |
| 804 | + ch2 = sys.stdin.read(1) |
| 805 | + if ch2 == '[': |
| 806 | + ch3 = sys.stdin.read(1) |
| 807 | + return {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}.get(ch3, '') |
| 808 | + return 'ESC' |
| 809 | + if ch in ('\r', '\n'): |
| 810 | + return 'ENTER' |
| 811 | + if ch == ' ': |
| 812 | + return 'SPACE' |
| 813 | + if ch == '\x03': |
| 814 | + raise KeyboardInterrupt |
| 815 | + return ch.upper() |
| 816 | + finally: |
| 817 | + termios.tcsetattr(fd, termios.TCSADRAIN, old) |
795 | 818 |
|
796 | 819 | # ────────────────────────────────────────────────────────────── |
797 | 820 | # Interactive Section Filter |
@@ -907,7 +930,7 @@ def get_lines_for_state(cap_out: int, cap_user: int, cap_agent: int, cap_reason: |
907 | 930 | pct = (selected_lines / total_lines * 100) if total_lines > 0 else 0 |
908 | 931 |
|
909 | 932 | # ── Render ── |
910 | | - os.system('clear') |
| 933 | + _clear_screen() |
911 | 934 | sessions_label = f"{len(parsers)} session{'s' if len(parsers) > 1 else ''}" |
912 | 935 | print(f"\n {Style.BOLD}{Style.HEADER}SECTION FILTER{Style.RESET} {Style.DIM}({sessions_label}){Style.RESET}") |
913 | 936 | print(f" {Style.DIM}{'━' * 62}{Style.RESET}\n") |
@@ -1189,7 +1212,7 @@ def process_conversion(indices_str: str, files: List[Path]): |
1189 | 1212 | input(f"\n{Style.DIM}Press Enter to return to menu...{Style.RESET}") |
1190 | 1213 | return |
1191 | 1214 |
|
1192 | | - os.system('clear') |
| 1215 | + _clear_screen() |
1193 | 1216 |
|
1194 | 1217 | # Ask for export destination |
1195 | 1218 | dest_choice = '' |
|
0 commit comments