blob: f6b63f7dedd18e16f7511a7120841ed6b3a424f0 [file] [log] [blame]
Chris Allegretta11b00112000-08-06 21:13:45 +00001/* $Id$ */
Chris Allegrettabceb1b22000-06-19 04:22:15 +00002/**************************************************************************
3 * search.c *
4 * *
5 * Copyright (C) 2000 Chris Allegretta *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 1, or (at your option) *
9 * any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
19 * *
20 **************************************************************************/
21
22#include <stdlib.h>
23#include <string.h>
Chris Allegretta47805612000-07-07 02:35:34 +000024#include <unistd.h>
Chris Allegrettabceb1b22000-06-19 04:22:15 +000025#include <stdio.h>
26#include "config.h"
27#include "proto.h"
28#include "nano.h"
Chris Allegretta4da1fc62000-06-21 03:00:43 +000029
Chris Allegrettabceb1b22000-06-19 04:22:15 +000030#ifndef NANO_SMALL
31#include <libintl.h>
32#define _(string) gettext(string)
33#else
34#define _(string) (string)
35#endif
36
Chris Allegretta756f2202000-09-01 13:32:47 +000037static char last_search[132]; /* Last string we searched for */
38static char last_replace[132]; /* Last replacement string */
39
Chris Allegretta9fc8d432000-07-07 01:49:52 +000040/* Regular expression helper functions */
41
Chris Allegretta805c26d2000-09-06 13:39:17 +000042#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +000043void regexp_init(const char *regexp)
44{
45 regcomp(&search_regexp, regexp, ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE);
46 SET(REGEXP_COMPILED);
47}
48
49void regexp_cleanup()
50{
51 UNSET(REGEXP_COMPILED);
52 regfree(&search_regexp);
53}
Chris Allegretta47805612000-07-07 02:35:34 +000054#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +000055
Chris Allegrettabceb1b22000-06-19 04:22:15 +000056/* Set up the system variables for a search or replace. Returns -1 on
57 abort, 0 on success, and 1 on rerun calling program
58 Return -2 to run opposite program (searchg -> replace, replace -> search)
59
60 replacing = 1 if we call from do_replace, 0 if called from do_search func.
61*/
62int search_init(int replacing)
63{
64 int i;
Robert Siemborski6af14312000-07-01 21:34:26 +000065 char buf[BUFSIZ];
Chris Allegrettaa4d21622000-07-08 23:57:03 +000066 char *prompt, *reprompt = "";
Chris Allegretta9fc8d432000-07-07 01:49:52 +000067
Chris Allegrettabceb1b22000-06-19 04:22:15 +000068 if (last_search[0]) {
Robert Siemborski6af14312000-07-01 21:34:26 +000069 snprintf(buf, BUFSIZ, " [%s]", last_search);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000070 } else {
71 buf[0] = '\0';
72 }
73
Chris Allegretta9fc8d432000-07-07 01:49:52 +000074 if (ISSET(USE_REGEXP) && ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000075 prompt = _("Case Sensitive Regexp Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000076 else if (ISSET(USE_REGEXP))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000077 prompt = _("Regexp Search%s%s");
Chris Allegretta47805612000-07-07 02:35:34 +000078 else
79 if (ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000080 prompt = _("Case Sensitive Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000081 else
Chris Allegrettaa4d21622000-07-08 23:57:03 +000082 prompt = _("Search%s%s");
83
84 if (replacing)
85 reprompt = _(" (to replace)");
86
Chris Allegrettabceb1b22000-06-19 04:22:15 +000087 i = statusq(replacing ? replace_list : whereis_list,
88 replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, "",
Chris Allegrettaa4d21622000-07-08 23:57:03 +000089 prompt, reprompt, buf);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000090
91 /* Cancel any search, or just return with no previous search */
92 if ((i == -1) || (i < 0 && !last_search[0])) {
93 statusbar(_("Search Cancelled"));
94 reset_cursor();
95 return -1;
96 } else if (i == -2) { /* Same string */
97 strncpy(answer, last_search, 132);
Chris Allegretta805c26d2000-09-06 13:39:17 +000098#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +000099 if (ISSET(USE_REGEXP))
100 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +0000101#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000102 } else if (i == 0) { /* They entered something new */
103 strncpy(last_search, answer, 132);
Chris Allegretta805c26d2000-09-06 13:39:17 +0000104#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000105 if (ISSET(USE_REGEXP))
106 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +0000107#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000108 /* Blow away last_replace because they entered a new search
109 string....uh, right? =) */
110 last_replace[0] = '\0';
111 } else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
112 if (ISSET(CASE_SENSITIVE))
113 UNSET(CASE_SENSITIVE);
114 else
115 SET(CASE_SENSITIVE);
116
117 return 1;
118 } else if (i == NANO_OTHERSEARCH_KEY) {
119 return -2; /* Call the opposite search function */
Chris Allegretta8c2b40f2000-06-29 01:30:04 +0000120 } else if (i == NANO_FROMSEARCHTOGOTO_KEY) {
121 do_gotoline_void();
122 return -3;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000123 } else { /* First line key, etc. */
124 do_early_abort();
125 return -3;
126 }
127
128 return 0;
129}
130
131filestruct *findnextstr(int quiet, filestruct * begin, char *needle)
132{
133 filestruct *fileptr;
134 char *searchstr, *found = NULL, *tmp;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000135 int past_editbot = 0;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000136
Chris Allegrettab9bfc9b2000-09-10 05:06:09 +0000137 fileptr = current;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000138
139 searchstr = &current->data[current_x + 1];
140 /* Look for searchstr until EOF */
141 while (fileptr != NULL &&
142 (found = strstrwrapper(searchstr, needle)) == NULL) {
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000143
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000144 fileptr = fileptr->next;
145
Adam Rogoyskia966d992000-07-09 18:42:53 +0000146 if (!past_editbot && (fileptr == editbot))
147 past_editbot = 1;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000148
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000149 if (fileptr == begin)
150 return NULL;
151
152 if (fileptr != NULL)
153 searchstr = fileptr->data;
154 }
155
156 /* If we're not at EOF, we found an instance */
157 if (fileptr != NULL) {
158 current = fileptr;
159 current_x = 0;
160 for (tmp = fileptr->data; tmp != found; tmp++)
161 current_x++;
162
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000163 if (past_editbot)
Chris Allegretta234a34d2000-07-29 04:33:38 +0000164 edit_update(current, CENTER);
Chris Allegrettae10debd2000-08-22 01:26:42 +0000165 placewewant = xplustabs();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000166 reset_cursor();
167 } else { /* We're at EOF, go back to the top, once */
168
169 fileptr = fileage;
170
Chris Allegretta9babaf32000-08-30 13:49:33 +0000171 while (fileptr != begin->next &&
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000172 (found = strstrwrapper(fileptr->data, needle)) == NULL)
173 fileptr = fileptr->next;
174
Chris Allegretta9babaf32000-08-30 13:49:33 +0000175 if (fileptr == begin->next) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000176 if (!quiet)
177 statusbar(_("\"%s\" not found"), needle);
178
179 return NULL;
180 }
Chris Allegretta9babaf32000-08-30 13:49:33 +0000181 else { /* We found something */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000182 current = fileptr;
183 current_x = 0;
184 for (tmp = fileptr->data; tmp != found; tmp++)
185 current_x++;
186
Chris Allegretta234a34d2000-07-29 04:33:38 +0000187 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000188 reset_cursor();
189
190 if (!quiet)
191 statusbar(_("Search Wrapped"));
Chris Allegretta9babaf32000-08-30 13:49:33 +0000192 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000193 }
194
195 return fileptr;
196}
197
198void search_abort(void)
199{
200 UNSET(KEEP_CUTBUFFER);
201 display_main_list();
202 wrefresh(bottomwin);
Chris Allegrettaf1d33d32000-08-19 03:53:39 +0000203 if (ISSET(MARK_ISSET))
204 edit_refresh_clearok();
Chris Allegretta47805612000-07-07 02:35:34 +0000205
Chris Allegretta805c26d2000-09-06 13:39:17 +0000206#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000207 if (ISSET(REGEXP_COMPILED))
208 regexp_cleanup();
Chris Allegretta47805612000-07-07 02:35:34 +0000209#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000210}
211
212/* Search for a string */
213int do_search(void)
214{
215 int i;
216 filestruct *fileptr = current;
217
218 wrap_reset();
219 if ((i = search_init(0)) == -1) {
220 current = fileptr;
221 search_abort();
222 return 0;
223 } else if (i == -3) {
224 search_abort();
225 return 0;
226 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000227 do_replace();
228 return 0;
229 } else if (i == 1) {
230 do_search();
231 search_abort();
232 return 1;
233 }
234 findnextstr(0, current, answer);
235 search_abort();
236 return 1;
237}
238
239void print_replaced(int num)
240{
241 if (num > 1)
242 statusbar(_("Replaced %d occurences"), num);
243 else if (num == 1)
244 statusbar(_("Replaced 1 occurence"));
245}
246
247void replace_abort(void)
248{
Chris Allegretta18bd0292000-07-28 01:18:10 +0000249 /* Identicle to search_abort, so we'll call it here. If it
250 does something different later, we can change it back. For now
251 it's just a waste to duplicat code */
252 search_abort();
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000253}
254
Chris Allegretta805c26d2000-09-06 13:39:17 +0000255#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000256int replace_regexp(char *string, int create_flag)
257{
258 /* split personality here - if create_flag is null, just calculate
259 * the size of the replacement line (necessary because of
260 * subexpressions like \1 \2 \3 in the replaced text) */
261
262 char *c;
263 int new_size = strlen(current->data) + 1;
264 int search_match_count = regmatches[0].rm_eo -
265 regmatches[0].rm_so;
266
267 new_size -= search_match_count;
268
269 /* Iterate through the replacement text to handle
270 * subexpression replacement using \1, \2, \3, etc */
271
272 c = last_replace;
273 while (*c) {
274 if (*c != '\\') {
275 if (create_flag)
276 *string++=*c;
277 c++;
278 new_size++;
279 } else {
280 int num = (int)*(c+1) - (int)'0';
281 if (num >= 1 && num <= 9) {
282
283 int i = regmatches[num].rm_so;
284
285 if (num > search_regexp.re_nsub) {
286 /* Ugh, they specified a subexpression that doesn't
287 exist. */
288 return -1;
289 }
290
291 /* Skip over the replacement expression */
292 c+=2;
293
294 /* But add the length of the subexpression to new_size */
295 new_size += regmatches[num].rm_eo - regmatches[num].rm_so;
296
297 /* And if create_flag is set, append the result of the
298 * subexpression match to the new line */
299 while (create_flag && i < regmatches[num].rm_eo )
300 *string++=*(current->data + i++);
301
302 } else {
303 if (create_flag)
304 *string++=*c;
305 c++;
306 new_size++;
307 }
308 }
309 }
310
311 if (create_flag)
312 *string = 0;
313
314 return new_size;
315}
Chris Allegretta47805612000-07-07 02:35:34 +0000316#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000317
318char *replace_line()
319{
320 char *copy, *tmp;
321 int new_line_size;
322 int search_match_count;
323
324 /* Calculate size of new line */
Chris Allegretta805c26d2000-09-06 13:39:17 +0000325#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000326 if (ISSET(USE_REGEXP)) {
327 search_match_count = regmatches[0].rm_eo -
328 regmatches[0].rm_so;
329 new_line_size = replace_regexp(NULL, 0);
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000330 /* If they specified an invalid subexpression in the replace
331 * text, return NULL indicating an error */
332 if (new_line_size < 0)
333 return NULL;
334 } else {
Chris Allegretta47805612000-07-07 02:35:34 +0000335#else
336 {
337#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000338 search_match_count = strlen(last_search);
339 new_line_size = strlen(current->data) - strlen(last_search) +
340 strlen(last_replace) + 1;
341 }
342
343 /* Create buffer */
344 copy = nmalloc(new_line_size);
345
346 /* Head of Original Line */
347 strncpy(copy, current->data, current_x);
348 copy[current_x] = 0;
349
350 /* Replacement Text */
351 if (!ISSET(USE_REGEXP))
352 strcat(copy, last_replace);
Chris Allegretta805c26d2000-09-06 13:39:17 +0000353#ifdef HAVE_REGEX_H
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000354 else
355 (void)replace_regexp(copy + current_x, 1);
Chris Allegretta47805612000-07-07 02:35:34 +0000356#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000357
358 /* The tail of the original line */
359 /* This may expose other bugs, because it no longer
360 goes through each character on the string
361 and tests for string goodness. But because
362 we can assume the invariant that current->data
363 is less than current_x + strlen(last_search) long,
364 this should be safe. Or it will expose bugs ;-) */
365 tmp = current->data + current_x + search_match_count;
366 strcat(copy, tmp);
367
368 return copy;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000369}
370
Chris Allegretta47805612000-07-07 02:35:34 +0000371/* Replace a string */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000372int do_replace(void)
373{
374 int i, replaceall = 0, numreplaced = 0, beginx;
375 filestruct *fileptr, *begin;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000376 char *copy, prevanswer[132] = "";
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000377
378 if ((i = search_init(1)) == -1) {
379 statusbar(_("Replace Cancelled"));
380 replace_abort();
381 return 0;
382 } else if (i == 1) {
383 do_replace();
384 return 1;
385 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000386 do_search();
387 return 0;
388 } else if (i == -3) {
389 replace_abort();
390 return 0;
391 }
392 strncpy(prevanswer, answer, 132);
393
394 if (strcmp(last_replace, "")) { /* There's a previous replace str */
395 i = statusq(replace_list, REPLACE_LIST_LEN, "",
396 _("Replace with [%s]"), last_replace);
397
398 if (i == -1) { /* Aborted enter */
399 strncpy(answer, last_replace, 132);
400 statusbar(_("Replace Cancelled"));
401 replace_abort();
402 return 0;
403 } else if (i == 0) /* They actually entered something */
404 strncpy(last_replace, answer, 132);
405 else if (i == NANO_CASE_KEY) { /* They asked for case sensitivity */
406 if (ISSET(CASE_SENSITIVE))
407 UNSET(CASE_SENSITIVE);
408 else
409 SET(CASE_SENSITIVE);
410
411 do_replace();
412 return 0;
Chris Allegrettaf6b13422000-07-07 04:25:00 +0000413 } else if (i != -2 ) { /* First page, last page, for example could get here */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000414
415 do_early_abort();
416 replace_abort();
417 return 0;
418 }
419 } else { /* last_search is empty */
420
421 i = statusq(replace_list, REPLACE_LIST_LEN, "", _("Replace with"));
422 if (i == -1) {
423 statusbar(_("Replace Cancelled"));
424 reset_cursor();
425 replace_abort();
426 return 0;
427 } else if (i == 0) /* They entered something new */
428 strncpy(last_replace, answer, 132);
429 else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
430 if (ISSET(CASE_SENSITIVE))
431 UNSET(CASE_SENSITIVE);
432 else
433 SET(CASE_SENSITIVE);
434
435 do_replace();
436 return 1;
437 } else { /* First line key, etc. */
438
439 do_early_abort();
440 replace_abort();
441 return 0;
442 }
443 }
444
445 /* save where we are */
446 begin = current;
447 beginx = current_x;
448
449 while (1) {
450
451 if (replaceall)
452 fileptr = findnextstr(1, begin, prevanswer);
453 else
454 fileptr = findnextstr(0, begin, prevanswer);
455
456 /* No more matches. Done! */
457 if (!fileptr)
458 break;
459
460 /* If we're here, we've found the search string */
461 if (!replaceall)
462 i = do_yesno(1, 1, _("Replace this instance?"));
463
464 if (i > 0 || replaceall) { /* Yes, replace it!!!! */
465 if (i == 2)
466 replaceall = 1;
467
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000468 copy = replace_line();
469 if (!copy) {
470 statusbar("Replace failed: unknown subexpression!");
471 replace_abort();
Chris Allegretta68b12332000-07-07 13:10:28 +0000472 return 0;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000473 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000474
475 /* Cleanup */
476 free(current->data);
477 current->data = copy;
478
479 /* Stop bug where we replace a substring of the replacement text */
Chris Allegrettafa8f7bf2000-07-12 02:41:13 +0000480 current_x += strlen(last_replace) - 1;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000481
482 edit_refresh();
483 set_modified();
484 numreplaced++;
485 } else if (i == -1) /* Abort, else do nothing and continue loop */
486 break;
487 }
488
489 current = begin;
490 current_x = beginx;
491 renumber_all();
Chris Allegretta234a34d2000-07-29 04:33:38 +0000492 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000493 print_replaced(numreplaced);
494 replace_abort();
495 return 1;
496}
497
498void goto_abort(void)
499{
500 UNSET(KEEP_CUTBUFFER);
501 display_main_list();
502}
503
504int do_gotoline(long defline)
505{
506 long line, i = 1, j = 0;
507 filestruct *fileptr;
508
509 if (defline > 0) /* We already know what line we want to go to */
510 line = defline;
511 else { /* Ask for it */
512
513 j = statusq(goto_list, GOTO_LIST_LEN, "", _("Enter line number"));
514 if (j == -1) {
515 statusbar(_("Aborted"));
516 goto_abort();
517 return 0;
518 } else if (j != 0) {
519 do_early_abort();
520 goto_abort();
521 return 0;
522 }
523 if (!strcmp(answer, "$")) {
524 current = filebot;
525 current_x = 0;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000526 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000527 goto_abort();
528 return 1;
529 }
530 line = atoi(answer);
531 }
532
533 /* Bounds check */
534 if (line <= 0) {
535 statusbar(_("Come on, be reasonable"));
536 goto_abort();
537 return 0;
538 }
539 if (line > totlines) {
540 statusbar(_("Only %d lines available, skipping to last line"),
541 filebot->lineno);
542 current = filebot;
543 current_x = 0;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000544 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000545 } else {
546 for (fileptr = fileage; fileptr != NULL && i < line; i++)
547 fileptr = fileptr->next;
548
549 current = fileptr;
550 current_x = 0;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000551 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000552 }
553
554 goto_abort();
555 return 1;
556}
557
558int do_gotoline_void(void)
559{
560 return do_gotoline(0);
561}