dte test coverage


Directory: ./
File: src/search.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 94 107 87.9%
Functions: 7 8 87.5%
Branches: 42 68 61.8%

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