dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 89.4% 93 / 0 / 104
Functions: 87.5% 7 / 0 / 8
Branches: 70.3% 45 / 0 / 64

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