blob: 8667e2196448b3363f31d9a4006418f520c0d552 [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 Allegretta9fc8d432000-07-07 01:49:52 +000037/* Regular expression helper functions */
38
Chris Allegretta47805612000-07-07 02:35:34 +000039#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +000040void regexp_init(const char *regexp)
41{
42 regcomp(&search_regexp, regexp, ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE);
43 SET(REGEXP_COMPILED);
44}
45
46void regexp_cleanup()
47{
48 UNSET(REGEXP_COMPILED);
49 regfree(&search_regexp);
50}
Chris Allegretta47805612000-07-07 02:35:34 +000051#endif
Chris Allegretta9fc8d432000-07-07 01:49:52 +000052
Chris Allegrettabceb1b22000-06-19 04:22:15 +000053/* Set up the system variables for a search or replace. Returns -1 on
54 abort, 0 on success, and 1 on rerun calling program
55 Return -2 to run opposite program (searchg -> replace, replace -> search)
56
57 replacing = 1 if we call from do_replace, 0 if called from do_search func.
58*/
59int search_init(int replacing)
60{
61 int i;
Robert Siemborski6af14312000-07-01 21:34:26 +000062 char buf[BUFSIZ];
Chris Allegrettaa4d21622000-07-08 23:57:03 +000063 char *prompt, *reprompt = "";
Chris Allegretta9fc8d432000-07-07 01:49:52 +000064
Chris Allegrettabceb1b22000-06-19 04:22:15 +000065 if (last_search[0]) {
Robert Siemborski6af14312000-07-01 21:34:26 +000066 snprintf(buf, BUFSIZ, " [%s]", last_search);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000067 } else {
68 buf[0] = '\0';
69 }
70
Chris Allegretta9fc8d432000-07-07 01:49:52 +000071 if (ISSET(USE_REGEXP) && ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000072 prompt = _("Case Sensitive Regexp Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000073 else if (ISSET(USE_REGEXP))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000074 prompt = _("Regexp Search%s%s");
Chris Allegretta47805612000-07-07 02:35:34 +000075 else
76 if (ISSET(CASE_SENSITIVE))
Chris Allegrettaa4d21622000-07-08 23:57:03 +000077 prompt = _("Case Sensitive Search%s%s");
Chris Allegretta9fc8d432000-07-07 01:49:52 +000078 else
Chris Allegrettaa4d21622000-07-08 23:57:03 +000079 prompt = _("Search%s%s");
80
81 if (replacing)
82 reprompt = _(" (to replace)");
83
Chris Allegrettabceb1b22000-06-19 04:22:15 +000084 i = statusq(replacing ? replace_list : whereis_list,
85 replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, "",
Chris Allegrettaa4d21622000-07-08 23:57:03 +000086 prompt, reprompt, buf);
Chris Allegrettabceb1b22000-06-19 04:22:15 +000087
88 /* Cancel any search, or just return with no previous search */
89 if ((i == -1) || (i < 0 && !last_search[0])) {
90 statusbar(_("Search Cancelled"));
91 reset_cursor();
92 return -1;
93 } else if (i == -2) { /* Same string */
94 strncpy(answer, last_search, 132);
Chris Allegretta47805612000-07-07 02:35:34 +000095#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +000096 if (ISSET(USE_REGEXP))
97 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +000098#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +000099 } else if (i == 0) { /* They entered something new */
100 strncpy(last_search, answer, 132);
Chris Allegretta47805612000-07-07 02:35:34 +0000101#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000102 if (ISSET(USE_REGEXP))
103 regexp_init(answer);
Chris Allegretta47805612000-07-07 02:35:34 +0000104#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000105 /* Blow away last_replace because they entered a new search
106 string....uh, right? =) */
107 last_replace[0] = '\0';
108 } else if (i == NANO_CASE_KEY) { /* They want it case sensitive */
109 if (ISSET(CASE_SENSITIVE))
110 UNSET(CASE_SENSITIVE);
111 else
112 SET(CASE_SENSITIVE);
113
114 return 1;
115 } else if (i == NANO_OTHERSEARCH_KEY) {
116 return -2; /* Call the opposite search function */
Chris Allegretta8c2b40f2000-06-29 01:30:04 +0000117 } else if (i == NANO_FROMSEARCHTOGOTO_KEY) {
118 do_gotoline_void();
119 return -3;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000120 } else { /* First line key, etc. */
121 do_early_abort();
122 return -3;
123 }
124
125 return 0;
126}
127
128filestruct *findnextstr(int quiet, filestruct * begin, char *needle)
129{
130 filestruct *fileptr;
131 char *searchstr, *found = NULL, *tmp;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000132 int past_editbot = 0;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000133
134 fileptr = current;
135
136 searchstr = &current->data[current_x + 1];
137 /* Look for searchstr until EOF */
138 while (fileptr != NULL &&
139 (found = strstrwrapper(searchstr, needle)) == NULL) {
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000140
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000141 fileptr = fileptr->next;
142
Adam Rogoyskia966d992000-07-09 18:42:53 +0000143 if (!past_editbot && (fileptr == editbot))
144 past_editbot = 1;
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000145
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000146 if (fileptr == begin)
147 return NULL;
148
149 if (fileptr != NULL)
150 searchstr = fileptr->data;
151 }
152
153 /* If we're not at EOF, we found an instance */
154 if (fileptr != NULL) {
155 current = fileptr;
156 current_x = 0;
157 for (tmp = fileptr->data; tmp != found; tmp++)
158 current_x++;
159
Adam Rogoyski2a4ef922000-07-09 00:15:11 +0000160 if (past_editbot)
Chris Allegretta234a34d2000-07-29 04:33:38 +0000161 edit_update(current, CENTER);
Chris Allegrettae10debd2000-08-22 01:26:42 +0000162 placewewant = xplustabs();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000163 reset_cursor();
164 } else { /* We're at EOF, go back to the top, once */
165
166 fileptr = fileage;
167
168 while (fileptr != current && fileptr != begin &&
169 (found = strstrwrapper(fileptr->data, needle)) == NULL)
170 fileptr = fileptr->next;
171
172 if (fileptr == begin) {
173 if (!quiet)
174 statusbar(_("\"%s\" not found"), needle);
175
176 return NULL;
177 }
178 if (fileptr != current) { /* We found something */
179 current = fileptr;
180 current_x = 0;
181 for (tmp = fileptr->data; tmp != found; tmp++)
182 current_x++;
183
Chris Allegretta234a34d2000-07-29 04:33:38 +0000184 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000185 reset_cursor();
186
187 if (!quiet)
188 statusbar(_("Search Wrapped"));
189 } else { /* Nada */
190
191 if (!quiet)
192 statusbar(_("\"%s\" not found"), needle);
193 return NULL;
194 }
195 }
196
197 return fileptr;
198}
199
200void search_abort(void)
201{
202 UNSET(KEEP_CUTBUFFER);
203 display_main_list();
204 wrefresh(bottomwin);
Chris Allegrettaf1d33d32000-08-19 03:53:39 +0000205 if (ISSET(MARK_ISSET))
206 edit_refresh_clearok();
Chris Allegretta47805612000-07-07 02:35:34 +0000207
208#ifdef _POSIX_VERSION
Chris Allegretta9fc8d432000-07-07 01:49:52 +0000209 if (ISSET(REGEXP_COMPILED))
210 regexp_cleanup();
Chris Allegretta47805612000-07-07 02:35:34 +0000211#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000212}
213
214/* Search for a string */
215int do_search(void)
216{
217 int i;
218 filestruct *fileptr = current;
219
220 wrap_reset();
221 if ((i = search_init(0)) == -1) {
222 current = fileptr;
223 search_abort();
224 return 0;
225 } else if (i == -3) {
226 search_abort();
227 return 0;
228 } else if (i == -2) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000229 do_replace();
230 return 0;
231 } else if (i == 1) {
232 do_search();
233 search_abort();
234 return 1;
235 }
236 findnextstr(0, current, answer);
237 search_abort();
238 return 1;
239}
240
241void print_replaced(int num)
242{
243 if (num > 1)
244 statusbar(_("Replaced %d occurences"), num);
245 else if (num == 1)
246 statusbar(_("Replaced 1 occurence"));
247}
248
249void replace_abort(void)
250{
Chris Allegretta18bd0292000-07-28 01:18:10 +0000251 /* Identicle to search_abort, so we'll call it here. If it
252 does something different later, we can change it back. For now
253 it's just a waste to duplicat code */
254 search_abort();
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();
Chris Allegretta234a34d2000-07-29 04:33:38 +0000494 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000495 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;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000528 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000529 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;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000546 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000547 } else {
548 for (fileptr = fileage; fileptr != NULL && i < line; i++)
549 fileptr = fileptr->next;
550
551 current = fileptr;
552 current_x = 0;
Chris Allegretta234a34d2000-07-29 04:33:38 +0000553 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000554 }
555
556 goto_abort();
557 return 1;
558}
559
560int do_gotoline_void(void)
561{
562 return do_gotoline(0);
563}