blob: 53675c4bc3515d5b6f0200d3bf953272d057fd60 [file] [log] [blame]
Chris Allegrettabceb1b22000-06-19 04:22:15 +00001/**************************************************************************
2 * search.c *
3 * *
4 * Copyright (C) 2000 Chris Allegretta *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 1, or (at your option) *
8 * any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software *
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
18 * *
19 **************************************************************************/
20
21#include <stdlib.h>
22#include <string.h>
Chris Allegretta47805612000-07-07 02:35:34 +000023#include <unistd.h>
Chris Allegrettabceb1b22000-06-19 04:22:15 +000024#include <stdio.h>
25#include "config.h"
26#include "proto.h"
27#include "nano.h"
Chris Allegretta4da1fc62000-06-21 03:00:43 +000028
Chris Allegrettabceb1b22000-06-19 04:22:15 +000029#ifndef NANO_SMALL
30#include <libintl.h>
31#define _(string) gettext(string)
32#else
33#define _(string) (string)
34#endif
35
Chris Allegretta9fc8d432000-07-07 01:49:52 +000036/* Regular expression helper functions */
37
Chris Allegretta47805612000-07-07 02:35:34 +000038#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +000039void regexp_init(const char *regexp)
40{
41 regcomp(&search_regexp, regexp, ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE);
42 SET(REGEXP_COMPILED);
43}
44
45void regexp_cleanup()
46{
47 UNSET(REGEXP_COMPILED);
48 regfree(&search_regexp);
49}
Chris Allegretta47805612000-07-07 02:35:34 +000050#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +000051
Chris Allegrettabceb1b22000-06-19 04:22:15 +000052/* Set up the system variables for a search or replace. Returns -1 on
53 abort, 0 on success, and 1 on rerun calling program
54 Return -2 to run opposite program (searchg -> replace, replace -> search)
55
56 replacing = 1 if we call from do_replace, 0 if called from do_search func.
57*/
58int search_init(int replacing)
59{
60 int i;
Robert Siemborski6af14312000-07-01 21:34:26 +000061 char buf[BUFSIZ];
Chris Allegrettaa4d21622000-07-08 23:57:03 +000062 char *prompt, *reprompt = "";
Chris Allegretta9fc8d432000-07-07 01:49:52 +000063
Chris Allegrettabceb1b22000-06-19 04:22:15 +000064 if (last_search[0]) {
Robert Siemborski6af14312000-07-01 21:34:26 +000065 snprintf(buf, BUFSIZ, " [%s]", last_search);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000066 } else {
67 buf[0] = '\0';
68 }
69
Chris Allegretta9fc8d432000-07-07 01:49:52 +000070 if (ISSET(USE_REGEXP) && ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000071 prompt = _("Case Sensitive Regexp Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000072 else if (ISSET(USE_REGEXP))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000073 prompt = _("Regexp Search%s%s");
Chris Allegretta47805612000-07-07 02:35:34 +000074 else
75 if (ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000076 prompt = _("Case Sensitive Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000077 else
Chris Allegrettaa4d21622000-07-08 23:57:03 +000078 prompt = _("Search%s%s");
79
80 if (replacing)
81 reprompt = _(" (to replace)");
82
Chris Allegrettabceb1b22000-06-19 04:22:15 +000083 i = statusq(replacing ? replace_list : whereis_list,
84 replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, "",
Chris Allegrettaa4d21622000-07-08 23:57:03 +000085 prompt, reprompt, buf);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000086
87 /* Cancel any search, or just return with no previous search */
88 if ((i == -1) || (i < 0 && !last_search[0])) {
89 statusbar(_("Search Cancelled"));
90 reset_cursor();
91 return -1;
92 } else if (i == -2) { /* Same string */
93 strncpy(answer, last_search, 132);
Chris Allegretta47805612000-07-07 02:35:34 +000094#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +000095 if (ISSET(USE_REGEXP))
96 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +000097#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +000098 } else if (i == 0) { /* They entered something new */
99 strncpy(last_search, answer, 132);
Chris Allegretta47805612000-07-07 02:35:34 +0000100#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000101 if (ISSET(USE_REGEXP))
102 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +0000103#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000104 /* Blow away last_replace because they entered a new search
105 string....uh, right? =) */
106 last_replace[0] = '\0';
107 } else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
108 if (ISSET(CASE_SENSITIVE))
109 UNSET(CASE_SENSITIVE);
110 else
111 SET(CASE_SENSITIVE);
112
113 return 1;
114 } else if (i == NANO_OTHERSEARCH_KEY) {
115 return -2; /* Call the opposite search function */
Chris Allegretta8c2b40f2000-06-29 01:30:04 +0000116 } else if (i == NANO_FROMSEARCHTOGOTO_KEY) {
117 do_gotoline_void();
118 return -3;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000119 } else { /* First line key, etc. */
120 do_early_abort();
121 return -3;
122 }
123
124 return 0;
125}
126
127filestruct *findnextstr(int quiet, filestruct * begin, char *needle)
128{
129 filestruct *fileptr;
130 char *searchstr, *found = NULL, *tmp;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000131 int past_editbot = 0;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000132
133 fileptr = current;
134
135 searchstr = &current->data[current_x + 1];
136 /* Look for searchstr until EOF */
137 while (fileptr != NULL &&
138 (found = strstrwrapper(searchstr, needle)) == NULL) {
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000139
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000140 fileptr = fileptr->next;
141
Adam Rogoyskia966d992000-07-09 18:42:53 +0000142 if (!past_editbot && (fileptr == editbot))
143 past_editbot = 1;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000144
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000145 if (fileptr == begin)
146 return NULL;
147
148 if (fileptr != NULL)
149 searchstr = fileptr->data;
150 }
151
152 /* If we're not at EOF, we found an instance */
153 if (fileptr != NULL) {
154 current = fileptr;
155 current_x = 0;
156 for (tmp = fileptr->data; tmp != found; tmp++)
157 current_x++;
158
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000159 if (past_editbot)
160 edit_update(current);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000161 reset_cursor();
162 } else { /* We're at EOF, go back to the top, once */
163
164 fileptr = fileage;
165
166 while (fileptr != current && fileptr != begin &&
167 (found = strstrwrapper(fileptr->data, needle)) == NULL)
168 fileptr = fileptr->next;
169
170 if (fileptr == begin) {
171 if (!quiet)
172 statusbar(_("\"%s\" not found"), needle);
173
174 return NULL;
175 }
176 if (fileptr != current) { /* We found something */
177 current = fileptr;
178 current_x = 0;
179 for (tmp = fileptr->data; tmp != found; tmp++)
180 current_x++;
181
Adam Rogoyskia966d992000-07-09 18:42:53 +0000182 edit_update(current);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000183 reset_cursor();
184
185 if (!quiet)
186 statusbar(_("Search Wrapped"));
187 } else { /* Nada */
188
189 if (!quiet)
190 statusbar(_("\"%s\" not found"), needle);
191 return NULL;
192 }
193 }
194
195 return fileptr;
196}
197
198void search_abort(void)
199{
200 UNSET(KEEP_CUTBUFFER);
201 display_main_list();
202 wrefresh(bottomwin);
Chris Allegretta47805612000-07-07 02:35:34 +0000203
204#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000205 if (ISSET(REGEXP_COMPILED))
206 regexp_cleanup();
Chris Allegretta47805612000-07-07 02:35:34 +0000207#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000208}
209
210/* Search for a string */
211int do_search(void)
212{
213 int i;
214 filestruct *fileptr = current;
215
216 wrap_reset();
217 if ((i = search_init(0)) == -1) {
218 current = fileptr;
219 search_abort();
220 return 0;
221 } else if (i == -3) {
222 search_abort();
223 return 0;
224 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000225 do_replace();
226 return 0;
227 } else if (i == 1) {
228 do_search();
229 search_abort();
230 return 1;
231 }
232 findnextstr(0, current, answer);
233 search_abort();
234 return 1;
235}
236
237void print_replaced(int num)
238{
239 if (num > 1)
240 statusbar(_("Replaced %d occurences"), num);
241 else if (num == 1)
242 statusbar(_("Replaced 1 occurence"));
243}
244
245void replace_abort(void)
246{
247 UNSET(KEEP_CUTBUFFER);
248 display_main_list();
249 reset_cursor();
Chris Allegretta47805612000-07-07 02:35:34 +0000250
251#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000252 if (ISSET(REGEXP_COMPILED))
253 regexp_cleanup();
Chris Allegretta47805612000-07-07 02:35:34 +0000254#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000255}
256
Chris Allegretta47805612000-07-07 02:35:34 +0000257#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000258int replace_regexp(char *string, int create_flag)
259{
260 /* split personality here - if create_flag is null, just calculate
261 * the size of the replacement line (necessary because of
262 * subexpressions like \1 \2 \3 in the replaced text) */
263
264 char *c;
265 int new_size = strlen(current->data) + 1;
266 int search_match_count = regmatches[0].rm_eo -
267 regmatches[0].rm_so;
268
269 new_size -= search_match_count;
270
271 /* Iterate through the replacement text to handle
272 * subexpression replacement using \1, \2, \3, etc */
273
274 c = last_replace;
275 while (*c) {
276 if (*c != '\\') {
277 if (create_flag)
278 *string++=*c;
279 c++;
280 new_size++;
281 } else {
282 int num = (int)*(c+1) - (int)'0';
283 if (num >= 1 && num <= 9) {
284
285 int i = regmatches[num].rm_so;
286
287 if (num > search_regexp.re_nsub) {
288 /* Ugh, they specified a subexpression that doesn't
289 exist. */
290 return -1;
291 }
292
293 /* Skip over the replacement expression */
294 c+=2;
295
296 /* But add the length of the subexpression to new_size */
297 new_size += regmatches[num].rm_eo - regmatches[num].rm_so;
298
299 /* And if create_flag is set, append the result of the
300 * subexpression match to the new line */
301 while (create_flag && i < regmatches[num].rm_eo )
302 *string++=*(current->data + i++);
303
304 } else {
305 if (create_flag)
306 *string++=*c;
307 c++;
308 new_size++;
309 }
310 }
311 }
312
313 if (create_flag)
314 *string = 0;
315
316 return new_size;
317}
Chris Allegretta47805612000-07-07 02:35:34 +0000318#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000319
320char *replace_line()
321{
322 char *copy, *tmp;
323 int new_line_size;
324 int search_match_count;
325
326 /* Calculate size of new line */
Chris Allegretta47805612000-07-07 02:35:34 +0000327#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000328 if (ISSET(USE_REGEXP)) {
329 search_match_count = regmatches[0].rm_eo -
330 regmatches[0].rm_so;
331 new_line_size = replace_regexp(NULL, 0);
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000332 /* If they specified an invalid subexpression in the replace
333 * text, return NULL indicating an error */
334 if (new_line_size < 0)
335 return NULL;
336 } else {
Chris Allegretta47805612000-07-07 02:35:34 +0000337#else
338 {
339#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000340 search_match_count = strlen(last_search);
341 new_line_size = strlen(current->data) - strlen(last_search) +
342 strlen(last_replace) + 1;
343 }
344
345 /* Create buffer */
346 copy = nmalloc(new_line_size);
347
348 /* Head of Original Line */
349 strncpy(copy, current->data, current_x);
350 copy[current_x] = 0;
351
352 /* Replacement Text */
353 if (!ISSET(USE_REGEXP))
354 strcat(copy, last_replace);
Chris Allegretta47805612000-07-07 02:35:34 +0000355#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000356 else
357 (void)replace_regexp(copy + current_x, 1);
Chris Allegretta47805612000-07-07 02:35:34 +0000358#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000359
360 /* The tail of the original line */
361 /* This may expose other bugs, because it no longer
362 goes through each character on the string
363 and tests for string goodness. But because
364 we can assume the invariant that current->data
365 is less than current_x + strlen(last_search) long,
366 this should be safe. Or it will expose bugs ;-) */
367 tmp = current->data + current_x + search_match_count;
368 strcat(copy, tmp);
369
370 return copy;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000371}
372
Chris Allegretta47805612000-07-07 02:35:34 +0000373/* Replace a string */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000374int do_replace(void)
375{
376 int i, replaceall = 0, numreplaced = 0, beginx;
377 filestruct *fileptr, *begin;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000378 char *copy, prevanswer[132] = "";
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000379
380 if ((i = search_init(1)) == -1) {
381 statusbar(_("Replace Cancelled"));
382 replace_abort();
383 return 0;
384 } else if (i == 1) {
385 do_replace();
386 return 1;
387 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000388 do_search();
389 return 0;
390 } else if (i == -3) {
391 replace_abort();
392 return 0;
393 }
394 strncpy(prevanswer, answer, 132);
395
396 if (strcmp(last_replace, "")) { /* There's a previous replace str */
397 i = statusq(replace_list, REPLACE_LIST_LEN, "",
398 _("Replace with [%s]"), last_replace);
399
400 if (i == -1) { /* Aborted enter */
401 strncpy(answer, last_replace, 132);
402 statusbar(_("Replace Cancelled"));
403 replace_abort();
404 return 0;
405 } else if (i == 0) /* They actually entered something */
406 strncpy(last_replace, answer, 132);
407 else if (i == NANO_CASE_KEY) { /* They asked for case sensitivity */
408 if (ISSET(CASE_SENSITIVE))
409 UNSET(CASE_SENSITIVE);
410 else
411 SET(CASE_SENSITIVE);
412
413 do_replace();
414 return 0;
Chris Allegrettaf6b13422000-07-07 04:25:00 +0000415 } else if (i != -2 ) { /* First page, last page, for example could get here */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000416
417 do_early_abort();
418 replace_abort();
419 return 0;
420 }
421 } else { /* last_search is empty */
422
423 i = statusq(replace_list, REPLACE_LIST_LEN, "", _("Replace with"));
424 if (i == -1) {
425 statusbar(_("Replace Cancelled"));
426 reset_cursor();
427 replace_abort();
428 return 0;
429 } else if (i == 0) /* They entered something new */
430 strncpy(last_replace, answer, 132);
431 else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
432 if (ISSET(CASE_SENSITIVE))
433 UNSET(CASE_SENSITIVE);
434 else
435 SET(CASE_SENSITIVE);
436
437 do_replace();
438 return 1;
439 } else { /* First line key, etc. */
440
441 do_early_abort();
442 replace_abort();
443 return 0;
444 }
445 }
446
447 /* save where we are */
448 begin = current;
449 beginx = current_x;
450
451 while (1) {
452
453 if (replaceall)
454 fileptr = findnextstr(1, begin, prevanswer);
455 else
456 fileptr = findnextstr(0, begin, prevanswer);
457
458 /* No more matches. Done! */
459 if (!fileptr)
460 break;
461
462 /* If we're here, we've found the search string */
463 if (!replaceall)
464 i = do_yesno(1, 1, _("Replace this instance?"));
465
466 if (i > 0 || replaceall) { /* Yes, replace it!!!! */
467 if (i == 2)
468 replaceall = 1;
469
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000470 copy = replace_line();
471 if (!copy) {
472 statusbar("Replace failed: unknown subexpression!");
473 replace_abort();
Chris Allegretta68b12332000-07-07 13:10:28 +0000474 return 0;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000475 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000476
477 /* Cleanup */
478 free(current->data);
479 current->data = copy;
480
481 /* Stop bug where we replace a substring of the replacement text */
Chris Allegrettafa8f7bf2000-07-12 02:41:13 +0000482 current_x += strlen(last_replace) - 1;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000483
484 edit_refresh();
485 set_modified();
486 numreplaced++;
487 } else if (i == -1) /* Abort, else do nothing and continue loop */
488 break;
489 }
490
491 current = begin;
492 current_x = beginx;
493 renumber_all();
494 edit_update(current);
495 print_replaced(numreplaced);
496 replace_abort();
497 return 1;
498}
499
500void goto_abort(void)
501{
502 UNSET(KEEP_CUTBUFFER);
503 display_main_list();
504}
505
506int do_gotoline(long defline)
507{
508 long line, i = 1, j = 0;
509 filestruct *fileptr;
510
511 if (defline > 0) /* We already know what line we want to go to */
512 line = defline;
513 else { /* Ask for it */
514
515 j = statusq(goto_list, GOTO_LIST_LEN, "", _("Enter line number"));
516 if (j == -1) {
517 statusbar(_("Aborted"));
518 goto_abort();
519 return 0;
520 } else if (j != 0) {
521 do_early_abort();
522 goto_abort();
523 return 0;
524 }
525 if (!strcmp(answer, "$")) {
526 current = filebot;
527 current_x = 0;
528 edit_update(current);
529 goto_abort();
530 return 1;
531 }
532 line = atoi(answer);
533 }
534
535 /* Bounds check */
536 if (line <= 0) {
537 statusbar(_("Come on, be reasonable"));
538 goto_abort();
539 return 0;
540 }
541 if (line > totlines) {
542 statusbar(_("Only %d lines available, skipping to last line"),
543 filebot->lineno);
544 current = filebot;
545 current_x = 0;
546 edit_update(current);
547 } else {
548 for (fileptr = fileage; fileptr != NULL && i < line; i++)
549 fileptr = fileptr->next;
550
551 current = fileptr;
552 current_x = 0;
553 edit_update(current);
554 }
555
556 goto_abort();
557 return 1;
558}
559
560int do_gotoline_void(void)
561{
562 return do_gotoline(0);
563}