dte test coverage


Directory: ./
File: src/search.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 93 105 88.6%
Functions: 7 8 87.5%
Branches: 42 64 65.6%

Line Branch Exec Source
1 #include <stdlib.h>
2 #include "search.h"
3 #include "block-iter.h"
4 #include "buffer.h"
5 #include "command/error.h"
6 #include "editor.h"
7 #include "regexp.h"
8 #include "util/ascii.h"
9 #include "util/xmalloc.h"
10 #include "window.h"
11
12 // Recurses at most once
13 // NOLINTNEXTLINE(misc-no-recursion)
14 8 static bool do_search_fwd(View *view, regex_t *regex, BlockIter *bi, bool skip)
15 {
16 8 int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL;
17
18 16 do {
19
2/2
✓ Branch 0 (3→4) taken 14 times.
✓ Branch 1 (3→18) taken 2 times.
16 if (block_iter_is_eof(bi)) {
20 6 return false;
21 }
22
23 14 regmatch_t match;
24 14 StringView line = block_iter_get_line(bi);
25
26 // NOTE: If this is the first iteration then line.data contains
27 // partial line (text starting from the cursor position) and
28 // if match.rm_so is 0 then match is at beginning of the text
29 // which is same as the cursor position.
30
2/2
✓ Branch 0 (6→7) taken 4 times.
✓ Branch 1 (6→15) taken 10 times.
14 if (regexp_exec(regex, line.data, line.length, 1, &match, flags)) {
31
3/4
✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→13) taken 3 times.
✓ Branch 2 (8→9) taken 1 times.
✗ Branch 3 (8→13) not taken.
4 if (skip && match.rm_so == 0) {
32 // Ignore match at current cursor position
33 1 regoff_t count = match.rm_eo;
34
1/2
✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→11) taken 1 times.
1 if (count == 0) {
35 // It is safe to skip one byte because every line
36 // has one extra byte (newline) that is not in line.data
37 count = 1;
38 }
39 1 block_iter_skip_bytes(bi, (size_t)count);
40 1 return do_search_fwd(view, regex, bi, false);
41 }
42
43 3 block_iter_skip_bytes(bi, match.rm_so);
44 3 view->cursor = *bi;
45 3 view->center_on_scroll = true;
46 3 view_reset_preferred_x(view);
47 3 return true;
48 }
49
50 10 skip = false; // Not at cursor position any more
51 10 flags = 0;
52
2/2
✓ Branch 0 (16→3) taken 8 times.
✓ Branch 1 (16→19) taken 2 times.
10 } while (block_iter_next_line(bi));
53
54 return false;
55 }
56
57 2 static bool do_search_bwd(View *view, regex_t *regex, BlockIter *bi, ssize_t cx, bool skip)
58 {
59
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 1 times.
2 if (block_iter_is_eof(bi)) {
60 1 goto next;
61 }
62
63 5 do {
64 5 regmatch_t match;
65 5 int flags = 0;
66 5 regoff_t offset = -1;
67 5 regoff_t pos = 0;
68 5 StringView line = block_iter_get_line(bi);
69
70 5 while (
71 6 pos <= line.length
72
3/4
✓ Branch 0 (11→12) taken 6 times.
✗ Branch 1 (11→14) not taken.
✓ Branch 2 (13→6) taken 2 times.
✓ Branch 3 (13→14) taken 4 times.
6 && regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags)
73 ) {
74 2 flags = REG_NOTBOL;
75
2/2
✓ Branch 0 (6→7) taken 1 times.
✓ Branch 1 (6→10) taken 1 times.
2 if (cx >= 0) {
76
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→14) taken 1 times.
1 if (pos + match.rm_so >= cx) {
77 // Ignore match at or after cursor
78 break;
79 }
80 if (skip && pos + match.rm_eo > cx) {
81 // Search -rw should not find word under cursor
82 break;
83 }
84 }
85
86 // This might be what we want (last match before cursor)
87 1 offset = pos + match.rm_so;
88 1 pos += match.rm_eo;
89
90
1/2
✓ Branch 0 (10→11) taken 1 times.
✗ Branch 1 (10→14) not taken.
1 if (match.rm_so == match.rm_eo) {
91 // Zero length match
92 break;
93 }
94 }
95
96
2/2
✓ Branch 0 (14→15) taken 1 times.
✓ Branch 1 (14→18) taken 4 times.
5 if (offset >= 0) {
97 1 block_iter_skip_bytes(bi, offset);
98 1 view->cursor = *bi;
99 1 view->center_on_scroll = true;
100 1 view_reset_preferred_x(view);
101 1 return true;
102 }
103
104 4 next:
105 5 cx = -1;
106
2/2
✓ Branch 0 (20→4) taken 4 times.
✓ Branch 1 (20→21) taken 1 times.
5 } while (block_iter_prev_line(bi));
107
108 return false;
109 }
110
111 1 bool search_tag(View *view, const char *pattern)
112 {
113 // DEFAULT_REGEX_FLAGS is not used here because pattern has been
114 // escaped by parse_ex_pattern() for use as a POSIX BRE
115 1 regex_t regex;
116 1 int err = regcomp(&regex, pattern, REG_NEWLINE);
117
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 1 times.
1 if (unlikely(err)) {
118 regexp_error_msg(&view->window->editor->err, &regex, pattern, err);
119 }
120
121 1 BlockIter bi = block_iter(view->buffer);
122 1 bool found = do_search_fwd(view, &regex, &bi, false);
123 1 regfree(&regex);
124
125
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 1 times.
1 if (!found) {
126 // Don't center view to cursor unnecessarily
127 view->force_center = false;
128 return error_msg(&view->window->editor->err, "Tag not found");
129 }
130
131 1 view->center_on_scroll = true;
132 1 return true;
133 }
134
135 static bool has_upper(const char *str)
136 {
137 return strn_contains_ascii_char_type(str, strlen(str), ASCII_UPPER);
138 }
139
140 4 static bool update_regex(SearchState *search, ErrorBuffer *ebuf, SearchCaseSensitivity cs)
141 {
142 4 const char *pattern = search->pattern;
143
2/6
✓ Branch 0 (2→3) taken 4 times.
✗ Branch 1 (2→6) not taken.
✗ Branch 2 (3→4) not taken.
✓ Branch 3 (3→6) taken 4 times.
✗ Branch 4 (4→5) not taken.
✗ Branch 5 (4→6) not taken.
4 bool icase = (cs == CSS_FALSE) || (cs == CSS_AUTO && !has_upper(pattern));
144 int flags = REG_NEWLINE | (icase ? REG_ICASE : 0);
145
2/2
✓ Branch 0 (6→7) taken 2 times.
✓ Branch 1 (6→15) taken 2 times.
4 if (flags == search->re_flags) {
146 return true;
147 }
148
149
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→10) taken 2 times.
2 if (search->re_flags) {
150 regfree(&search->regex);
151 search->re_flags = 0;
152 }
153
154
1/2
✓ Branch 0 (11→12) taken 2 times.
✗ Branch 1 (11→13) not taken.
2 if (regexp_compile(ebuf, &search->regex, pattern, flags)) {
155 2 search->re_flags = flags;
156 2 return true;
157 }
158
159 regfree(&search->regex);
160 return false;
161 }
162
163 11 void search_free_regexp(SearchState *search)
164 {
165
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→5) taken 9 times.
11 if (search->re_flags) {
166 2 regfree(&search->regex);
167 2 search->re_flags = 0;
168 }
169 11 free(search->pattern);
170 11 }
171
172 2 void search_set_regexp(SearchState *search, const char *pattern)
173 {
174 2 search_free_regexp(search);
175 2 search->pattern = xstrdup(pattern);
176 2 }
177
178 4 bool do_search_next(View *view, SearchState *search, SearchCaseSensitivity cs, bool skip)
179 {
180 4 ErrorBuffer *ebuf = &view->window->editor->err;
181
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 4 times.
4 if (!search->pattern) {
182 return error_msg(ebuf, "No previous search pattern");
183 }
184
1/2
✓ Branch 0 (5→6) taken 4 times.
✗ Branch 1 (5→21) not taken.
4 if (!update_regex(search, ebuf, cs)) {
185 return false;
186 }
187
188 4 BlockIter bi = view->cursor;
189 4 regex_t *regex = &search->regex;
190
2/2
✓ Branch 0 (6→7) taken 3 times.
✓ Branch 1 (6→13) taken 1 times.
4 if (!search->reverse) {
191
1/2
✓ Branch 0 (8→9) taken 3 times.
✗ Branch 1 (8→21) not taken.
3 if (do_search_fwd(view, regex, &bi, true)) {
192 return true;
193 }
194 3 block_iter_bof(&bi);
195
2/2
✓ Branch 0 (11→12) taken 2 times.
✓ Branch 1 (11→20) taken 1 times.
3 if (do_search_fwd(view, regex, &bi, false)) {
196 2 return info_msg(ebuf, "Continuing at top");
197 }
198 } else {
199 1 size_t cursor_x = block_iter_bol(&bi);
200
1/2
✓ Branch 0 (15→16) taken 1 times.
✗ Branch 1 (15→21) not taken.
1 if (do_search_bwd(view, regex, &bi, cursor_x, skip)) {
201 return true;
202 }
203 1 block_iter_eof(&bi);
204
1/2
✓ Branch 0 (18→19) taken 1 times.
✗ Branch 1 (18→20) not taken.
1 if (do_search_bwd(view, regex, &bi, -1, false)) {
205 1 return info_msg(ebuf, "Continuing at bottom");
206 }
207 }
208
209 1 return error_msg(ebuf, "Pattern '%s' not found", search->pattern);
210 }
211