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