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(®ex, pattern, REG_NEWLINE)) { | |
114 | return false; | ||
115 | } | ||
116 | |||
117 | ✗ | BlockIter bi = block_iter(view->buffer); | |
118 | ✗ | bool found = do_search_fwd(view, ®ex, &bi, false); | |
119 | ✗ | regfree(®ex); | |
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 |