fortrangoingonforty/fuss / f7bf5a2

Browse files

shore up keyboard problems on rename mode

Authored by espadonne
SHA
f7bf5a269ccd4c73301309c53a4b9664c12f8599
Parents
73f447a
Tree
e58fa89

3 changed files

StatusFile+-
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)