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