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 | print '(A)', '' | 180 | print '(A)', '' |
| 181 | if (in_rename_mode) then | 181 | if (in_rename_mode) then |
| 182 | ! Rename mode help - show in cyan | 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 | else if (mode == 'git') then | 184 | else if (mode == 'git') then |
| 185 | ! Git mode help - show in yellow tint | 185 | ! Git mode help - show in yellow tint |
| 186 | print '(A)', achar(27) // '[33mLegend: ' // achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // & | 186 | print '(A)', achar(27) // '[33mLegend: ' // achar(27) // '[32m↑' // achar(27) // '[0m=staged ' // & |
src/fuss_main.f90modified@@ -465,24 +465,26 @@ contains | |||
| 465 | needs_full_redraw = .true. | 465 | needs_full_redraw = .true. |
| 466 | end if | 466 | end if |
| 467 | cycle | 467 | cycle |
| 468 | - ! NOTE: Arrow keys (after escape processing) are indistinguishable from uppercase A/B/C/D | 468 | + ! Arrow keys are now encoded as control codes: Up=28, Down=29, Right=30, Left=31 |
| 469 | - ! Trade-off: Prioritize arrow functionality over uppercase C/D letters | 469 | + ! This allows uppercase C and D to work in rename mode |
| 470 | - ! Uppercase A/B work fine, C/D reserved for arrows | 470 | + else if (key == achar(30)) then |
| 471 | - else if (key == 'C') then | 471 | + ! Right arrow - move cursor right |
| 472 | - ! Right arrow - move cursor right (also blocks uppercase C) | ||
| 473 | if (rename_cursor_pos < len_trim(rename_buffer)) then | 472 | if (rename_cursor_pos < len_trim(rename_buffer)) then |
| 474 | rename_cursor_pos = rename_cursor_pos + 1 | 473 | rename_cursor_pos = rename_cursor_pos + 1 |
| 475 | needs_full_redraw = .true. | 474 | needs_full_redraw = .true. |
| 476 | end if | 475 | end if |
| 477 | cycle | 476 | cycle |
| 478 | - else if (key == 'D') then | 477 | + else if (key == achar(31)) then |
| 479 | - ! Left arrow - move cursor left (also blocks uppercase D) | 478 | + ! Left arrow - move cursor left |
| 480 | if (rename_cursor_pos > 0) then | 479 | if (rename_cursor_pos > 0) then |
| 481 | rename_cursor_pos = rename_cursor_pos - 1 | 480 | rename_cursor_pos = rename_cursor_pos - 1 |
| 482 | needs_full_redraw = .true. | 481 | needs_full_redraw = .true. |
| 483 | end if | 482 | end if |
| 484 | cycle | 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 | else if ((key >= 'a' .and. key <= 'z') .or. & | 488 | else if ((key >= 'a' .and. key <= 'z') .or. & |
| 487 | (key >= 'A' .and. key <= 'Z') .or. & | 489 | (key >= 'A' .and. key <= 'Z') .or. & |
| 488 | (key >= '0' .and. key <= '9') .or. & | 490 | (key >= '0' .and. key <= '9') .or. & |
@@ -507,10 +509,10 @@ contains | |||
| 507 | end if | 509 | end if |
| 508 | 510 | ||
| 509 | ! Fuzzy search in normal mode - handle any printable character | 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 | if (mode == 'normal') then | 513 | if (mode == 'normal') then |
| 512 | if ((key >= 'a' .and. key <= 'z') .or. & | 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 | key == '_' .or. key == '-' .or. key == '.') then | 516 | key == '_' .or. key == '-' .or. key == '.') then |
| 515 | 517 | ||
| 516 | ! Check if timeout elapsed since last keypress - if so, start fresh search | 518 | ! Check if timeout elapsed since last keypress - if so, start fresh search |
@@ -558,36 +560,42 @@ contains | |||
| 558 | end if | 560 | end if |
| 559 | 561 | ||
| 560 | ! Handle input | 562 | ! Handle input |
| 561 | - select case (key) | 563 | + ! Note: Arrow keys are now control codes (28-31), checked with if-else before select case |
| 562 | - case ('j', 'B') ! j or down arrow - navigate to next sibling (skip nested items) | 564 | + if (key == 'j' .or. key == achar(29)) then |
| 565 | + ! j or down arrow (29) - navigate to next sibling (skip nested items) | ||
| 563 | ! Clear search buffer on navigation | 566 | ! Clear search buffer on navigation |
| 564 | if (search_length > 0) then | 567 | if (search_length > 0) then |
| 565 | search_length = 0 | 568 | search_length = 0 |
| 566 | search_buffer = '' | 569 | search_buffer = '' |
| 567 | end if | 570 | end if |
| 568 | call navigate_down(items, n_items, selected) | 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 | ! Clear search buffer on navigation | 574 | ! Clear search buffer on navigation |
| 571 | if (search_length > 0) then | 575 | if (search_length > 0) then |
| 572 | search_length = 0 | 576 | search_length = 0 |
| 573 | search_buffer = '' | 577 | search_buffer = '' |
| 574 | end if | 578 | end if |
| 575 | call navigate_up(items, n_items, selected) | 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 | ! Clear search buffer on navigation | 582 | ! Clear search buffer on navigation |
| 578 | if (search_length > 0) then | 583 | if (search_length > 0) then |
| 579 | search_length = 0 | 584 | search_length = 0 |
| 580 | search_buffer = '' | 585 | search_buffer = '' |
| 581 | end if | 586 | end if |
| 582 | call navigate_left(items, n_items, selected) | 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 | ! Clear search buffer on navigation | 590 | ! Clear search buffer on navigation |
| 585 | if (search_length > 0) then | 591 | if (search_length > 0) then |
| 586 | search_length = 0 | 592 | search_length = 0 |
| 587 | search_buffer = '' | 593 | search_buffer = '' |
| 588 | end if | 594 | end if |
| 589 | call navigate_right(items, n_items, selected, tree_root, hide_dotfiles) | 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 | ! Clear search buffer on navigation | 599 | ! Clear search buffer on navigation |
| 592 | if (search_length > 0) then | 600 | if (search_length > 0) then |
| 593 | search_length = 0 | 601 | search_length = 0 |
@@ -848,6 +856,7 @@ contains | |||
| 848 | ! Unhandled keys - do nothing | 856 | ! Unhandled keys - do nothing |
| 849 | continue | 857 | continue |
| 850 | end select | 858 | end select |
| 859 | + end if ! End of arrow key if-else chain | ||
| 851 | end do | 860 | end do |
| 852 | 861 | ||
| 853 | ! Restore terminal to normal state | 862 | ! Restore terminal to normal state |
src/terminal_module.f90modified@@ -132,41 +132,82 @@ contains | |||
| 132 | 132 | ||
| 133 | subroutine read_key(key) | 133 | subroutine read_key(key) |
| 134 | character(len=1), intent(out) :: key | 134 | character(len=1), intent(out) :: key |
| 135 | - character(len=3) :: escape_seq | 135 | + integer :: status, unit_num, iostat |
| 136 | - integer :: iostat, tty_unit | 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 | 149 | + ! Read the character from temp file |
| 139 | - open(newunit=tty_unit, file='/dev/tty', status='old', action='read', iostat=iostat) | 150 | + open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat) |
| 140 | if (iostat /= 0) then | 151 | if (iostat /= 0) then |
| 141 | - key = 'q' ! If we can't open tty, quit | 152 | + key = 'q' |
| 142 | return | 153 | return |
| 143 | end if | 154 | end if |
| 155 | + read(unit_num, '(A1)', iostat=iostat) key | ||
| 156 | + close(unit_num, status='delete') | ||
| 144 | 157 | ||
| 145 | - ! Read one character | 158 | + if (iostat /= 0) then |
| 146 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') key | 159 | + key = 'q' |
| 160 | + return | ||
| 161 | + end if | ||
| 147 | 162 | ||
| 148 | ! Check for escape sequence (arrow keys or alt-key combos) | 163 | ! Check for escape sequence (arrow keys or alt-key combos) |
| 149 | if (key == achar(27)) then | 164 | if (key == achar(27)) then |
| 150 | - ! Read just the first character after ESC | 165 | + ! Detected ESC - try to read next char quickly with timeout |
| 151 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') escape_seq(1:1) | 166 | + write(cmd, '(A)') 'bash -c "read -t 0.05 -n 1 -s key < /dev/tty && echo -n $key" > ' // & |
| 152 | - | 167 | + FUSS_TEMP // ' 2>/dev/null' |
| 153 | - if (escape_seq(1:1) == '[') then | 168 | + call execute_command_line(trim(cmd), exitstat=status) |
| 154 | - ! Arrow key sequence: ESC[A/B/C/D - need to read one more char | 169 | + |
| 155 | - read(tty_unit, '(A1)', iostat=iostat, advance='no') escape_seq(2:2) | 170 | + if (status == 0) then |
| 156 | - key = escape_seq(2:2) ! Return A, B, C, or D | 171 | + ! Got a following character - check what it is |
| 157 | - else if (escape_seq(1:1) >= 'a' .and. escape_seq(1:1) <= 'z') then | 172 | + open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat) |
| 158 | - ! Alt-letter sequence: ESC followed by letter | 173 | + if (iostat == 0) then |
| 159 | - ! Encode as ASCII control characters (1-26 for alt-a through alt-z) | 174 | + read(unit_num, '(A1)', iostat=iostat) key |
| 160 | - ! This keeps us in valid ASCII range [0-127] | 175 | + close(unit_num, status='delete') |
| 161 | - ! e.g., alt-g returns achar(7) = ASCII BEL | 176 | + |
| 162 | - key = achar(1 + ichar(escape_seq(1:1)) - ichar('a')) | 177 | + ! Check for arrow keys (ESC [ A/B/C/D) or alt-keys (ESC letter) |
| 163 | - else if (iostat /= 0) then | 178 | + if (key == '[') then |
| 164 | - ! Just ESC key alone (no following character or timeout) | 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 | key = achar(27) | 208 | key = achar(27) |
| 166 | end if | 209 | end if |
| 167 | end if | 210 | end if |
| 168 | - | ||
| 169 | - close(tty_unit) | ||
| 170 | end subroutine read_key | 211 | end subroutine read_key |
| 171 | 212 | ||
| 172 | subroutine read_line(prompt, line) | 213 | subroutine read_line(prompt, line) |