shore up keyboard problems on rename mode
- SHA
f7bf5a269ccd4c73301309c53a4b9664c12f8599- Parents
-
73f447a - Tree
e58fa89
f7bf5a2
f7bf5a269ccd4c73301309c53a4b9664c12f859973f447a
e58fa89| Status | File | + | - |
|---|---|---|---|
| M |
src/display_module.f90
|
1 | 1 |
| M |
src/fuss_main.f90
|
25 | 16 |
| M |
src/terminal_module.f90
|
65 | 24 |
src/display_module.f90modified@@ -180,7 +180,7 @@ contains | ||
| 180 | 180 | print '(A)', '' |
| 181 | 181 | if (in_rename_mode) then |
| 182 | 182 | ! Rename mode help - show in cyan |
| 183 | - print '(A)', achar(27) // '[36mRENAME MODE: Type name (caps A/B ok, C/D use arrows) | ←/→:cursor | Backspace:del | Tab:save | ESC:cancel' // achar(27) // '[0m' | |
| 183 | + print '(A)', achar(27) // '[36mRENAME MODE: Type name (all letters/numbers ok) | ←/→:cursor | Backspace:del | Tab:save | ESC:cancel' // achar(27) // '[0m' | |
| 184 | 184 | else if (mode == 'git') then |
| 185 | 185 | ! Git mode help - show in yellow tint |
| 186 | 186 | print '(A)', achar(27) // '[33mLegend: ' // achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // & |
src/fuss_main.f90modified@@ -465,24 +465,26 @@ contains | ||
| 465 | 465 | needs_full_redraw = .true. |
| 466 | 466 | end if |
| 467 | 467 | cycle |
| 468 | - ! NOTE: Arrow keys (after escape processing) are indistinguishable from uppercase A/B/C/D | |
| 469 | - ! Trade-off: Prioritize arrow functionality over uppercase C/D letters | |
| 470 | - ! Uppercase A/B work fine, C/D reserved for arrows | |
| 471 | - else if (key == 'C') then | |
| 472 | - ! Right arrow - move cursor right (also blocks uppercase C) | |
| 468 | + ! Arrow keys are now encoded as control codes: Up=28, Down=29, Right=30, Left=31 | |
| 469 | + ! This allows uppercase C and D to work in rename mode | |
| 470 | + else if (key == achar(30)) then | |
| 471 | + ! Right arrow - move cursor right | |
| 473 | 472 | if (rename_cursor_pos < len_trim(rename_buffer)) then |
| 474 | 473 | rename_cursor_pos = rename_cursor_pos + 1 |
| 475 | 474 | needs_full_redraw = .true. |
| 476 | 475 | end if |
| 477 | 476 | cycle |
| 478 | - else if (key == 'D') then | |
| 479 | - ! Left arrow - move cursor left (also blocks uppercase D) | |
| 477 | + else if (key == achar(31)) then | |
| 478 | + ! Left arrow - move cursor left | |
| 480 | 479 | if (rename_cursor_pos > 0) then |
| 481 | 480 | rename_cursor_pos = rename_cursor_pos - 1 |
| 482 | 481 | needs_full_redraw = .true. |
| 483 | 482 | end if |
| 484 | 483 | cycle |
| 485 | - ! A and B (up/down arrows) are allowed as uppercase letters - arrows ignored | |
| 484 | + ! Up/Down arrows (28, 29) are ignored in rename mode | |
| 485 | + else if (key == achar(28) .or. key == achar(29)) then | |
| 486 | + ! Ignore up/down arrows in rename mode | |
| 487 | + cycle | |
| 486 | 488 | else if ((key >= 'a' .and. key <= 'z') .or. & |
| 487 | 489 | (key >= 'A' .and. key <= 'Z') .or. & |
| 488 | 490 | (key >= '0' .and. key <= '9') .or. & |
@@ -507,10 +509,10 @@ contains | ||
| 507 | 509 | end if |
| 508 | 510 | |
| 509 | 511 | ! Fuzzy search in normal mode - handle any printable character |
| 510 | - ! Exclude A, B, C, D since those are arrow key codes after escape sequence processing | |
| 512 | + ! Arrow keys are now control codes (28-31), so all letters A-Z work in search | |
| 511 | 513 | if (mode == 'normal') then |
| 512 | 514 | if ((key >= 'a' .and. key <= 'z') .or. & |
| 513 | - ((key >= 'E' .and. key <= 'Z') .or. (key >= '0' .and. key <= '9')) .or. & | |
| 515 | + ((key >= 'A' .and. key <= 'Z') .or. (key >= '0' .and. key <= '9')) .or. & | |
| 514 | 516 | key == '_' .or. key == '-' .or. key == '.') then |
| 515 | 517 | |
| 516 | 518 | ! Check if timeout elapsed since last keypress - if so, start fresh search |
@@ -558,36 +560,42 @@ contains | ||
| 558 | 560 | end if |
| 559 | 561 | |
| 560 | 562 | ! Handle input |
| 561 | - select case (key) | |
| 562 | - case ('j', 'B') ! j or down arrow - navigate to next sibling (skip nested items) | |
| 563 | + ! Note: Arrow keys are now control codes (28-31), checked with if-else before select case | |
| 564 | + if (key == 'j' .or. key == achar(29)) then | |
| 565 | + ! j or down arrow (29) - navigate to next sibling (skip nested items) | |
| 563 | 566 | ! Clear search buffer on navigation |
| 564 | 567 | if (search_length > 0) then |
| 565 | 568 | search_length = 0 |
| 566 | 569 | search_buffer = '' |
| 567 | 570 | end if |
| 568 | 571 | call navigate_down(items, n_items, selected) |
| 569 | - case ('k', 'A') ! k or up arrow - navigate to previous sibling (skip nested items) | |
| 572 | + else if (key == 'k' .or. key == achar(28)) then | |
| 573 | + ! k or up arrow (28) - navigate to previous sibling (skip nested items) | |
| 570 | 574 | ! Clear search buffer on navigation |
| 571 | 575 | if (search_length > 0) then |
| 572 | 576 | search_length = 0 |
| 573 | 577 | search_buffer = '' |
| 574 | 578 | end if |
| 575 | 579 | call navigate_up(items, n_items, selected) |
| 576 | - case ('D') ! Left arrow - navigate to parent directory | |
| 580 | + else if (key == achar(31)) then | |
| 581 | + ! Left arrow (31) - navigate to parent directory | |
| 577 | 582 | ! Clear search buffer on navigation |
| 578 | 583 | if (search_length > 0) then |
| 579 | 584 | search_length = 0 |
| 580 | 585 | search_buffer = '' |
| 581 | 586 | end if |
| 582 | 587 | call navigate_left(items, n_items, selected) |
| 583 | - case ('C') ! Right arrow - enter directory | |
| 588 | + else if (key == achar(30)) then | |
| 589 | + ! Right arrow (30) - enter directory | |
| 584 | 590 | ! Clear search buffer on navigation |
| 585 | 591 | if (search_length > 0) then |
| 586 | 592 | search_length = 0 |
| 587 | 593 | search_buffer = '' |
| 588 | 594 | end if |
| 589 | 595 | call navigate_right(items, n_items, selected, tree_root, hide_dotfiles) |
| 590 | - case (' ') ! Space bar - toggle expand/collapse | |
| 596 | + else | |
| 597 | + select case (key) | |
| 598 | + case (' ') ! Space bar - toggle expand/collapse | |
| 591 | 599 | ! Clear search buffer on navigation |
| 592 | 600 | if (search_length > 0) then |
| 593 | 601 | search_length = 0 |
@@ -848,6 +856,7 @@ contains | ||
| 848 | 856 | ! Unhandled keys - do nothing |
| 849 | 857 | continue |
| 850 | 858 | end select |
| 859 | + end if ! End of arrow key if-else chain | |
| 851 | 860 | end do |
| 852 | 861 | |
| 853 | 862 | ! Restore terminal to normal state |
src/terminal_module.f90modified@@ -132,41 +132,82 @@ contains | ||
| 132 | 132 | |
| 133 | 133 | subroutine read_key(key) |
| 134 | 134 | character(len=1), intent(out) :: key |
| 135 | - character(len=3) :: escape_seq | |
| 136 | - integer :: iostat, tty_unit | |
| 135 | + integer :: status, unit_num, iostat | |
| 136 | + character(len=256) :: cmd | |
| 137 | + | |
| 138 | + ! Use bash with timeout to read a character (avoids blocking on ESC) | |
| 139 | + ! This prevents the ESC key from requiring a second keypress | |
| 140 | + write(cmd, '(A)') 'bash -c "read -t 0.1 -n 1 -s key < /dev/tty && echo -n $key" > ' // FUSS_TEMP // ' 2>/dev/null' | |
| 141 | + call execute_command_line(trim(cmd), exitstat=status) | |
| 142 | + | |
| 143 | + if (status /= 0) then | |
| 144 | + ! Timeout or error - treat as quit | |
| 145 | + key = 'q' | |
| 146 | + return | |
| 147 | + end if | |
| 137 | 148 | |
| 138 | - ! Open /dev/tty for reading | |
| 139 | - open(newunit=tty_unit, file='/dev/tty', status='old', action='read', iostat=iostat) | |
| 149 | + ! Read the character from temp file | |
| 150 | + open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat) | |
| 140 | 151 | if (iostat /= 0) then |
| 141 | - key = 'q' ! If we can't open tty, quit | |
| 152 | + key = 'q' | |
| 142 | 153 | return |
| 143 | 154 | end if |
| 155 | + read(unit_num, '(A1)', iostat=iostat) key | |
| 156 | + close(unit_num, status='delete') | |
| 144 | 157 | |
| 145 | - ! Read one character | |
| 146 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') key | |
| 158 | + if (iostat /= 0) then | |
| 159 | + key = 'q' | |
| 160 | + return | |
| 161 | + end if | |
| 147 | 162 | |
| 148 | 163 | ! Check for escape sequence (arrow keys or alt-key combos) |
| 149 | 164 | if (key == achar(27)) then |
| 150 | - ! Read just the first character after ESC | |
| 151 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') escape_seq(1:1) | |
| 152 | - | |
| 153 | - if (escape_seq(1:1) == '[') then | |
| 154 | - ! Arrow key sequence: ESC[A/B/C/D - need to read one more char | |
| 155 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') escape_seq(2:2) | |
| 156 | - key = escape_seq(2:2) ! Return A, B, C, or D | |
| 157 | - else if (escape_seq(1:1) >= 'a' .and. escape_seq(1:1) <= 'z') then | |
| 158 | - ! Alt-letter sequence: ESC followed by letter | |
| 159 | - ! Encode as ASCII control characters (1-26 for alt-a through alt-z) | |
| 160 | - ! This keeps us in valid ASCII range [0-127] | |
| 161 | - ! e.g., alt-g returns achar(7) = ASCII BEL | |
| 162 | - key = achar(1 + ichar(escape_seq(1:1)) - ichar('a')) | |
| 163 | - else if (iostat /= 0) then | |
| 164 | - ! Just ESC key alone (no following character or timeout) | |
| 165 | + ! Detected ESC - try to read next char quickly with timeout | |
| 166 | + write(cmd, '(A)') 'bash -c "read -t 0.05 -n 1 -s key < /dev/tty && echo -n $key" > ' // & | |
| 167 | + FUSS_TEMP // ' 2>/dev/null' | |
| 168 | + call execute_command_line(trim(cmd), exitstat=status) | |
| 169 | + | |
| 170 | + if (status == 0) then | |
| 171 | + ! Got a following character - check what it is | |
| 172 | + open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat) | |
| 173 | + if (iostat == 0) then | |
| 174 | + read(unit_num, '(A1)', iostat=iostat) key | |
| 175 | + close(unit_num, status='delete') | |
| 176 | + | |
| 177 | + ! Check for arrow keys (ESC [ A/B/C/D) or alt-keys (ESC letter) | |
| 178 | + if (key == '[') then | |
| 179 | + ! Arrow key sequence - read final character | |
| 180 | + write(cmd, '(A)') 'bash -c "read -t 0.05 -n 1 -s key < /dev/tty && echo -n $key" > ' // & | |
| 181 | + FUSS_TEMP // ' 2>/dev/null' | |
| 182 | + call execute_command_line(trim(cmd), exitstat=status) | |
| 183 | + if (status == 0) then | |
| 184 | + open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat) | |
| 185 | + if (iostat == 0) then | |
| 186 | + read(unit_num, '(A1)', iostat=iostat) key | |
| 187 | + close(unit_num, status='delete') | |
| 188 | + ! Encode arrow keys as unique control codes to avoid conflict with uppercase letters | |
| 189 | + ! Up=28, Down=29, Right=30, Left=31 | |
| 190 | + if (key == 'A') then | |
| 191 | + key = achar(28) ! Up arrow | |
| 192 | + else if (key == 'B') then | |
| 193 | + key = achar(29) ! Down arrow | |
| 194 | + else if (key == 'C') then | |
| 195 | + key = achar(30) ! Right arrow | |
| 196 | + else if (key == 'D') then | |
| 197 | + key = achar(31) ! Left arrow | |
| 198 | + end if | |
| 199 | + end if | |
| 200 | + end if | |
| 201 | + else if (key >= 'a' .and. key <= 'z') then | |
| 202 | + ! Alt-letter: encode as control char (1-26 for alt-a through alt-z) | |
| 203 | + key = achar(1 + ichar(key) - ichar('a')) | |
| 204 | + end if | |
| 205 | + end if | |
| 206 | + else | |
| 207 | + ! Just ESC alone (timeout, no following character) | |
| 165 | 208 | key = achar(27) |
| 166 | 209 | end if |
| 167 | 210 | end if |
| 168 | - | |
| 169 | - close(tty_unit) | |
| 170 | 211 | end subroutine read_key |
| 171 | 212 | |
| 172 | 213 | subroutine read_line(prompt, line) |