fortrangoingonforty/facsimile / b0e44e7

Browse files

fix highlighting for ctrl-f

Authored by espadonne
SHA
b0e44e7b59d6e30e767e08172bd9752a65ea2979
Parents
acc28ec
Tree
51649b4

3 changed files

StatusFile+-
M src/ui/unified_search_module.f90 144 25
A src/utils/regex_module.f90 106 0
A src/utils/regex_wrapper.c 77 0
src/ui/unified_search_module.f90modified
@@ -3,6 +3,8 @@ module unified_search_module
33
     use terminal_io_module
44
     use editor_state_module, only: editor_state_t, cursor_t
55
     use text_buffer_module
6
+    use regex_module
7
+    use renderer_module, only: render_screen
68
     implicit none
79
     private
810
 
@@ -23,6 +25,12 @@ module unified_search_module
2325
     integer :: total_matches = 0
2426
     integer :: current_match_index = 0
2527
 
28
+    ! Compiled regex ID (when regex mode is active)
29
+    integer :: compiled_regex_id = -1
30
+
31
+    ! Length of last match (needed for regex where match length != pattern length)
32
+    integer :: last_match_length = 0
33
+
2634
     ! Active search mode - persists after first search
2735
     logical :: search_mode_active = .false.
2836
 
@@ -300,6 +308,20 @@ contains
300308
         logical :: found
301309
         integer :: found_line, found_col
302310
 
311
+        ! Compile regex if in regex mode
312
+        if (use_regex) then
313
+            ! Free old regex if any
314
+            if (compiled_regex_id >= 0) then
315
+                call regex_free(compiled_regex_id)
316
+            end if
317
+            ! Compile new pattern
318
+            compiled_regex_id = regex_compile(pattern, case_sensitive)
319
+            if (compiled_regex_id < 0) then
320
+                ! Regex compilation failed - could show error, for now just skip
321
+                return
322
+            end if
323
+        end if
324
+
303325
         call find_next_match(buffer, pattern, &
304326
                             editor%cursors(editor%active_cursor)%line, &
305327
                             editor%cursors(editor%active_cursor)%column, &
@@ -311,15 +333,22 @@ contains
311333
             editor%cursors(editor%active_cursor)%desired_column = found_col
312334
 
313335
             ! Create selection
336
+            ! For regex, use the match length from last search
337
+            ! For normal search, use pattern length
314338
             editor%cursors(editor%active_cursor)%has_selection = .true.
315339
             editor%cursors(editor%active_cursor)%selection_start_line = found_line
316340
             editor%cursors(editor%active_cursor)%selection_start_col = found_col
317
-            editor%cursors(editor%active_cursor)%column = found_col + len(pattern)
341
+            if (use_regex .and. last_match_length > 0) then
342
+                editor%cursors(editor%active_cursor)%column = found_col + last_match_length
343
+            else
344
+                editor%cursors(editor%active_cursor)%column = found_col + len(pattern)
345
+            end if
318346
 
319347
             last_search_line = found_line
320348
             last_search_col = found_col
321349
 
322350
             call center_viewport_on_cursor(editor)
351
+            call render_screen(buffer, editor)
323352
         end if
324353
     end subroutine perform_search
325354
 
@@ -351,12 +380,19 @@ contains
351380
             editor%cursors(editor%active_cursor)%has_selection = .true.
352381
             editor%cursors(editor%active_cursor)%selection_start_line = found_line
353382
             editor%cursors(editor%active_cursor)%selection_start_col = found_col
354
-            editor%cursors(editor%active_cursor)%column = found_col + len(current_search_pattern)
383
+
384
+            ! Use last_match_length for regex (which was set by find_next_match)
385
+            if (use_regex .and. last_match_length > 0) then
386
+                editor%cursors(editor%active_cursor)%column = found_col + last_match_length
387
+            else
388
+                editor%cursors(editor%active_cursor)%column = found_col + len(current_search_pattern)
389
+            end if
355390
 
356391
             last_search_line = found_line
357392
             last_search_col = found_col
358393
 
359394
             call center_viewport_on_cursor(editor)
395
+            call render_screen(buffer, editor)
360396
         end if
361397
     end subroutine search_forward
362398
 
@@ -370,11 +406,17 @@ contains
370406
         ! If cursor has selection, replace it
371407
         if (editor%cursors(editor%active_cursor)%has_selection) then
372408
             call perform_replacement(buffer, editor%cursors(editor%active_cursor), &
373
-                                    current_search_pattern, current_replace_text)
374
-        end if
409
+                                    current_replace_text, last_match_length)
410
+
411
+            ! Clear selection after replacement
412
+            editor%cursors(editor%active_cursor)%has_selection = .false.
375413
 
376
-        ! Find next match
377
-        call search_forward(editor, buffer)
414
+            ! Re-count matches after replacement
415
+            call count_all_matches(buffer, current_search_pattern)
416
+
417
+            ! Render to show the replacement (without selection)
418
+            call render_screen(buffer, editor)
419
+        end if
378420
     end subroutine replace_current_and_advance
379421
 
380422
     subroutine replace_all_matches(editor, buffer)
@@ -407,10 +449,10 @@ contains
407449
             temp_cursor%has_selection = .true.
408450
             temp_cursor%selection_start_line = found_line
409451
             temp_cursor%selection_start_col = found_col
410
-            temp_cursor%column = found_col + len(current_search_pattern)
452
+            temp_cursor%column = found_col + last_match_length
411453
 
412454
             ! Replace
413
-            call perform_replacement(buffer, temp_cursor, current_search_pattern, current_replace_text)
455
+            call perform_replacement(buffer, temp_cursor, current_replace_text, last_match_length)
414456
 
415457
             replace_count = replace_count + 1
416458
 
@@ -422,10 +464,11 @@ contains
422464
         editor%cursors(editor%active_cursor) = temp_cursor
423465
     end subroutine replace_all_matches
424466
 
425
-    subroutine perform_replacement(buffer, cursor, find_pattern, replace_text)
467
+    subroutine perform_replacement(buffer, cursor, replace_text, match_len)
426468
         type(buffer_t), intent(inout) :: buffer
427469
         type(cursor_t), intent(inout) :: cursor
428
-        character(len=*), intent(in) :: find_pattern, replace_text
470
+        character(len=*), intent(in) :: replace_text
471
+        integer, intent(in) :: match_len
429472
         character(len=:), allocatable :: line, new_line
430473
         integer :: col, i
431474
 
@@ -434,7 +477,7 @@ contains
434477
 
435478
         ! Build new line with replacement
436479
         col = cursor%selection_start_col
437
-        allocate(character(len=len(line) - len(find_pattern) + len(replace_text)) :: new_line)
480
+        allocate(character(len=len(line) - match_len + len(replace_text)) :: new_line)
438481
 
439482
         ! Copy part before match
440483
         if (col > 1) then
@@ -447,8 +490,8 @@ contains
447490
         end if
448491
 
449492
         ! Copy part after match
450
-        if (col + len(find_pattern) <= len(line)) then
451
-            new_line(col+len(replace_text):) = line(col+len(find_pattern):)
493
+        if (col + match_len <= len(line)) then
494
+            new_line(col+len(replace_text):) = line(col+match_len:)
452495
         end if
453496
 
454497
         ! Delete old line content
@@ -545,6 +588,13 @@ contains
545588
         search_mode_active = .false.
546589
         last_search_line = 1
547590
         last_search_col = 1
591
+
592
+        ! Free compiled regex if any
593
+        if (compiled_regex_id >= 0) then
594
+            call regex_free(compiled_regex_id)
595
+            compiled_regex_id = -1
596
+        end if
597
+        last_match_length = 0
548598
     end subroutine clear_search_pattern
549599
 
550600
     ! Search helper functions
@@ -556,11 +606,13 @@ contains
556606
         integer, intent(out) :: found_line, found_col
557607
         character(len=:), allocatable :: line
558608
         integer :: line_count, current_line, pos, search_col
609
+        integer :: match_len
559610
 
560611
         found = .false.
561612
         found_line = 0
562613
         found_col = 0
563614
         line_count = buffer_get_line_count(buffer)
615
+        last_match_length = 0
564616
 
565617
         ! Search from current position to end
566618
         do current_line = start_line, line_count
@@ -571,17 +623,23 @@ contains
571623
                 search_col = 1
572624
             end if
573625
             if (search_col <= len(line)) then
574
-                call find_pattern_in_line(line(search_col:), pattern, pos)
626
+                if (use_regex) then
627
+                    call find_regex_in_line(line(search_col:), compiled_regex_id, pos, match_len)
628
+                else
629
+                    call find_pattern_in_line(line(search_col:), pattern, pos)
630
+                    match_len = len(pattern)
631
+                end if
575632
                 if (pos > 0) then
576633
                     found_col = search_col + pos - 1
577634
                     if (whole_word) then
578
-                        if (.not. is_whole_word_match(line, found_col, len(pattern))) then
635
+                        if (.not. is_whole_word_match(line, found_col, match_len)) then
579636
                             if (allocated(line)) deallocate(line)
580637
                             cycle
581638
                         end if
582639
                     end if
583640
                     found = .true.
584641
                     found_line = current_line
642
+                    last_match_length = match_len
585643
                     if (allocated(line)) deallocate(line)
586644
                     call update_match_index(buffer, pattern, found_line, found_col)
587645
                     return
@@ -595,16 +653,27 @@ contains
595653
             line = buffer_get_line(buffer, current_line)
596654
             if (current_line == start_line) then
597655
                 if (start_col > 1) then
598
-                    call find_pattern_in_line(line(1:start_col-1), pattern, pos)
656
+                    if (use_regex) then
657
+                        call find_regex_in_line(line(1:start_col-1), compiled_regex_id, pos, match_len)
658
+                    else
659
+                        call find_pattern_in_line(line(1:start_col-1), pattern, pos)
660
+                        match_len = len(pattern)
661
+                    end if
599662
                 else
600663
                     pos = 0
664
+                    match_len = 0
601665
                 end if
602666
             else
603
-                call find_pattern_in_line(line, pattern, pos)
667
+                if (use_regex) then
668
+                    call find_regex_in_line(line, compiled_regex_id, pos, match_len)
669
+                else
670
+                    call find_pattern_in_line(line, pattern, pos)
671
+                    match_len = len(pattern)
672
+                end if
604673
             end if
605674
             if (pos > 0) then
606675
                 if (whole_word) then
607
-                    if (.not. is_whole_word_match(line, pos, len(pattern))) then
676
+                    if (.not. is_whole_word_match(line, pos, match_len)) then
608677
                         if (allocated(line)) deallocate(line)
609678
                         cycle
610679
                     end if
@@ -612,6 +681,7 @@ contains
612681
                 found = .true.
613682
                 found_line = current_line
614683
                 found_col = pos
684
+                last_match_length = match_len
615685
                 if (allocated(line)) deallocate(line)
616686
                 call update_match_index(buffer, pattern, found_line, found_col)
617687
                 return
@@ -628,11 +698,13 @@ contains
628698
         integer, intent(out) :: found_line, found_col
629699
         character(len=:), allocatable :: line
630700
         integer :: line_count, current_line, pos, last_pos, check_col
701
+        integer :: match_len, last_match_len
631702
 
632703
         found = .false.
633704
         found_line = 0
634705
         found_col = 0
635706
         line_count = buffer_get_line_count(buffer)
707
+        last_match_length = 0
636708
 
637709
         ! Search backward from current position
638710
         do current_line = start_line, 1, -1
@@ -644,16 +716,24 @@ contains
644716
             end if
645717
 
646718
             last_pos = 0
719
+            last_match_len = 0
647720
             pos = 1
648
-            do while (pos <= check_col - len(pattern) + 1)
649
-                call find_pattern_in_line(line(pos:), pattern, found_col)
721
+            do while (pos <= check_col)
722
+                if (use_regex) then
723
+                    call find_regex_in_line(line(pos:), compiled_regex_id, found_col, match_len)
724
+                else
725
+                    call find_pattern_in_line(line(pos:), pattern, found_col)
726
+                    match_len = len(pattern)
727
+                end if
650728
                 if (found_col > 0 .and. pos + found_col - 1 <= check_col) then
651729
                     if (whole_word) then
652
-                        if (is_whole_word_match(line, pos + found_col - 1, len(pattern))) then
730
+                        if (is_whole_word_match(line, pos + found_col - 1, match_len)) then
653731
                             last_pos = pos + found_col - 1
732
+                            last_match_len = match_len
654733
                         end if
655734
                     else
656735
                         last_pos = pos + found_col - 1
736
+                        last_match_len = match_len
657737
                     end if
658738
                     pos = pos + found_col
659739
                 else
@@ -665,6 +745,7 @@ contains
665745
                 found = .true.
666746
                 found_line = current_line
667747
                 found_col = last_pos
748
+                last_match_length = last_match_len
668749
                 if (allocated(line)) deallocate(line)
669750
                 call update_match_index(buffer, pattern, found_line, found_col)
670751
                 return
@@ -709,6 +790,32 @@ contains
709790
         end if
710791
     end subroutine find_pattern_in_line
711792
 
793
+    ! Find pattern using regex in a line
794
+    ! Returns position where match starts (1-based), or 0 if not found
795
+    ! Also returns match_len (length of what was matched)
796
+    subroutine find_regex_in_line(line, regex_id, pos, match_len)
797
+        character(len=*), intent(in) :: line
798
+        integer, intent(in) :: regex_id
799
+        integer, intent(out) :: pos, match_len
800
+        logical :: found
801
+        integer :: start_pos, len_matched
802
+
803
+        if (regex_id < 0) then
804
+            pos = 0
805
+            match_len = 0
806
+            return
807
+        end if
808
+
809
+        found = regex_match(regex_id, line, start_pos, len_matched)
810
+        if (found) then
811
+            pos = start_pos
812
+            match_len = len_matched
813
+        else
814
+            pos = 0
815
+            match_len = 0
816
+        end if
817
+    end subroutine find_regex_in_line
818
+
712819
     logical function is_whole_word_match(line, start_pos, pattern_len)
713820
         character(len=*), intent(in) :: line
714821
         integer, intent(in) :: start_pos, pattern_len
@@ -745,6 +852,7 @@ contains
745852
         character(len=*), intent(in) :: pattern
746853
         character(len=:), allocatable :: line
747854
         integer :: line_count, current_line, pos, col, match_count
855
+        integer :: match_len
748856
 
749857
         match_count = 0
750858
         line_count = buffer_get_line_count(buffer)
@@ -753,11 +861,16 @@ contains
753861
             line = buffer_get_line(buffer, current_line)
754862
             col = 1
755863
             do while (col <= len(line))
756
-                call find_pattern_in_line(line(col:), pattern, pos)
864
+                if (use_regex) then
865
+                    call find_regex_in_line(line(col:), compiled_regex_id, pos, match_len)
866
+                else
867
+                    call find_pattern_in_line(line(col:), pattern, pos)
868
+                    match_len = len(pattern)
869
+                end if
757870
                 if (pos > 0) then
758871
                     pos = col + pos - 1
759872
                     if (whole_word) then
760
-                        if (is_whole_word_match(line, pos, len(pattern))) then
873
+                        if (is_whole_word_match(line, pos, match_len)) then
761874
                             match_count = match_count + 1
762875
                         end if
763876
                     else
@@ -781,6 +894,7 @@ contains
781894
         integer, intent(in) :: match_line, match_col
782895
         character(len=:), allocatable :: line
783896
         integer :: line_count, current_line, pos, col, match_count
897
+        integer :: match_len
784898
 
785899
         match_count = 0
786900
         line_count = buffer_get_line_count(buffer)
@@ -789,11 +903,16 @@ contains
789903
             line = buffer_get_line(buffer, current_line)
790904
             col = 1
791905
             do while (col <= len(line))
792
-                call find_pattern_in_line(line(col:), pattern, pos)
906
+                if (use_regex) then
907
+                    call find_regex_in_line(line(col:), compiled_regex_id, pos, match_len)
908
+                else
909
+                    call find_pattern_in_line(line(col:), pattern, pos)
910
+                    match_len = len(pattern)
911
+                end if
793912
                 if (pos > 0) then
794913
                     pos = col + pos - 1
795914
                     if (whole_word) then
796
-                        if (is_whole_word_match(line, pos, len(pattern))) then
915
+                        if (is_whole_word_match(line, pos, match_len)) then
797916
                             match_count = match_count + 1
798917
                             if (current_line == match_line .and. pos == match_col) then
799918
                                 current_match_index = match_count
src/utils/regex_module.f90added
@@ -0,0 +1,106 @@
1
+module regex_module
2
+    use iso_c_binding
3
+    implicit none
4
+    private
5
+
6
+    public :: regex_compile, regex_match, regex_free, regex_free_all
7
+    public :: REG_EXTENDED, REG_ICASE, REG_NOSUB
8
+
9
+    ! POSIX regex flags (from regex.h)
10
+    integer(c_int), parameter :: REG_EXTENDED = 1  ! Use Extended Regular Expressions
11
+    integer(c_int), parameter :: REG_ICASE = 2     ! Ignore case
12
+    integer(c_int), parameter :: REG_NOSUB = 4     ! Don't store match positions
13
+
14
+    ! C function interfaces
15
+    interface
16
+        function compile_regex_c(pattern, cflags) bind(C, name="compile_regex")
17
+            use iso_c_binding
18
+            character(kind=c_char), dimension(*), intent(in) :: pattern
19
+            integer(c_int), value :: cflags
20
+            integer(c_int) :: compile_regex_c
21
+        end function compile_regex_c
22
+
23
+        function match_regex_c(id, text, match_start, match_len) bind(C, name="match_regex")
24
+            use iso_c_binding
25
+            integer(c_int), value :: id
26
+            character(kind=c_char), dimension(*), intent(in) :: text
27
+            integer(c_int), intent(out) :: match_start
28
+            integer(c_int), intent(out) :: match_len
29
+            integer(c_int) :: match_regex_c
30
+        end function match_regex_c
31
+
32
+        subroutine free_regex_c(id) bind(C, name="free_regex")
33
+            use iso_c_binding
34
+            integer(c_int), value :: id
35
+        end subroutine free_regex_c
36
+
37
+        subroutine free_all_regex_c() bind(C, name="free_all_regex")
38
+            use iso_c_binding
39
+        end subroutine free_all_regex_c
40
+    end interface
41
+
42
+contains
43
+
44
+    ! Compile a regex pattern
45
+    ! Returns regex ID (>= 0) on success, -1 on error
46
+    function regex_compile(pattern, case_sensitive) result(regex_id)
47
+        character(len=*), intent(in) :: pattern
48
+        logical, intent(in) :: case_sensitive
49
+        integer :: regex_id
50
+        integer(c_int) :: cflags
51
+        character(len=len_trim(pattern)+1, kind=c_char) :: c_pattern
52
+
53
+        ! Set flags
54
+        cflags = REG_EXTENDED
55
+        if (.not. case_sensitive) then
56
+            cflags = cflags + REG_ICASE
57
+        end if
58
+
59
+        ! Convert Fortran string to C string (null-terminated)
60
+        c_pattern = trim(pattern) // c_null_char
61
+
62
+        ! Call C function
63
+        regex_id = compile_regex_c(c_pattern, cflags)
64
+    end function regex_compile
65
+
66
+    ! Match a regex against text
67
+    ! Returns .true. if match found, .false. otherwise
68
+    ! match_start and match_len are 1-based indices (Fortran style)
69
+    function regex_match(regex_id, text, match_start, match_len) result(found)
70
+        integer, intent(in) :: regex_id
71
+        character(len=*), intent(in) :: text
72
+        integer, intent(out) :: match_start, match_len
73
+        logical :: found
74
+        integer(c_int) :: result, c_start, c_len
75
+        character(len=len(text)+1, kind=c_char) :: c_text
76
+
77
+        ! Convert Fortran string to C string
78
+        c_text = text // c_null_char
79
+
80
+        ! Call C function
81
+        result = match_regex_c(regex_id, c_text, c_start, c_len)
82
+
83
+        if (result == 1) then
84
+            ! Match found - convert C indices (0-based) to Fortran (1-based)
85
+            match_start = c_start + 1
86
+            match_len = c_len
87
+            found = .true.
88
+        else
89
+            match_start = 0
90
+            match_len = 0
91
+            found = .false.
92
+        end if
93
+    end function regex_match
94
+
95
+    ! Free a compiled regex
96
+    subroutine regex_free(regex_id)
97
+        integer, intent(in) :: regex_id
98
+        call free_regex_c(regex_id)
99
+    end subroutine regex_free
100
+
101
+    ! Free all compiled regexes
102
+    subroutine regex_free_all()
103
+        call free_all_regex_c()
104
+    end subroutine regex_free_all
105
+
106
+end module regex_module
src/utils/regex_wrapper.cadded
@@ -0,0 +1,77 @@
1
+#include <regex.h>
2
+#include <stdlib.h>
3
+#include <string.h>
4
+
5
+// Maximum number of compiled regex patterns we can store
6
+#define MAX_REGEX 10
7
+
8
+// Storage for compiled regex patterns
9
+static regex_t regex_storage[MAX_REGEX];
10
+static int regex_used[MAX_REGEX] = {0};
11
+
12
+// Compile a regex pattern and return an ID (index into storage)
13
+// Returns -1 on error, >= 0 on success
14
+int compile_regex(const char *pattern, int cflags) {
15
+    int id = -1;
16
+
17
+    // Find free slot
18
+    for (int i = 0; i < MAX_REGEX; i++) {
19
+        if (!regex_used[i]) {
20
+            id = i;
21
+            break;
22
+        }
23
+    }
24
+
25
+    if (id == -1) {
26
+        return -1;  // No free slots
27
+    }
28
+
29
+    // Compile the pattern
30
+    int result = regcomp(&regex_storage[id], pattern, cflags);
31
+    if (result != 0) {
32
+        return -1;  // Compilation failed
33
+    }
34
+
35
+    regex_used[id] = 1;
36
+    return id;
37
+}
38
+
39
+// Match a compiled regex against text
40
+// Returns 1 if match found, 0 if no match, -1 on error
41
+int match_regex(int id, const char *text, int *match_start, int *match_len) {
42
+    if (id < 0 || id >= MAX_REGEX || !regex_used[id]) {
43
+        return -1;  // Invalid ID
44
+    }
45
+
46
+    regmatch_t pmatch[1];
47
+    int result = regexec(&regex_storage[id], text, 1, pmatch, 0);
48
+
49
+    if (result == 0) {
50
+        // Match found - return start position and length
51
+        *match_start = (int)pmatch[0].rm_so;
52
+        *match_len = (int)(pmatch[0].rm_eo - pmatch[0].rm_so);
53
+        return 1;
54
+    } else if (result == REG_NOMATCH) {
55
+        return 0;  // No match
56
+    } else {
57
+        return -1;  // Error
58
+    }
59
+}
60
+
61
+// Free a compiled regex
62
+void free_regex(int id) {
63
+    if (id >= 0 && id < MAX_REGEX && regex_used[id]) {
64
+        regfree(&regex_storage[id]);
65
+        regex_used[id] = 0;
66
+    }
67
+}
68
+
69
+// Free all compiled regexes
70
+void free_all_regex(void) {
71
+    for (int i = 0; i < MAX_REGEX; i++) {
72
+        if (regex_used[i]) {
73
+            regfree(&regex_storage[i]);
74
+            regex_used[i] = 0;
75
+        }
76
+    }
77
+}