blob: adad3208c447017907a01ef5ca4bd0ede62f3f8c [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{
Chris Allegretta18bd0292000-07-28 01:18:10 +0000247 /* Identicle to search_abort, so we'll call it here. If it
248 does something different later, we can change it back. For now
249 it's just a waste to duplicat code */
250 search_abort();
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000251}
252
Chris Allegretta47805612000-07-07 02:35:34 +0000253#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000254int replace_regexp(char *string, int create_flag)
255{
256 /* split personality here - if create_flag is null, just calculate
257 * the size of the replacement line (necessary because of
258 * subexpressions like \1 \2 \3 in the replaced text) */
259
260 char *c;
261 int new_size = strlen(current->data) + 1;
262 int search_match_count = regmatches[0].rm_eo -
263 regmatches[0].rm_so;
264
265 new_size -= search_match_count;
266
267 /* Iterate through the replacement text to handle
268 * subexpression replacement using \1, \2, \3, etc */
269
270 c = last_replace;
271 while (*c) {
272 if (*c != '\\') {
273 if (create_flag)
274 *string++=*c;
275 c++;
276 new_size++;
277 } else {
278 int num = (int)*(c+1) - (int)'0';
279 if (num >= 1 && num <= 9) {
280
281 int i = regmatches[num].rm_so;
282
283 if (num > search_regexp.re_nsub) {
284 /* Ugh, they specified a subexpression that doesn't
285 exist. */
286 return -1;
287 }
288
289 /* Skip over the replacement expression */
290 c+=2;
291
292 /* But add the length of the subexpression to new_size */
293 new_size += regmatches[num].rm_eo - regmatches[num].rm_so;
294
295 /* And if create_flag is set, append the result of the
296 * subexpression match to the new line */
297 while (create_flag && i < regmatches[num].rm_eo )
298 *string++=*(current->data + i++);
299
300 } else {
301 if (create_flag)
302 *string++=*c;
303 c++;
304 new_size++;
305 }
306 }
307 }
308
309 if (create_flag)
310 *string = 0;
311
312 return new_size;
313}
Chris Allegretta47805612000-07-07 02:35:34 +0000314#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000315
316char *replace_line()
317{
318 char *copy, *tmp;
319 int new_line_size;
320 int search_match_count;
321
322 /* Calculate size of new line */
Chris Allegretta47805612000-07-07 02:35:34 +0000323#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000324 if (ISSET(USE_REGEXP)) {
325 search_match_count = regmatches[0].rm_eo -
326 regmatches[0].rm_so;
327 new_line_size = replace_regexp(NULL, 0);
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000328 /* If they specified an invalid subexpression in the replace
329 * text, return NULL indicating an error */
330 if (new_line_size < 0)
331 return NULL;
332 } else {
Chris Allegretta47805612000-07-07 02:35:34 +0000333#else
334 {
335#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000336 search_match_count = strlen(last_search);
337 new_line_size = strlen(current->data) - strlen(last_search) +
338 strlen(last_replace) + 1;
339 }
340
341 /* Create buffer */
342 copy = nmalloc(new_line_size);
343
344 /* Head of Original Line */
345 strncpy(copy, current->data, current_x);
346 copy[current_x] = 0;
347
348 /* Replacement Text */
349 if (!ISSET(USE_REGEXP))
350 strcat(copy, last_replace);
Chris Allegretta47805612000-07-07 02:35:34 +0000351#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000352 else
353 (void)replace_regexp(copy + current_x, 1);
Chris Allegretta47805612000-07-07 02:35:34 +0000354#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000355
356 /* The tail of the original line */
357 /* This may expose other bugs, because it no longer
358 goes through each character on the string
359 and tests for string goodness. But because
360 we can assume the invariant that current->data
361 is less than current_x + strlen(last_search) long,
362 this should be safe. Or it will expose bugs ;-) */
363 tmp = current->data + current_x + search_match_count;
364 strcat(copy, tmp);
365
366 return copy;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000367}
368
Chris Allegretta47805612000-07-07 02:35:34 +0000369/* Replace a string */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000370int do_replace(void)
371{
372 int i, replaceall = 0, numreplaced = 0, beginx;
373 filestruct *fileptr, *begin;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000374 char *copy, prevanswer[132] = "";
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000375
376 if ((i = search_init(1)) == -1) {
377 statusbar(_("Replace Cancelled"));
378 replace_abort();
379 return 0;
380 } else if (i == 1) {
381 do_replace();
382 return 1;
383 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000384 do_search();
385 return 0;
386 } else if (i == -3) {
387 replace_abort();
388 return 0;
389 }
390 strncpy(prevanswer, answer, 132);
391
392 if (strcmp(last_replace, "")) { /* There's a previous replace str */
393 i = statusq(replace_list, REPLACE_LIST_LEN, "",
394 _("Replace with [%s]"), last_replace);
395
396 if (i == -1) { /* Aborted enter */
397 strncpy(answer, last_replace, 132);
398 statusbar(_("Replace Cancelled"));
399 replace_abort();
400 return 0;
401 } else if (i == 0) /* They actually entered something */
402 strncpy(last_replace, answer, 132);
403 else if (i == NANO_CASE_KEY) { /* They asked for case sensitivity */
404 if (ISSET(CASE_SENSITIVE))
405 UNSET(CASE_SENSITIVE);
406 else
407 SET(CASE_SENSITIVE);
408
409 do_replace();
410 return 0;
Chris Allegrettaf6b13422000-07-07 04:25:00 +0000411 } else if (i != -2 ) { /* First page, last page, for example could get here */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000412
413 do_early_abort();
414 replace_abort();
415 return 0;
416 }
417 } else { /* last_search is empty */
418
419 i = statusq(replace_list, REPLACE_LIST_LEN, "", _("Replace with"));
420 if (i == -1) {
421 statusbar(_("Replace Cancelled"));
422 reset_cursor();
423 replace_abort();
424 return 0;
425 } else if (i == 0) /* They entered something new */
426 strncpy(last_replace, answer, 132);
427 else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
428 if (ISSET(CASE_SENSITIVE))
429 UNSET(CASE_SENSITIVE);
430 else
431 SET(CASE_SENSITIVE);
432
433 do_replace();
434 return 1;
435 } else { /* First line key, etc. */
436
437 do_early_abort();
438 replace_abort();
439 return 0;
440 }
441 }
442
443 /* save where we are */
444 begin = current;
445 beginx = current_x;
446
447 while (1) {
448
449 if (replaceall)
450 fileptr = findnextstr(1, begin, prevanswer);
451 else
452 fileptr = findnextstr(0, begin, prevanswer);
453
454 /* No more matches. Done! */
455 if (!fileptr)
456 break;
457
458 /* If we're here, we've found the search string */
459 if (!replaceall)
460 i = do_yesno(1, 1, _("Replace this instance?"));
461
462 if (i > 0 || replaceall) { /* Yes, replace it!!!! */
463 if (i == 2)
464 replaceall = 1;
465
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000466 copy = replace_line();
467 if (!copy) {
468 statusbar("Replace failed: unknown subexpression!");
469 replace_abort();
Chris Allegretta68b12332000-07-07 13:10:28 +0000470 return 0;
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000471 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000472
473 /* Cleanup */
474 free(current->data);
475 current->data = copy;
476
477 /* Stop bug where we replace a substring of the replacement text */
Chris Allegrettafa8f7bf2000-07-12 02:41:13 +0000478 current_x += strlen(last_replace) - 1;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000479
480 edit_refresh();
481 set_modified();
482 numreplaced++;
483 } else if (i == -1) /* Abort, else do nothing and continue loop */
484 break;
485 }
486
487 current = begin;
488 current_x = beginx;
489 renumber_all();
490 edit_update(current);
491 print_replaced(numreplaced);
492 replace_abort();
493 return 1;
494}
495
496void goto_abort(void)
497{
498 UNSET(KEEP_CUTBUFFER);
499 display_main_list();
500}
501
502int do_gotoline(long defline)
503{
504 long line, i = 1, j = 0;
505 filestruct *fileptr;
506
507 if (defline > 0) /* We already know what line we want to go to */
508 line = defline;
509 else { /* Ask for it */
510
511 j = statusq(goto_list, GOTO_LIST_LEN, "", _("Enter line number"));
512 if (j == -1) {
513 statusbar(_("Aborted"));
514 goto_abort();
515 return 0;
516 } else if (j != 0) {
517 do_early_abort();
518 goto_abort();
519 return 0;
520 }
521 if (!strcmp(answer, "$")) {
522 current = filebot;
523 current_x = 0;
524 edit_update(current);
525 goto_abort();
526 return 1;
527 }
528 line = atoi(answer);
529 }
530
531 /* Bounds check */
532 if (line <= 0) {
533 statusbar(_("Come on, be reasonable"));
534 goto_abort();
535 return 0;
536 }
537 if (line > totlines) {
538 statusbar(_("Only %d lines available, skipping to last line"),
539 filebot->lineno);
540 current = filebot;
541 current_x = 0;
542 edit_update(current);
543 } else {
544 for (fileptr = fileage; fileptr != NULL && i < line; i++)
545 fileptr = fileptr->next;
546
547 current = fileptr;
548 current_x = 0;
549 edit_update(current);
550 }
551
552 goto_abort();
553 return 1;
554}
555
556int do_gotoline_void(void)
557{
558 return do_gotoline(0);
559}