fortrangoingonforty/fuss / dada48b

Browse files

close to perfect fuzzy jump

Authored by espadonne
SHA
dada48be1acc8f551dc18c064e8c704c4359f32b
Parents
1e3f0b6
Tree
9cf2554

2 changed files

StatusFile+-
M src/fuss_main.f90 86 58
M src/terminal_module.f90 78 0
src/fuss_main.f90modified
@@ -182,7 +182,7 @@ contains
182182
         ! Search state for fuzzy jump
183183
         character(len=32) :: search_buffer
184184
         integer :: search_length
185
-        real(8) :: last_search_time, current_time
185
+        integer(8) :: last_search_tick, current_tick, clock_rate
186186
         type(tree_node), pointer :: tree_root
187187
 
188188
         ! Initialize tree pointer
@@ -253,7 +253,8 @@ contains
253253
         ! Initialize search state
254254
         search_buffer = ''
255255
         search_length = 0
256
-        last_search_time = 0.0d0
256
+        last_search_tick = 0
257
+        call system_clock(count_rate=clock_rate)
257258
 
258259
         ! Partial redraw optimization: initialize tracking state
259260
         prev_selected = 0  ! Force initial draw
@@ -300,15 +301,16 @@ contains
300301
 
301302
             ! Check search timeout (1 second)
302303
             if (search_length > 0) then
303
-                current_time = get_wall_time()
304
-                if (current_time - last_search_time > 1.0d0) then
304
+                call system_clock(current_tick)
305
+                ! Check if 1 second has elapsed (clock_rate ticks per second)
306
+                if (current_tick - last_search_tick > clock_rate) then
305307
                     search_length = 0
306308
                     search_buffer = ''
307309
                     needs_full_redraw = .true.
308310
                 end if
309311
             end if
310312
 
311
-            ! Read key
313
+            ! Always use fast blocking read - timeouts are too slow
312314
             call read_key(key)
313315
 
314316
             ! Check for alt-g to toggle git mode
@@ -355,17 +357,83 @@ contains
355357
                 cycle
356358
             end if
357359
 
360
+            ! Fuzzy search in normal mode - handle any printable character
361
+            ! Exclude A, B, C, D since those are arrow key codes after escape sequence processing
362
+            if (mode == 'normal') then
363
+                if ((key >= 'a' .and. key <= 'z') .or. &
364
+                    ((key >= 'E' .and. key <= 'Z') .or. (key >= '0' .and. key <= '9')) .or. &
365
+                    key == '_' .or. key == '-' .or. key == '.') then
366
+                    ! Add to search buffer
367
+                    if (search_length < 32) then
368
+                        search_length = search_length + 1
369
+                        search_buffer(search_length:search_length) = key
370
+                        call system_clock(last_search_tick)
371
+
372
+                        ! DEBUG
373
+                        open(99, file='/tmp/fuss_debug.log', position='append')
374
+                        write(99, '(A,A,A,I0)') 'Buffer: "', search_buffer(1:search_length), '" -> jumping to match'
375
+                        close(99)
376
+
377
+                        call fuzzy_jump_to_match(items, n_items, search_buffer(1:search_length), selected)
378
+
379
+                        ! DEBUG
380
+                        open(99, file='/tmp/fuss_debug.log', position='append')
381
+                        write(99, '(A,I0,A,A)') '  Result: selected=', selected, ' path=', trim(items(selected)%path)
382
+                        close(99)
383
+
384
+                        needs_full_redraw = .true.
385
+                    end if
386
+                    cycle  ! Skip case statement
387
+                else if (key == achar(127) .or. key == achar(8)) then
388
+                    ! Backspace - remove last character
389
+                    if (search_length > 0) then
390
+                        search_length = search_length - 1
391
+                        call system_clock(last_search_tick)
392
+                        if (search_length > 0) then
393
+                            call fuzzy_jump_to_match(items, n_items, search_buffer(1:search_length), selected)
394
+                        end if
395
+                        needs_full_redraw = .true.
396
+                    end if
397
+                    cycle  ! Skip case statement
398
+                end if
399
+            end if
400
+
358401
             ! Handle input
359402
             select case (key)
360403
             case ('j', 'B')  ! j or down arrow - navigate to next sibling (skip nested items)
404
+                ! Clear search buffer on navigation
405
+                if (search_length > 0) then
406
+                    search_length = 0
407
+                    search_buffer = ''
408
+                end if
361409
                 call navigate_down(items, n_items, selected)
362410
             case ('k', 'A')  ! k or up arrow - navigate to previous sibling (skip nested items)
411
+                ! Clear search buffer on navigation
412
+                if (search_length > 0) then
413
+                    search_length = 0
414
+                    search_buffer = ''
415
+                end if
363416
                 call navigate_up(items, n_items, selected)
364417
             case ('D')  ! Left arrow - navigate to parent directory
418
+                ! Clear search buffer on navigation
419
+                if (search_length > 0) then
420
+                    search_length = 0
421
+                    search_buffer = ''
422
+                end if
365423
                 call navigate_left(items, n_items, selected)
366424
             case ('C')  ! Right arrow - enter directory
425
+                ! Clear search buffer on navigation
426
+                if (search_length > 0) then
427
+                    search_length = 0
428
+                    search_buffer = ''
429
+                end if
367430
                 call navigate_right(items, n_items, selected, tree_root, hide_dotfiles)
368431
             case (' ')  ! Space bar - toggle expand/collapse
432
+                ! Clear search buffer on navigation
433
+                if (search_length > 0) then
434
+                    search_length = 0
435
+                    search_buffer = ''
436
+                end if
369437
                 if (.not. items(selected)%is_file .and. associated(items(selected)%node)) then
370438
                     ! Toggle the expanded state
371439
                     items(selected)%node%is_expanded = .not. items(selected)%node%is_expanded
@@ -618,39 +686,9 @@ contains
618686
                     ! In normal mode: q quits the application
619687
                     running = .false.
620688
                 end if
621
-            case default  ! Handle fuzzy search in normal mode
622
-                if (mode == 'normal') then
623
-                    ! Check if it's a printable letter/number for search
624
-                    if ((key >= 'a' .and. key <= 'z') .or. (key >= 'A' .and. key <= 'Z') .or. &
625
-                        (key >= '0' .and. key <= '9') .or. key == '_' .or. key == '-' .or. key == '.') then
626
-                        ! Add to search buffer
627
-                        if (search_length < 32) then
628
-                            search_length = search_length + 1
629
-                            search_buffer(search_length:search_length) = key
630
-                            last_search_time = get_wall_time()
631
-
632
-                            ! Find first matching item and jump immediately
633
-                            call fuzzy_jump_to_match(items, n_items, search_buffer(1:search_length), selected)
634
-
635
-                            ! Redraw will happen at top of loop
636
-                            needs_full_redraw = .true.
637
-                        end if
638
-                    else if (key == achar(127) .or. key == achar(8)) then
639
-                        ! Backspace - remove last character
640
-                        if (search_length > 0) then
641
-                            search_length = search_length - 1
642
-                            last_search_time = get_wall_time()
643
-
644
-                            ! Re-search with shorter pattern
645
-                            if (search_length > 0) then
646
-                                call fuzzy_jump_to_match(items, n_items, search_buffer(1:search_length), selected)
647
-                            end if
648
-
649
-                            ! Redraw will happen at top of loop
650
-                            needs_full_redraw = .true.
651
-                        end if
652
-                    end if
653
-                end if
689
+            case default
690
+                ! Unhandled keys - do nothing
691
+                continue
654692
             end select
655693
         end do
656694
 
@@ -1474,22 +1512,27 @@ contains
14741512
 
14751513
     subroutine fuzzy_jump_to_match(items, n_items, pattern, selected)
14761514
         ! Jump to first item that fuzzy matches the pattern
1515
+        ! Searches from NEXT position (skips current item to allow cycling)
14771516
         type(selectable_item), intent(in) :: items(:)
14781517
         integer, intent(in) :: n_items
14791518
         character(len=*), intent(in) :: pattern
14801519
         integer, intent(inout) :: selected
1481
-        integer :: i
1520
+        integer :: i, start_pos
14821521
 
1483
-        ! Search from current position forward
1484
-        do i = selected, n_items
1522
+        ! Start from next item (so repeated searches cycle through matches)
1523
+        start_pos = selected + 1
1524
+        if (start_pos > n_items) start_pos = 1
1525
+
1526
+        ! Search from next position forward
1527
+        do i = start_pos, n_items
14851528
             if (fuzzy_match(pattern, items(i)%path)) then
14861529
                 selected = i
14871530
                 return
14881531
             end if
14891532
         end do
14901533
 
1491
-        ! Wrap around: search from beginning to current position
1492
-        do i = 1, selected - 1
1534
+        ! Wrap around: search from beginning to current position (inclusive)
1535
+        do i = 1, selected
14931536
             if (fuzzy_match(pattern, items(i)%path)) then
14941537
                 selected = i
14951538
                 return
@@ -1543,19 +1586,4 @@ contains
15431586
         matches = (pattern_idx > len_trim(pattern))
15441587
     end function fuzzy_match
15451588
 
1546
-    function get_wall_time() result(time_seconds)
1547
-        ! Get wall-clock time in seconds (for timeouts)
1548
-        ! Uses system date_and_time which provides millisecond precision
1549
-        real(8) :: time_seconds
1550
-        integer :: values(8)
1551
-
1552
-        call date_and_time(values=values)
1553
-
1554
-        ! Convert to seconds: hours*3600 + minutes*60 + seconds + milliseconds/1000
1555
-        time_seconds = real(values(5), 8) * 3600.0d0 + &
1556
-                      real(values(6), 8) * 60.0d0 + &
1557
-                      real(values(7), 8) + &
1558
-                      real(values(8), 8) / 1000.0d0
1559
-    end function get_wall_time
1560
-
15611589
 end program fuss
src/terminal_module.f90modified
@@ -52,6 +52,84 @@ contains
5252
         call read_key(key)
5353
     end subroutine wait_for_key
5454
 
55
+    subroutine read_key_with_timeout(key, timeout_ms, timed_out)
56
+        ! Read a key with timeout (in milliseconds)
57
+        ! If timeout occurs, timed_out is set to .true. and key is set to null
58
+        character(len=1), intent(out) :: key
59
+        integer, intent(in) :: timeout_ms
60
+        logical, intent(out) :: timed_out
61
+        integer :: status
62
+        character(len=256) :: cmd
63
+        integer :: unit_num, iostat
64
+
65
+        timed_out = .false.
66
+        key = achar(0)
67
+
68
+        ! Use bash read with timeout to read a single character
69
+        ! Timeout is in fractional seconds
70
+        write(cmd, '(A,F6.3,A)') 'bash -c "read -t ', real(timeout_ms)/1000.0, &
71
+            ' -n 1 -s key < /dev/tty && echo -n $key" > ' // FUSS_TEMP // ' 2>/dev/null'
72
+
73
+        call execute_command_line(trim(cmd), exitstat=status)
74
+
75
+        if (status /= 0) then
76
+            ! Timeout occurred (read returned non-zero)
77
+            timed_out = .true.
78
+            return
79
+        end if
80
+
81
+        ! Read the character from temp file
82
+        open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
83
+        if (iostat == 0) then
84
+            read(unit_num, '(A1)', iostat=iostat) key
85
+            close(unit_num, status='delete')
86
+            if (iostat /= 0) then
87
+                ! Empty file means timeout
88
+                timed_out = .true.
89
+                key = achar(0)
90
+            end if
91
+        else
92
+            timed_out = .true.
93
+        end if
94
+
95
+        ! Handle arrow keys and escape sequences
96
+        if (.not. timed_out .and. key == achar(27)) then
97
+            ! Detected ESC - try to read next char quickly
98
+            write(cmd, '(A)') 'bash -c "read -t 0.05 -n 1 -s key < /dev/tty && echo -n $key" > ' // &
99
+                              FUSS_TEMP // ' 2>/dev/null'
100
+            call execute_command_line(trim(cmd), exitstat=status)
101
+
102
+            if (status == 0) then
103
+                open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
104
+                if (iostat == 0) then
105
+                    read(unit_num, '(A1)', iostat=iostat) key
106
+                    close(unit_num, status='delete')
107
+
108
+                    ! Check for arrow keys (ESC [ A/B/C/D)
109
+                    if (key == '[') then
110
+                        ! Read final character
111
+                        write(cmd, '(A)') 'bash -c "read -t 0.05 -n 1 -s key < /dev/tty && echo -n $key" > ' // &
112
+                                          FUSS_TEMP // ' 2>/dev/null'
113
+                        call execute_command_line(trim(cmd), exitstat=status)
114
+                        if (status == 0) then
115
+                            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
116
+                            if (iostat == 0) then
117
+                                read(unit_num, '(A1)', iostat=iostat) key
118
+                                close(unit_num, status='delete')
119
+                            end if
120
+                        end if
121
+                    else if (key >= 'a' .and. key <= 'z') then
122
+                        ! Alt-letter: encode as control char
123
+                        key = achar(1 + ichar(key) - ichar('a'))
124
+                    end if
125
+                end if
126
+            else
127
+                ! Just ESC alone
128
+                key = achar(27)
129
+            end if
130
+        end if
131
+    end subroutine read_key_with_timeout
132
+
55133
     subroutine read_key(key)
56134
         character(len=1), intent(out) :: key
57135
         character(len=3) :: escape_seq