@@ -182,7 +182,7 @@ contains |
| 182 | 182 | ! Search state for fuzzy jump |
| 183 | 183 | character(len=32) :: search_buffer |
| 184 | 184 | integer :: search_length |
| 185 | | - real(8) :: last_search_time, current_time |
| 185 | + integer(8) :: last_search_tick, current_tick, clock_rate |
| 186 | 186 | type(tree_node), pointer :: tree_root |
| 187 | 187 | |
| 188 | 188 | ! Initialize tree pointer |
@@ -253,7 +253,8 @@ contains |
| 253 | 253 | ! Initialize search state |
| 254 | 254 | search_buffer = '' |
| 255 | 255 | search_length = 0 |
| 256 | | - last_search_time = 0.0d0 |
| 256 | + last_search_tick = 0 |
| 257 | + call system_clock(count_rate=clock_rate) |
| 257 | 258 | |
| 258 | 259 | ! Partial redraw optimization: initialize tracking state |
| 259 | 260 | prev_selected = 0 ! Force initial draw |
@@ -300,15 +301,16 @@ contains |
| 300 | 301 | |
| 301 | 302 | ! Check search timeout (1 second) |
| 302 | 303 | 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 |
| 305 | 307 | search_length = 0 |
| 306 | 308 | search_buffer = '' |
| 307 | 309 | needs_full_redraw = .true. |
| 308 | 310 | end if |
| 309 | 311 | end if |
| 310 | 312 | |
| 311 | | - ! Read key |
| 313 | + ! Always use fast blocking read - timeouts are too slow |
| 312 | 314 | call read_key(key) |
| 313 | 315 | |
| 314 | 316 | ! Check for alt-g to toggle git mode |
@@ -355,17 +357,83 @@ contains |
| 355 | 357 | cycle |
| 356 | 358 | end if |
| 357 | 359 | |
| 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 | + |
| 358 | 401 | ! Handle input |
| 359 | 402 | select case (key) |
| 360 | 403 | 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 |
| 361 | 409 | call navigate_down(items, n_items, selected) |
| 362 | 410 | 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 |
| 363 | 416 | call navigate_up(items, n_items, selected) |
| 364 | 417 | 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 |
| 365 | 423 | call navigate_left(items, n_items, selected) |
| 366 | 424 | 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 |
| 367 | 430 | call navigate_right(items, n_items, selected, tree_root, hide_dotfiles) |
| 368 | 431 | 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 |
| 369 | 437 | if (.not. items(selected)%is_file .and. associated(items(selected)%node)) then |
| 370 | 438 | ! Toggle the expanded state |
| 371 | 439 | items(selected)%node%is_expanded = .not. items(selected)%node%is_expanded |
@@ -618,39 +686,9 @@ contains |
| 618 | 686 | ! In normal mode: q quits the application |
| 619 | 687 | running = .false. |
| 620 | 688 | 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 |
| 654 | 692 | end select |
| 655 | 693 | end do |
| 656 | 694 | |
@@ -1474,22 +1512,27 @@ contains |
| 1474 | 1512 | |
| 1475 | 1513 | subroutine fuzzy_jump_to_match(items, n_items, pattern, selected) |
| 1476 | 1514 | ! Jump to first item that fuzzy matches the pattern |
| 1515 | + ! Searches from NEXT position (skips current item to allow cycling) |
| 1477 | 1516 | type(selectable_item), intent(in) :: items(:) |
| 1478 | 1517 | integer, intent(in) :: n_items |
| 1479 | 1518 | character(len=*), intent(in) :: pattern |
| 1480 | 1519 | integer, intent(inout) :: selected |
| 1481 | | - integer :: i |
| 1520 | + integer :: i, start_pos |
| 1482 | 1521 | |
| 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 |
| 1485 | 1528 | if (fuzzy_match(pattern, items(i)%path)) then |
| 1486 | 1529 | selected = i |
| 1487 | 1530 | return |
| 1488 | 1531 | end if |
| 1489 | 1532 | end do |
| 1490 | 1533 | |
| 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 |
| 1493 | 1536 | if (fuzzy_match(pattern, items(i)%path)) then |
| 1494 | 1537 | selected = i |
| 1495 | 1538 | return |
@@ -1543,19 +1586,4 @@ contains |
| 1543 | 1586 | matches = (pattern_idx > len_trim(pattern)) |
| 1544 | 1587 | end function fuzzy_match |
| 1545 | 1588 | |
| 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 | | - |
| 1561 | 1589 | end program fuss |