dte test coverage


Directory: ./
File: src/search.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 83 104 79.8%
Functions: 6 8 75.0%
Branches: 40 68 58.8%

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