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.data, line.length, 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 regmatch_t match;
64 6 int flags = 0;
65 6 regoff_t offset = -1;
66 6 regoff_t pos = 0;
67 6 StringView line = block_iter_get_line(bi);
68
69 6 while (
70 8 pos <= line.length
71
3/4
✓ Branch 11 → 12 taken 8 times.
✗ Branch 11 → 14 not taken.
✓ Branch 13 → 6 taken 3 times.
✓ Branch 13 → 14 taken 5 times.
8 && regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags)
72 ) {
73 3 flags = REG_NOTBOL;
74
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 1 time.
3 if (cx >= 0) {
75
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 14 taken 1 time.
2 if (pos + match.rm_so >= cx) {
76 // Ignore match at or after cursor
77 break;
78 }
79
1/4
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 14 not taken.
1 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 2 offset = pos + match.rm_so;
87 2 pos += match.rm_eo;
88
89
1/2
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 14 not taken.
2 if (match.rm_so == match.rm_eo) {
90 // Zero length match
91 break;
92 }
93 }
94
95
2/2
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 18 taken 4 times.
6 if (offset >= 0) {
96 2 block_iter_skip_bytes(bi, offset);
97 2 view->cursor = *bi;
98 2 view->center_on_scroll = true;
99 2 view_reset_preferred_x(view);
100 2 return true;
101 }
102
103 4 next:
104 5 cx = -1;
105
2/2
✓ Branch 20 → 4 taken 4 times.
✓ Branch 20 → 21 taken 1 time.
5 } while (block_iter_prev_line(bi));
106
107 return false;
108 }
109
110 1 bool search_tag(View *view, ErrorBuffer *ebuf, 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 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (unlikely(err)) {
117 regexp_error_msg(ebuf, &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 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (!found) {
125 // Don't center view to cursor unnecessarily
126 view->force_center = false;
127 return error_msg(ebuf, "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 return strview_contains_char_type(strview(str), ASCII_UPPER);
137 }
138
139 5 static bool update_regex(SearchState *search, ErrorBuffer *ebuf, SearchCaseSensitivity cs)
140 {
141 5 const char *pattern = search->pattern;
142
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));
143 int flags = REG_NEWLINE | (icase ? REG_ICASE : 0);
144
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 15 taken 2 times.
5 if (flags == search->re_flags) {
145 return true;
146 }
147
148
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 3 times.
3 if (search->re_flags) {
149 regfree(&search->regex);
150 search->re_flags = 0;
151 }
152
153
1/2
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 13 not taken.
3 if (regexp_compile(ebuf, &search->regex, pattern, flags)) {
154 3 search->re_flags = flags;
155 3 return true;
156 }
157
158 regfree(&search->regex);
159 return false;
160 }
161
162 14 void search_free_regexp(SearchState *search)
163 {
164
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 5 taken 11 times.
14 if (search->re_flags) {
165 3 regfree(&search->regex);
166 3 search->re_flags = 0;
167 }
168 14 free(search->pattern);
169 14 }
170
171 3 void search_set_regexp(SearchState *search, const char *pattern)
172 {
173 3 search_free_regexp(search);
174 3 search->pattern = xstrdup(pattern);
175 3 }
176
177 5 bool do_search_next(View *view, SearchState *search, ErrorBuffer *ebuf, SearchCaseSensitivity cs, bool skip)
178 {
179
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 5 times.
5 if (!search->pattern) {
180 return error_msg(ebuf, "No previous search pattern");
181 }
182
1/2
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 21 not taken.
5 if (!update_regex(search, ebuf, cs)) {
183 return false;
184 }
185
186 5 BlockIter bi = view->cursor;
187 5 regex_t *regex = &search->regex;
188
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 13 taken 2 times.
5 if (!search->reverse) {
189
1/2
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 21 not taken.
3 if (do_search_fwd(view, regex, &bi, true)) {
190 return true;
191 }
192 3 block_iter_bof(&bi);
193
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 20 taken 1 time.
3 if (do_search_fwd(view, regex, &bi, false)) {
194 2 return info_msg(ebuf, "Continuing at top");
195 }
196 } else {
197 2 size_t cursor_x = block_iter_bol(&bi);
198
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)) {
199 return true;
200 }
201 1 block_iter_eof(&bi);
202
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
1 if (do_search_bwd(view, regex, &bi, -1, false)) {
203 1 return info_msg(ebuf, "Continuing at bottom");
204 }
205 }
206
207 1 return error_msg(ebuf, "Pattern '%s' not found", search->pattern);
208 }
209