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