dte test coverage


Directory: ./
File: src/replace.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 111 129 86.0%
Functions: 3 3 100.0%
Branches: 46 64 71.9%

Line Branch Exec Source
1 #include <stdlib.h>
2 #include "replace.h"
3 #include "buffer.h"
4 #include "change.h"
5 #include "command/error.h"
6 #include "editor.h"
7 #include "regexp.h"
8 #include "selection.h"
9 #include "ui.h"
10 #include "util/debug.h"
11 #include "util/string.h"
12 #include "util/xmalloc.h"
13 #include "view.h"
14 #include "window.h"
15
16 24 static void build_replacement (
17 String *buf,
18 const char *line,
19 const char *format,
20 const regmatch_t *matches
21 ) {
22
2/2
✓ Branch 0 (15→3) taken 50 times.
✓ Branch 1 (15→16) taken 24 times.
74 for (size_t i = 0; format[i]; ) {
23 50 char ch = format[i++];
24 50 size_t match_idx;
25
2/2
✓ Branch 0 (3→4) taken 11 times.
✓ Branch 1 (3→9) taken 39 times.
50 if (ch == '\\') {
26
1/2
✓ Branch 0 (4→5) taken 11 times.
✗ Branch 1 (4→16) not taken.
11 if (unlikely(format[i] == '\0')) {
27 break;
28 }
29 11 ch = format[i++];
30
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→8) taken 11 times.
11 if (ch < '1' || ch > '9') {
31 string_append_byte(buf, ch);
32 continue;
33 }
34 11 match_idx = ch - '0';
35
2/2
✓ Branch 0 (9→10) taken 27 times.
✓ Branch 1 (9→12) taken 12 times.
39 } else if (ch == '&') {
36 match_idx = 0;
37 } else {
38 27 string_append_byte(buf, ch);
39 27 continue;
40 }
41 23 const regmatch_t *match = &matches[match_idx];
42 23 regoff_t len = match->rm_eo - match->rm_so;
43
1/2
✓ Branch 0 (12→13) taken 23 times.
✗ Branch 1 (12→14) not taken.
23 if (len > 0) {
44 23 string_append_buf(buf, line + match->rm_so, (size_t)len);
45 }
46 }
47 24 }
48
49 /*
50 * s/abc/x
51 *
52 * string to match against
53 * -------------------------------------------
54 * "foo abc bar abc baz" "foo abc bar abc baz"
55 * "foo x bar abc baz" " bar abc baz"
56 */
57 21 static unsigned int replace_on_line (
58 View *view,
59 StringView *line,
60 regex_t *re,
61 const char *format,
62 BlockIter *bi,
63 ReplaceFlags *flagsp
64 ) {
65 21 const unsigned char *buf = line->data;
66 21 unsigned char *alloc = NULL;
67 21 EditorState *e = view->window->editor;
68 21 ReplaceFlags flags = *flagsp;
69 21 regmatch_t matches[32];
70 21 size_t pos = 0;
71 21 int eflags = 0;
72 21 unsigned int nr = 0;
73
74
2/2
✓ Branch 0 (28→3) taken 24 times.
✓ Branch 1 (28→29) taken 9 times.
54 while (regexp_exec (
75 re,
76 33 buf + pos,
77 33 line->length - pos,
78 ARRAYLEN(matches),
79 matches,
80 eflags
81 )) {
82 24 regoff_t match_len = matches[0].rm_eo - matches[0].rm_so;
83 24 bool skip = false;
84
85 // Move cursor to beginning of the text to replace
86 24 block_iter_skip_bytes(bi, matches[0].rm_so);
87 24 view->cursor = *bi;
88
89
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→10) taken 24 times.
24 if (flags & REPLACE_CONFIRM) {
90 e->screen_update |= UPDATE_CURRENT_BUFFER;
91 switch (status_prompt(e, "Replace? [Y/n/a/q]", "ynaq")) {
92 case 'y':
93 break;
94 case 'n':
95 skip = true;
96 break;
97 case 'a':
98 flags &= ~REPLACE_CONFIRM;
99 *flagsp = flags;
100
101 // Record rest of the changes as one chain
102 begin_change_chain();
103 break;
104 case 'q':
105 case 0:
106 *flagsp = flags | REPLACE_CANCEL;
107 goto out;
108 }
109 }
110
111 if (skip) {
112 // Move cursor after the matched text
113 block_iter_skip_bytes(&view->cursor, match_len);
114 } else {
115 24 String b = STRING_INIT;
116 24 build_replacement(&b, buf + pos, format, matches);
117
118 // line ref is invalidated by modification
119
3/4
✓ Branch 0 (11→12) taken 14 times.
✓ Branch 1 (11→16) taken 10 times.
✓ Branch 2 (12→13) taken 14 times.
✗ Branch 3 (12→16) not taken.
24 if (buf == line->data && line->length != 0) {
120 14 BUG_ON(alloc);
121 14 alloc = xmemdup(buf, line->length);
122 14 buf = alloc;
123 }
124
125 24 buffer_replace_bytes(view, match_len, b.buffer, b.len);
126 24 nr++;
127
128 // Update selection length
129
2/2
✓ Branch 0 (17→18) taken 6 times.
✓ Branch 1 (17→19) taken 18 times.
24 if (view->selection) {
130 6 view->sel_eo += b.len;
131 6 view->sel_eo -= match_len;
132 }
133
134 // Move cursor after the replaced text
135 24 block_iter_skip_bytes(&view->cursor, b.len);
136 24 string_free(&b);
137 }
138 24 *bi = view->cursor;
139
140
1/2
✗ Branch 0 (22→23) not taken.
✓ Branch 1 (22→24) taken 24 times.
24 if (!match_len) {
141 break;
142 }
143
144
2/2
✓ Branch 0 (24→25) taken 12 times.
✓ Branch 1 (24→26) taken 12 times.
24 if (!(flags & REPLACE_GLOBAL)) {
145 break;
146 }
147
148 12 pos += matches[0].rm_so + match_len;
149
150 // Don't match beginning of line again
151 12 eflags = REG_NOTBOL;
152 }
153
154 21 out:
155 21 free(alloc);
156 21 return nr;
157 }
158
159 12 bool reg_replace(View *view, const char *pattern, const char *format, ReplaceFlags flags)
160 {
161 12 ErrorBuffer *ebuf = &view->window->editor->err;
162
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 11 times.
12 if (unlikely(pattern[0] == '\0')) {
163 1 return error_msg(ebuf, "Search pattern must contain at least 1 character");
164 }
165
166 11 int re_flags = REG_NEWLINE;
167 11 re_flags |= (flags & REPLACE_IGNORE_CASE) ? REG_ICASE : 0;
168 11 re_flags |= (flags & REPLACE_BASIC) ? 0 : DEFAULT_REGEX_FLAGS;
169
170 11 regex_t re;
171 11 int err = regcomp(&re, pattern, re_flags);
172
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 11 times.
11 if (unlikely(err)) {
173 return regexp_error_msg(ebuf, &re, pattern, err);
174 }
175
176 11 BlockIter bi;
177 11 bool swapped;
178 11 size_t nr_bytes = 0;
179
2/2
✓ Branch 0 (7→8) taken 2 times.
✓ Branch 1 (7→10) taken 9 times.
11 if (view->selection) {
180 2 SelectionInfo info = init_selection(view);
181 2 bi = info.si;
182 2 view->cursor = info.si;
183 2 view->sel_so = info.so;
184 2 view->sel_eo = info.eo;
185 2 nr_bytes = info.eo - info.so;
186 2 swapped = info.swapped;
187 } else {
188 9 const Block *blk;
189
2/2
✓ Branch 0 (12→11) taken 9 times.
✓ Branch 1 (12→13) taken 9 times.
18 block_for_each(blk, &view->buffer->blocks) {
190 9 nr_bytes += blk->size;
191 }
192 9 bi = block_iter(view->buffer); // BOF
193 9 swapped = false;
194 }
195
196 // Record multiple changes as one chain only when replacing all
197
1/2
✓ Branch 0 (14→15) taken 11 times.
✗ Branch 1 (14→16) not taken.
11 if (!(flags & REPLACE_CONFIRM)) {
198 11 begin_change_chain();
199 }
200
201 unsigned int nr_substitutions = 0;
202 size_t nr_lines = 0;
203 10 while (1) {
204 21 StringView line = block_iter_get_line(&bi);
205
206 // Number of bytes to process
207 21 size_t count = line.length;
208
1/2
✗ Branch 0 (18→19) not taken.
✓ Branch 1 (18→20) taken 21 times.
21 if (line.length > nr_bytes) {
209 // End of selection is not full line
210 line.length = nr_bytes;
211 }
212
213 21 unsigned int nr = replace_on_line(view, &line, &re, format, &bi, &flags);
214
2/2
✓ Branch 0 (21→22) taken 14 times.
✓ Branch 1 (21→23) taken 7 times.
21 if (nr) {
215 14 nr_substitutions += nr;
216 14 nr_lines++;
217 }
218
219
3/4
✓ Branch 0 (23→24) taken 21 times.
✗ Branch 1 (23→27) not taken.
✓ Branch 2 (24→25) taken 10 times.
✓ Branch 3 (24→27) taken 11 times.
21 if (flags & REPLACE_CANCEL || count + 1 >= nr_bytes) {
220 break;
221 }
222
223 10 nr_bytes -= count + 1;
224 10 block_iter_next_line(&bi);
225 }
226
227
1/2
✓ Branch 0 (27→28) taken 11 times.
✗ Branch 1 (27→29) not taken.
11 if (!(flags & REPLACE_CONFIRM)) {
228 11 end_change_chain(view);
229 }
230
231 11 regfree(&re);
232
233
2/2
✓ Branch 0 (30→31) taken 10 times.
✓ Branch 1 (30→36) taken 1 times.
11 if (nr_substitutions) {
234
4/4
✓ Branch 0 (31→32) taken 8 times.
✓ Branch 1 (31→33) taken 2 times.
✓ Branch 2 (33→34) taken 6 times.
✓ Branch 3 (33→35) taken 4 times.
24 info_msg (
235 ebuf,
236 "%u substitution%s on %zu line%s",
237 nr_substitutions,
238 (nr_substitutions > 1) ? "s" : "",
239 nr_lines,
240 (nr_lines > 1) ? "s" : ""
241 );
242
1/2
✓ Branch 0 (36→37) taken 1 times.
✗ Branch 1 (36→38) not taken.
1 } else if (!(flags & REPLACE_CANCEL)) {
243 1 error_msg(ebuf, "Pattern '%s' not found", pattern);
244 }
245
246
2/2
✓ Branch 0 (38→39) taken 2 times.
✓ Branch 1 (38→45) taken 9 times.
11 if (view->selection) {
247 // Undo what init_selection() did
248
1/2
✓ Branch 0 (39→40) taken 2 times.
✗ Branch 1 (39→41) not taken.
2 if (view->sel_eo) {
249 2 view->sel_eo--;
250 }
251
1/2
✓ Branch 0 (41→42) taken 2 times.
✗ Branch 1 (41→43) not taken.
2 if (swapped) {
252 2 ssize_t tmp = view->sel_so;
253 2 view->sel_so = view->sel_eo;
254 2 view->sel_eo = tmp;
255 }
256 2 block_iter_goto_offset(&view->cursor, view->sel_eo);
257 2 view->sel_eo = SEL_EO_RECALC;
258 }
259
260 11 return (nr_substitutions > 0);
261 }
262