1 #include "search.h"
2 #include "buffer.h"
3 #include "change.h"
4 #include "edit.h"
5 #include "editor.h"
6 #include "error.h"
7 #include "regexp.h"
8 #include "selection.h"
9 #include "util/ascii.h"
10 #include "util/string.h"
11 #include "util/xmalloc.h"
12 #include "view.h"
13
14 #define MAX_SUBSTRINGS 32
15
do_search_fwd(regex_t * regex,BlockIter * bi,bool skip)16 static bool do_search_fwd(regex_t *regex, BlockIter *bi, bool skip)
17 {
18 int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL;
19
20 do {
21 regmatch_t match;
22 LineRef lr;
23
24 if (block_iter_is_eof(bi)) {
25 return false;
26 }
27
28 fill_line_ref(bi, &lr);
29
30 // NOTE: If this is the first iteration then lr.line contains
31 // partial line (text starting from the cursor position) and
32 // if match.rm_so is 0 then match is at beginning of the text
33 // which is same as the cursor position.
34 if (regexp_exec(regex, lr.line, lr.size, 1, &match, flags)) {
35 if (skip && match.rm_so == 0) {
36 // Ignore match at current cursor position
37 regoff_t count = match.rm_eo;
38 if (count == 0) {
39 // It is safe to skip one byte because every line
40 // has one extra byte (newline) that is not in lr.line
41 count = 1;
42 }
43 block_iter_skip_bytes(bi, (size_t)count);
44 return do_search_fwd(regex, bi, false);
45 }
46
47 block_iter_skip_bytes(bi, match.rm_so);
48 view->cursor = *bi;
49 view->center_on_scroll = true;
50 view_reset_preferred_x(view);
51 return true;
52 }
53 skip = false; // Not at cursor position anymore
54 flags = 0;
55 } while (block_iter_next_line(bi));
56 return false;
57 }
58
do_search_bwd(regex_t * regex,BlockIter * bi,ssize_t cx,bool skip)59 static bool do_search_bwd(regex_t *regex, BlockIter *bi, ssize_t cx, bool skip)
60 {
61 if (block_iter_is_eof(bi)) {
62 goto next;
63 }
64
65 do {
66 regmatch_t match;
67 LineRef lr;
68 int flags = 0;
69 regoff_t offset = -1;
70 regoff_t pos = 0;
71
72 fill_line_ref(bi, &lr);
73 while (
74 pos <= lr.size
75 && regexp_exec(regex, lr.line + pos, lr.size - pos, 1, &match, flags)
76 ) {
77 flags = REG_NOTBOL;
78 if (cx >= 0) {
79 if (pos + match.rm_so >= cx) {
80 // Ignore match at or after cursor
81 break;
82 }
83 if (skip && pos + match.rm_eo > cx) {
84 // Search -rw should not find word under cursor
85 break;
86 }
87 }
88
89 // This might be what we want (last match before cursor)
90 offset = pos + match.rm_so;
91 pos += match.rm_eo;
92
93 if (match.rm_so == match.rm_eo) {
94 // Zero length match
95 break;
96 }
97 }
98
99 if (offset >= 0) {
100 block_iter_skip_bytes(bi, offset);
101 view->cursor = *bi;
102 view->center_on_scroll = true;
103 view_reset_preferred_x(view);
104 return true;
105 }
106 next:
107 cx = -1;
108 } while (block_iter_prev_line(bi));
109 return false;
110 }
111
search_tag(const char * pattern,bool * err)112 bool search_tag(const char *pattern, bool *err)
113 {
114 BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks);
115 regex_t regex;
116 bool found = false;
117
118 if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) {
119 *err = true;
120 } else if (do_search_fwd(®ex, &bi, false)) {
121 view->center_on_scroll = true;
122 found = true;
123 } else {
124 // Don't center view to cursor unnecessarily
125 view->force_center = false;
126 error_msg("Tag not found.");
127 *err = true;
128 }
129 regfree(®ex);
130 return found;
131 }
132
133 static struct {
134 regex_t regex;
135 char *pattern;
136 SearchDirection direction;
137
138 // If zero then regex hasn't been compiled
139 int re_flags;
140 } current_search;
141
search_set_direction(SearchDirection dir)142 void search_set_direction(SearchDirection dir)
143 {
144 current_search.direction = dir;
145 }
146
current_search_direction(void)147 SearchDirection current_search_direction(void)
148 {
149 return current_search.direction;
150 }
151
free_regex(void)152 static void free_regex(void)
153 {
154 if (current_search.re_flags) {
155 regfree(¤t_search.regex);
156 current_search.re_flags = 0;
157 }
158 }
159
has_upper(const char * str)160 static bool has_upper(const char *str)
161 {
162 for (size_t i = 0; str[i]; i++) {
163 if (ascii_isupper(str[i])) {
164 return true;
165 }
166 }
167 return false;
168 }
169
update_regex(void)170 static bool update_regex(void)
171 {
172 int re_flags = REG_NEWLINE;
173
174 switch (editor.options.case_sensitive_search) {
175 case CSS_TRUE:
176 break;
177 case CSS_FALSE:
178 re_flags |= REG_ICASE;
179 break;
180 case CSS_AUTO:
181 if (!has_upper(current_search.pattern)) {
182 re_flags |= REG_ICASE;
183 }
184 break;
185 }
186
187 if (re_flags == current_search.re_flags) {
188 return true;
189 }
190
191 free_regex();
192
193 current_search.re_flags = re_flags;
194 if (regexp_compile (
195 ¤t_search.regex,
196 current_search.pattern,
197 current_search.re_flags
198 )) {
199 return true;
200 }
201
202 free_regex();
203 return false;
204 }
205
search_set_regexp(const char * pattern)206 void search_set_regexp(const char *pattern)
207 {
208 free_regex();
209 free(current_search.pattern);
210 current_search.pattern = xstrdup(pattern);
211 }
212
do_search_next(bool skip)213 static void do_search_next(bool skip)
214 {
215 BlockIter bi = view->cursor;
216
217 if (!current_search.pattern) {
218 error_msg("No previous search pattern.");
219 return;
220 }
221 if (!update_regex()) {
222 return;
223 }
224 if (current_search.direction == SEARCH_FWD) {
225 if (do_search_fwd(¤t_search.regex, &bi, true)) {
226 return;
227 }
228
229 block_iter_bof(&bi);
230 if (do_search_fwd(¤t_search.regex, &bi, false)) {
231 info_msg("Continuing at top.");
232 } else {
233 info_msg("Pattern '%s' not found.", current_search.pattern);
234 }
235 } else {
236 size_t cursor_x = block_iter_bol(&bi);
237 if (do_search_bwd(¤t_search.regex, &bi, cursor_x, skip)) {
238 return;
239 }
240
241 block_iter_eof(&bi);
242 if (do_search_bwd(¤t_search.regex, &bi, -1, false)) {
243 info_msg("Continuing at bottom.");
244 } else {
245 info_msg("Pattern '%s' not found.", current_search.pattern);
246 }
247 }
248 }
249
search_prev(void)250 void search_prev(void)
251 {
252 current_search.direction ^= 1;
253 search_next();
254 current_search.direction ^= 1;
255 }
256
search_next(void)257 void search_next(void)
258 {
259 do_search_next(false);
260 }
261
search_next_word(void)262 void search_next_word(void)
263 {
264 do_search_next(true);
265 }
266
build_replacement(String * buf,const char * line,const char * format,regmatch_t * m)267 static void build_replacement (
268 String *buf,
269 const char *line,
270 const char *format,
271 regmatch_t *m
272 ) {
273 size_t i = 0;
274
275 while (format[i]) {
276 int ch = format[i++];
277
278 if (ch == '\\') {
279 if (format[i] >= '1' && format[i] <= '9') {
280 int n = format[i++] - '0';
281 int len = m[n].rm_eo - m[n].rm_so;
282 if (len > 0) {
283 string_add_buf(buf, line + m[n].rm_so, len);
284 }
285 } else if (format[i] != '\0') {
286 string_add_byte(buf, format[i++]);
287 }
288 } else if (ch == '&') {
289 int len = m[0].rm_eo - m[0].rm_so;
290 if (len > 0) {
291 string_add_buf(buf, line + m[0].rm_so, len);
292 }
293 } else {
294 string_add_byte(buf, ch);
295 }
296 }
297 }
298
299 /*
300 * s/abc/x
301 *
302 * string to match against
303 * -------------------------------------------
304 * "foo abc bar abc baz" "foo abc bar abc baz"
305 * "foo x bar abc baz" " bar abc baz"
306 */
replace_on_line(LineRef * lr,regex_t * re,const char * format,BlockIter * bi,ReplaceFlags * flagsp)307 static unsigned int replace_on_line (
308 LineRef *lr,
309 regex_t *re,
310 const char *format,
311 BlockIter *bi,
312 ReplaceFlags *flagsp
313 ) {
314 unsigned char *buf = (unsigned char *)lr->line;
315 ReplaceFlags flags = *flagsp;
316 regmatch_t m[MAX_SUBSTRINGS];
317 size_t pos = 0;
318 int eflags = 0;
319 unsigned int nr = 0;
320
321 while (regexp_exec (
322 re,
323 buf + pos,
324 lr->size - pos,
325 MAX_SUBSTRINGS,
326 m,
327 eflags
328 )) {
329 regoff_t match_len = m[0].rm_eo - m[0].rm_so;
330 bool skip = false;
331
332 // Move cursor to beginning of the text to replace
333 block_iter_skip_bytes(bi, m[0].rm_so);
334 view->cursor = *bi;
335
336 if (flags & REPLACE_CONFIRM) {
337 switch (get_confirmation("Ynaq", "Replace?")) {
338 case 'y':
339 break;
340 case 'n':
341 skip = true;
342 break;
343 case 'a':
344 flags &= ~REPLACE_CONFIRM;
345 *flagsp = flags;
346
347 // Record rest of the changes as one chain
348 begin_change_chain();
349 break;
350 case 'q':
351 case 0:
352 *flagsp = flags | REPLACE_CANCEL;
353 goto out;
354 }
355 }
356
357 if (skip) {
358 // Move cursor after the matched text
359 block_iter_skip_bytes(&view->cursor, match_len);
360 } else {
361 String b = STRING_INIT;
362
363 build_replacement(&b, buf + pos, format, m);
364
365 // lineref is invalidated by modification
366 if (buf == lr->line && lr->size != 0) {
367 buf = xmemdup(buf, lr->size);
368 }
369
370 buffer_replace_bytes(match_len, b.buffer, b.len);
371 nr++;
372
373 // Update selection length
374 if (view->selection) {
375 view->sel_eo += b.len;
376 view->sel_eo -= match_len;
377 }
378
379 // Move cursor after the replaced text
380 block_iter_skip_bytes(&view->cursor, b.len);
381 string_free(&b);
382 }
383 *bi = view->cursor;
384
385 if (!match_len) {
386 break;
387 }
388
389 if (!(flags & REPLACE_GLOBAL)) {
390 break;
391 }
392
393 pos += m[0].rm_so + match_len;
394
395 // Don't match beginning of line again
396 eflags = REG_NOTBOL;
397 }
398 out:
399 if (buf != lr->line) {
400 free(buf);
401 }
402 return nr;
403 }
404
reg_replace(const char * pattern,const char * format,ReplaceFlags flags)405 void reg_replace(const char *pattern, const char *format, ReplaceFlags flags)
406 {
407 BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks);
408 size_t nr_bytes;
409 bool swapped = false;
410 int re_flags = REG_NEWLINE;
411 unsigned int nr_substitutions = 0;
412 size_t nr_lines = 0;
413 regex_t re;
414
415 if (pattern[0] == '\0') {
416 error_msg("Search pattern must contain at least 1 character");
417 return;
418 }
419
420 if (flags & REPLACE_IGNORE_CASE) {
421 re_flags |= REG_ICASE;
422 }
423 if (flags & REPLACE_BASIC) {
424 if (!regexp_compile_basic(&re, pattern, re_flags)) {
425 return;
426 }
427 } else {
428 if (!regexp_compile(&re, pattern, re_flags)) {
429 return;
430 }
431 }
432
433 if (view->selection) {
434 SelectionInfo info;
435 init_selection(view, &info);
436 view->cursor = info.si;
437 view->sel_so = info.so;
438 view->sel_eo = info.eo;
439 swapped = info.swapped;
440 bi = view->cursor;
441 nr_bytes = info.eo - info.so;
442 } else {
443 BlockIter eof = bi;
444 block_iter_eof(&eof);
445 nr_bytes = block_iter_get_offset(&eof);
446 }
447
448 // Record multiple changes as one chain only when replacing all
449 if (!(flags & REPLACE_CONFIRM)) {
450 begin_change_chain();
451 }
452
453 while (1) {
454 // Number of bytes to process
455 size_t count;
456 LineRef lr;
457 unsigned int nr;
458
459 fill_line_ref(&bi, &lr);
460 count = lr.size;
461 if (lr.size > nr_bytes) {
462 // End of selection is not full line
463 lr.size = nr_bytes;
464 }
465
466 nr = replace_on_line(&lr, &re, format, &bi, &flags);
467 if (nr) {
468 nr_substitutions += nr;
469 nr_lines++;
470 }
471 if (flags & REPLACE_CANCEL) {
472 break;
473 }
474 if (count + 1 >= nr_bytes) {
475 break;
476 }
477 nr_bytes -= count + 1;
478
479 block_iter_next_line(&bi);
480 }
481
482 if (!(flags & REPLACE_CONFIRM)) {
483 end_change_chain();
484 }
485
486 regfree(&re);
487
488 if (nr_substitutions) {
489 info_msg("%u substitutions on %zu lines.", nr_substitutions, nr_lines);
490 } else if (!(flags & REPLACE_CANCEL)) {
491 info_msg("Pattern '%s' not found.", pattern);
492 }
493
494 if (view->selection) {
495 // Undo what init_selection() did
496 if (view->sel_eo) {
497 view->sel_eo--;
498 }
499 if (swapped) {
500 ssize_t tmp = view->sel_so;
501 view->sel_so = view->sel_eo;
502 view->sel_eo = tmp;
503 }
504 block_iter_goto_offset(&view->cursor, view->sel_eo);
505 view->sel_eo = UINT_MAX;
506 }
507 }
508