search.c 30.7 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   search.c                                                             *
 *                                                                        *
5
 *   Copyright (C) 2000-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
27
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <ctype.h>
29
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
32
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
33

34
#ifdef HAVE_REGEX_H
35
36
static bool regexp_compiled = FALSE;
	/* Have we compiled any regular expressions? */
37
38
39
40
41
42

/* Regular expression helper functions. */

/* Compile the given regular expression.  Return value 0 means the
 * expression was invalid, and we wrote an error message on the status
 * bar.  Return value 1 means success. */
43
int regexp_init(const char *regexp)
44
{
45
46
47
48
49
    int rc = regcomp(&search_regexp, regexp, REG_EXTENDED
#ifndef NANO_SMALL
	| (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE)
#endif
	);
50
51
52
53
54
55
56
57
58

    assert(!regexp_compiled);
    if (rc != 0) {
	size_t len = regerror(rc, &search_regexp, NULL, 0);
	char *str = charalloc(len);

	regerror(rc, &search_regexp, str, len);
	statusbar(_("Bad regex \"%s\": %s"), regexp, str);
	free(str);
59
	return 0;
60
    }
61

62
    regexp_compiled = TRUE;
63
    return 1;
64
65
}

66
void regexp_cleanup(void)
67
{
68
69
    if (regexp_compiled) {
	regexp_compiled = FALSE;
70
71
	regfree(&search_regexp);
    }
72
}
73
#endif
74

75
76
void not_found_msg(const char *str)
{
77
78
79
    char *disp;
    int numchars;
 
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
80
    assert(str != NULL);
81
82
83
84
85
86
87
88

    disp = display_string(str, 0, (COLS / 2) + 1);
    numchars = strnlen(disp, COLS / 2);

    statusbar(_("\"%.*s%s\" not found"), numchars, disp,
	disp[numchars] == '\0' ? "" : "...");

    free(disp);
89
90
91
92
93
}

void search_abort(void)
{
    display_main_list();
94
#ifndef NANO_SMALL
95
    if (ISSET(MARK_ISSET))
96
97
	edit_refresh();
#endif
98
#ifdef HAVE_REGEX_H
99
    regexp_cleanup();
100
101
102
#endif
}

Chris Allegretta's avatar
Chris Allegretta committed
103
104
void search_init_globals(void)
{
105
106
107
108
    if (last_search == NULL)
	last_search = mallocstrcpy(NULL, "");
    if (last_replace == NULL)
	last_replace = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
109
110
}

111
112
113
114
115
116
/* Set up the system variables for a search or replace.  If use_answer
 * is TRUE, only set backupstring to answer.  Return -2 to run opposite
 * program (search -> replace, replace -> search), return -1 if the
 * search should be canceled (due to Cancel, Go to Line, or a failed
 * regcomp()), return 0 on success, and return 1 on rerun calling
 * program.
Chris Allegretta's avatar
Chris Allegretta committed
117
 *
118
119
120
 * replacing is TRUE if we call from do_replace(), and FALSE if called
 * from do_search(). */
int search_init(bool replacing, bool use_answer)
Chris Allegretta's avatar
Chris Allegretta committed
121
{
122
    int i = 0;
123
    char *buf;
124
    static char *backupstring = NULL;
125
126
127
128
129
130
131
132
133
134
135
	/* The search string we'll be using. */

    /* If backupstring doesn't exist, initialize it to "". */
    if (backupstring == NULL)
	backupstring = mallocstrcpy(NULL, "");

    /* If use_answer is TRUE, set backupstring to answer and get out. */
    if (use_answer) {
	backupstring = mallocstrcpy(backupstring, answer);
	return 0;
    }
136
137
138
139

    /* We display the search prompt below.  If the user types a partial
     * search string and then Replace or a toggle, we will return to
     * do_search() or do_replace() and be called again.  In that case,
140
     * we should put the same search string back up. */
141

Chris Allegretta's avatar
Chris Allegretta committed
142
    search_init_globals();
143

144
#ifndef NANO_SMALL
145
    search_history.current = (historytype *)&search_history.next;
146
#endif
147
148

    if (last_search[0] != '\0') {
149
150
	char *disp = display_string(last_search, 0, COLS / 3);

Chris Allegretta's avatar
Chris Allegretta committed
151
	buf = charalloc(COLS / 3 + 7);
152
153
	/* We use COLS / 3 here because we need to see more on the
	 * line. */
154
155
156
	sprintf(buf, " [%s%s]", disp,
		strlenpt(last_search) > COLS / 3 ? "..." : "");
	free(disp);
157
158
    } else
	buf = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
159

160
    /* This is now one simple call.  It just does a lot. */
161
162
    i = statusq(FALSE, replacing ? replace_list : whereis_list,
	backupstring,
163
164
165
#ifndef NANO_SMALL
	&search_history,
#endif
166
	"%s%s%s%s%s%s", _("Search"),
167

168
#ifndef NANO_SMALL
169
170
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
171
172
173
	ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
#endif
		"",
174

175
#ifdef HAVE_REGEX_H
176
177
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
178
179
180
	ISSET(USE_REGEXP) ? _(" [Regexp]") :
#endif
		"",
181

182
#ifndef NANO_SMALL
183
184
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
185
186
187
	ISSET(REVERSE_SEARCH) ? _(" [Backwards]") :
#endif
		"",
188

Chris Allegretta's avatar
Chris Allegretta committed
189
190
	replacing ? _(" (to replace)") : "",
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
191

192
    /* Release buf now that we don't need it anymore. */
Chris Allegretta's avatar
Chris Allegretta committed
193
194
    free(buf);

195
196
197
    free(backupstring);
    backupstring = NULL;

198
199
200
    /* Cancel any search, or just return with no previous search. */
    if (i == -1 || (i < 0 && last_search[0] == '\0') ||
	    (!replacing && i == 0 && answer[0] == '\0')) {
201
	statusbar(_("Cancelled"));
202
203
204
#ifndef NANO_SMALL
	search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
205
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
206
207
    } else {
	switch (i) {
208
	    case -2:		/* It's the same string. */
209
#ifdef HAVE_REGEX_H
210
211
212
		/* Since answer is "", use last_search! */
		if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
		    return -1;
213
#endif
214
215
216
		break;
	    case 0:		/* They entered something new. */
		last_replace[0] = '\0';
217
#ifdef HAVE_REGEX_H
218
219
		if (ISSET(USE_REGEXP) && regexp_init(answer) == 0)
		    return -1;
220
#endif
221
		break;
Chris Allegretta's avatar
Chris Allegretta committed
222
#ifndef NANO_SMALL
223
224
225
226
227
228
229
230
	    case TOGGLE_CASE_KEY:
		TOGGLE(CASE_SENSITIVE);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	    case TOGGLE_BACKWARDS_KEY:
		TOGGLE(REVERSE_SEARCH);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
231
#ifdef HAVE_REGEX_H
232
233
234
235
	    case TOGGLE_REGEXP_KEY:
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
236
#endif
Chris Allegretta's avatar
Chris Allegretta committed
237
#endif /* !NANO_SMALL */
238
239
240
241
	    case NANO_TOOTHERSEARCH_KEY:
		backupstring = mallocstrcpy(backupstring, answer);
		return -2;	/* Call the opposite search function. */
	    case NANO_TOGOTOLINE_KEY:
242
#ifndef NANO_SMALL
243
		search_history.current = search_history.next;
244
#endif
245
246
247
248
249
		/* Put answer up on the statusbar. */
		do_gotoline(-1, FALSE);
		/* Fall through. */
	    default:
		return -1;
Chris Allegretta's avatar
Chris Allegretta committed
250
	}
Chris Allegretta's avatar
Chris Allegretta committed
251
252
253
254
    }
    return 0;
}

255
bool is_whole_word(int curr_pos, const char *datastr, const char
256
	*searchword)
257
{
Chris Allegretta's avatar
Chris Allegretta committed
258
    size_t sln = curr_pos + strlen(searchword);
259

260
261
    /* Start of line or previous character is not a letter and end of
     * line or next character is not a letter. */
262
263
    return (curr_pos < 1 || !isalpha(datastr[curr_pos - 1])) &&
	(sln == strlen(datastr) || !isalpha(datastr[sln]));
264
265
}

266
/* Look for needle, starting at current, column current_x.  If
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
267
268
 * no_sameline is TRUE, skip over begin when looking for needle.  begin
 * is the line where we first started searching, at column beginx.  If
269
 * can_display_wrap is TRUE, we put messages on the statusbar, wrap
270
271
272
 * around the file boundaries.  The return value specifies whether we
 * found anything.  If we did, set needle_len to the length of the
 * string we found if it isn't NULL. */
273
274
bool findnextstr(bool can_display_wrap, bool wholeword, bool
	no_sameline, const filestruct *begin, size_t beginx, const char
275
	*needle, size_t *needle_len)
Chris Allegretta's avatar
Chris Allegretta committed
276
{
Chris Allegretta's avatar
Chris Allegretta committed
277
    filestruct *fileptr = current;
278
    const char *rev_start = NULL, *found = NULL;
279
280
    size_t found_len;
	/* The length of the match we found. */
281
    size_t current_x_find = 0;
282
	/* The location of the match we found. */
283
    int current_y_find = current_y;
284
285
    bool search_last_line = FALSE;
	/* Have we gone past the last line while searching? */
286
287
288
289
290
291

    /* rev_start might end up 1 character before the start or after the
     * end of the line.  This won't be a problem because strstrwrapper()
     * will return immediately and say that no match was found, and
     * rev_start will be properly set when the search continues on the
     * previous or next line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
292
    rev_start =
293
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
294
	ISSET(REVERSE_SEARCH) ? fileptr->data + (current_x - 1) :
295
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
296
	fileptr->data + (current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
297

298
    /* Look for needle in searchstr. */
299
    while (TRUE) {
300
	found = strstrwrapper(fileptr->data, needle, rev_start);
Chris Allegretta's avatar
Chris Allegretta committed
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
	/* We've found a potential match. */
	if (found != NULL) {
	    bool found_whole = FALSE;
		/* Is this potential match a whole word? */

	    /* Set found_len to the length of the potential match. */
	    found_len =
#ifdef HAVE_REGEX_H
		ISSET(USE_REGEXP) ?
		regmatches[0].rm_eo - regmatches[0].rm_so :
#endif
		strlen(needle);

	    /* If we're searching for whole words, see if this potential
	     * match is a whole word. */
	    if (wholeword) {
		char *word = charalloc(found_len + 1);
		strncpy(word, found, found_len);
		word[found_len] = '\0';

		found_whole = is_whole_word(found - fileptr->data,
			fileptr->data, word);
		free(word);
	    }

	    /* If we're searching for whole words and this potential
	     * match isn't a whole word, or if we're not allowed to find
	     * a match on the same line we started on and this potential
	     * match is on that line, continue searching. */
	    if ((!wholeword || found_whole) && (!no_sameline ||
		fileptr != current))
333
		break;
Chris Allegretta's avatar
Chris Allegretta committed
334
	}
335

336
	/* We've finished processing the file, so get out. */
337
338
	if (search_last_line) {
	    if (can_display_wrap)
Chris Allegretta's avatar
Chris Allegretta committed
339
		not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
340
	    return FALSE;
341
	}
342
343
344
345
346
347
348
349
350

#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH)) {
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
351
#ifndef NANO_SMALL
352
	}
353
#endif
354

355
	/* Start or end of buffer reached, so wrap around. */
356
357
	if (fileptr == NULL) {
	    if (!can_display_wrap)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
358
		return FALSE;
359

360
#ifndef NANO_SMALL
361
362
363
364
	    if (ISSET(REVERSE_SEARCH)) {
		fileptr = filebot;
		current_y_find = editwinrows - 1;
	    } else {
365
#endif
366
367
368
369
370
371
		fileptr = fileage;
		current_y_find = 0;
#ifndef NANO_SMALL
	    }
#endif

372
373
374
	    if (can_display_wrap)
		statusbar(_("Search Wrapped"));
	}
Chris Allegretta's avatar
Chris Allegretta committed
375

376
	/* Original start line reached. */
377
	if (fileptr == begin)
378
	    search_last_line = TRUE;
379

380
381
382
383
384
385
	rev_start = fileptr->data;
#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH))
	    rev_start += strlen(fileptr->data);
#endif
    }
386

387
388
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
389

390
391
392
393
394
395
396
397
398
    /* Ensure we haven't wrapped around again! */
    if (search_last_line &&
#ifndef NANO_SMALL
	((!ISSET(REVERSE_SEARCH) && current_x_find > beginx) ||
	(ISSET(REVERSE_SEARCH) && current_x_find < beginx))
#else
	current_x_find > beginx
#endif
	) {
Chris Allegretta's avatar
Chris Allegretta committed
399

400
401
	if (can_display_wrap)
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
402
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
403
404
    }

405
    /* Set globals now that we are sure we found something. */
406
407
    current = fileptr;
    current_x = current_x_find;
408
    current_y = current_y_find;
409
    placewewant = xplustabs();
410
411

    /* needle_len holds the length of needle. */
412
413
    if (needle_len != NULL)
	*needle_len = found_len;
414

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
415
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
416
417
}

Chris Allegretta's avatar
Chris Allegretta committed
418
/* Search for a string. */
419
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
420
{
421
    size_t old_pww = placewewant, fileptr_x = current_x;
422
423
    int i;
    bool didfind;
424
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
425

426
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
427
    wrap_reset();
428
#endif
429

430
    i = search_init(FALSE, FALSE);
431
432
    if (i == -1)	/* Cancel, Go to Line, blank search string, or
			 * regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
433
	search_abort();
434
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
435
	do_replace();
436
437
438
#ifndef NANO_SMALL
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
439
	do_search();
440
#endif
441

442
    if (i != 0)
443
	return;
444
445

    /* If answer is now "", copy last_search into answer. */
Chris Allegretta's avatar
Chris Allegretta committed
446
    if (answer[0] == '\0')
447
448
449
450
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

451
#ifndef NANO_SMALL
452
453
    /* If answer is not "", add this search string to the search history
     * list. */
Chris Allegretta's avatar
Chris Allegretta committed
454
    if (answer[0] != '\0')
455
	update_history(&search_history, answer);
456
#endif
457

458
    didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
459
	answer, NULL);
460

461
462
463
464
465
466
467
468
469
    /* Check to see if there's only one occurrence of the string and
     * we're on it now. */
    if (fileptr == current && fileptr_x == current_x && didfind) {
#ifdef HAVE_REGEX_H
	/* Do the search again, skipping over the current line, if we're
	 * doing a bol and/or eol regex search ("^", "$", or "^$"), so
	 * that we find one only once per line.  We should only end up
	 * back at the same position if the string isn't found again, in
	 * which case it's the only occurrence. */
470
471
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
472
	    didfind = findnextstr(TRUE, FALSE, TRUE, current, current_x,
473
		answer, NULL);
474
475
476
477
478
479
480
481
482
	    if (fileptr == current && fileptr_x == current_x && !didfind)
		statusbar(_("This is the only occurrence"));
	} else {
#endif
	    statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	}
#endif
    }
483

484
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
485
    edit_redraw(fileptr, old_pww);
Chris Allegretta's avatar
Chris Allegretta committed
486
487
488
    search_abort();
}

489
#ifndef NANO_SMALL
490
/* Search for the next string without prompting. */
491
void do_research(void)
492
{
493
    size_t old_pww = placewewant, fileptr_x = current_x;
494
    bool didfind;
495
    filestruct *fileptr = current;
496

497
#ifndef DISABLE_WRAPPING
498
    wrap_reset();
499
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
500

501
502
503
504
505
    search_init_globals();

    if (last_search[0] != '\0') {

#ifdef HAVE_REGEX_H
506
	/* Since answer is "", use last_search! */
507
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
508
	    return;
509
510
#endif

511
	didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
512
		last_search, NULL);
513

514
515
516
517
518
519
520
521
522
	/* Check to see if there's only one occurrence of the string and
	 * we're on it now. */
	if (fileptr == current && fileptr_x == current_x && didfind) {
#ifdef HAVE_REGEX_H
	    /* Do the search again, skipping over the current line, if
	     * we're doing a bol and/or eol regex search ("^", "$", or
	     * "^$"), so that we find one only once per line.  We should
	     * only end up back at the same position if the string isn't
	     * found again, in which case it's the only occurrence. */
523
524
	    if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
525
		didfind = findnextstr(TRUE, FALSE, TRUE, current,
526
			current_x, answer, NULL);
527
528
529
530
531
532
533
534
535
		if (fileptr == current && fileptr_x == current_x && !didfind)
		    statusbar(_("This is the only occurrence"));
	    } else {
#endif
		statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	    }
#endif
	}
536
537
538
    } else
        statusbar(_("No current search pattern"));

539
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
540
    edit_redraw(fileptr, old_pww);
541
542
    search_abort();
}
543
#endif
544

Chris Allegretta's avatar
Chris Allegretta committed
545
546
void replace_abort(void)
{
547
548
549
    /* Identical to search_abort(), so we'll call it here.  If it does
     * something different later, we can change it back.  For now, it's
     * just a waste to duplicate code. */
550
    search_abort();
551
    placewewant = xplustabs();
552
553
}

554
#ifdef HAVE_REGEX_H
555
int replace_regexp(char *string, bool create_flag)
556
{
557
    /* Split personality here - if create_flag is FALSE, just calculate
558
     * the size of the replacement line (necessary because of
559
     * subexpressions \1 to \9 in the replaced text). */
560

561
    const char *c = last_replace;
562
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
563
    int new_size = strlen(current->data) + 1 - search_match_count;
564

Chris Allegretta's avatar
Chris Allegretta committed
565
566
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
567
    while (*c != '\0') {
568
569
	int num = (int)(*(c + 1) - '0');

570
571
	if (*c != '\\' || num < 1 || num > 9 || num >
		search_regexp.re_nsub) {
572
573
574
575
576
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
577
	    int i = regmatches[num].rm_eo - regmatches[num].rm_so;
578

579
580
	    /* Skip over the replacement expression. */
	    c += 2;
581

582
583
	    /* But add the length of the subexpression to new_size. */
	    new_size += i;
584

585
	    /* And if create_flag is TRUE, append the result of the
586
587
588
589
590
	     * subexpression match to the new line. */
	    if (create_flag) {
		strncpy(string, current->data + current_x +
			regmatches[num].rm_so, i);
		string += i;
591
592
	    }
	}
593
594
595
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
596
	*string = '\0';
597
598
599

    return new_size;
}
600
#endif
601

602
char *replace_line(const char *needle)
603
{
604
    char *copy;
605
606
607
    int new_line_size;
    int search_match_count;

608
    /* Calculate the size of the new line. */
609
#ifdef HAVE_REGEX_H
610
    if (ISSET(USE_REGEXP)) {
611
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
612
	new_line_size = replace_regexp(NULL, 0);
613
    } else {
614
#endif
615
616
617
618
	search_match_count = strlen(needle);
	new_line_size = strlen(current->data) - search_match_count +
	    strlen(answer) + 1;
#ifdef HAVE_REGEX_H
619
    }
620
#endif
621

622
    /* Create the buffer. */
623
    copy = charalloc(new_line_size);
624

625
    /* The head of the original line. */
626
627
    strncpy(copy, current->data, current_x);

628
    /* The replacement text. */
629
#ifdef HAVE_REGEX_H
630
631
    if (ISSET(USE_REGEXP))
	replace_regexp(copy + current_x, TRUE);
632
    else
633
#endif
634
	strcpy(copy + current_x, answer);
635

636
637
638
    /* The tail of the original line. */
    assert(current_x + search_match_count <= strlen(current->data));
    strcat(copy, current->data + current_x + search_match_count);
639
640

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
641
642
}

643
/* Step through each replace word and prompt user before replacing.
644
645
646
 * Parameters real_current and real_current_x are needed in order to
 * allow the cursor position to be updated when a word before the cursor
 * is replaced by a shorter word.
647
648
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
649
650
 * if needle isn't found, else the number of replacements performed.  If
 * canceled isn't NULL, set it to TRUE if we canceled. */
651
ssize_t do_replace_loop(const char *needle, filestruct *real_current,
652
	size_t *real_current_x, bool wholewords, bool *canceled)
Chris Allegretta's avatar
Chris Allegretta committed
653
{
654
655
    ssize_t numreplaced = -1;
    size_t match_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
656
    size_t pww_save = placewewant, current_x_save = *real_current_x;
657
    const filestruct *current_save = real_current;
658
    bool replaceall = FALSE;
659
#ifdef HAVE_REGEX_H
660
    /* The starting-line match and bol/eol regex flags. */
661
    bool begin_line = FALSE, bol_or_eol = FALSE;
662
#endif
663
#ifndef NANO_SMALL
664
    bool old_mark_set = ISSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
665

666
667
668
669
    if (old_mark_set) {
	UNSET(MARK_ISSET);
	edit_refresh();
    }
670
#endif
671

672
673
674
    if (canceled != NULL)
	*canceled = FALSE;

675
    while (findnextstr(TRUE, wholewords,
676
#ifdef HAVE_REGEX_H
677
678
679
680
681
	/* We should find a bol and/or eol regex only once per line.  If
	 * the bol_or_eol flag is set, it means that the last search
	 * found one on the beginning line, so we should skip over the
	 * beginning line when doing this search. */
	bol_or_eol
682
#else
683
	FALSE
684
#endif
685
	, current_save, current_x_save, needle, &match_len)) {
686
687

	int i = 0;
688

689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
#ifdef HAVE_REGEX_H
	/* If the bol_or_eol flag is set, we've found a match on the
	 * beginning line already, and we're still on the beginning line
	 * after the search, it means that we've wrapped around, so
	 * we're done. */
	if (bol_or_eol && begin_line && current == real_current)
	    break;
	/* Otherwise, set the begin_line flag if we've found a match on
	 * the beginning line, reset the bol_or_eol flag, and
	 * continue. */
	else {
	    if (current == real_current)
		begin_line = TRUE;
	    bol_or_eol = FALSE;
	}
#endif

706
	if (!replaceall) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
707
708
	    edit_redraw(current_save, pww_save);
	    pww_save = placewewant;
709
	}
Chris Allegretta's avatar
Chris Allegretta committed
710

711
712
713
	/* Record for the return value that we found the search string. */
	if (numreplaced == -1)
	    numreplaced = 0;
714

715
	if (!replaceall) {
716
717
718
719
720
721
	    char *exp_word;
	    size_t xpt = xplustabs();

	    exp_word = display_string(current->data, xpt,
		strnlenpt(current->data, match_len + current_x) - xpt);

722
	    curs_set(0);
723
	    do_replace_highlight(TRUE, exp_word);
724

725
	    i = do_yesno(TRUE, _("Replace this instance?"));
Chris Allegretta's avatar
Chris Allegretta committed
726

727
728
	    do_replace_highlight(FALSE, exp_word);
	    free(exp_word);
729
	    curs_set(1);
730

731
732
733
	    if (i == -1) {	/* We canceled the replace. */
		if (canceled != NULL)
		    *canceled = TRUE;
734
		break;
735
	    }
736
737
	}

738
#ifdef HAVE_REGEX_H
739
	/* Set the bol_or_eol flag if we're doing a bol and/or eol regex
740
	 * replace ("^", "$", or "^$"). */
741
742
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		needle))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
743
	    bol_or_eol = TRUE;
744
745
#endif

746
	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
747
	    char *copy;
748
	    size_t length_change;
749

750
	    if (i == 2)
751
		replaceall = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
752

753
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
754

755
	    length_change = strlen(copy) - strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
756

757
758
759
760
761
762
763
764
#ifndef NANO_SMALL
	    if (current == mark_beginbuf && mark_beginx > current_x) {
		if (mark_beginx < current_x + match_len)
		    mark_beginx = current_x;
		else
		    mark_beginx += length_change;
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
765

766
767
768
769
	    if (current == real_current && current_x <= *real_current_x) {
		if (*real_current_x < current_x + match_len)
		    *real_current_x = current_x + match_len;
		*real_current_x += length_change;
770
	    }
771

772
	    /* Set the cursor at the last character of the replacement
773
	     * text, so searching will resume after the replacement
774
775
	     * text.  Note that current_x might be set to (size_t)-1
	     * here. */
776
#ifndef NANO_SMALL
777
	    if (!ISSET(REVERSE_SEARCH))
778
779
#endif
		current_x += match_len + length_change - 1;
780

781
	    /* Cleanup. */
782
783
784
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
785

786
787
788
789
790
791
792
793
794
	    if (!replaceall) {
#ifdef ENABLE_COLOR
		if (ISSET(COLOR_SYNTAX))
		    edit_refresh();
		else
#endif
		    update_line(current, current_x);
	    }

Chris Allegretta's avatar
Chris Allegretta committed
795
796
	    set_modified();
	    numreplaced++;
797
	}
Chris Allegretta's avatar
Chris Allegretta committed
798
799
    }

Chris Allegretta's avatar
Chris Allegretta committed
800
801
802
803
    /* If text has been added to the magicline, make a new magicline. */
    if (filebot->data[0] != '\0')
	new_magicline();

804
#ifndef NANO_SMALL
805
    if (old_mark_set)
806
807
808
	SET(MARK_ISSET);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
809
810
811
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
812
/* Replace a string. */
813
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
814
{
815
    int i;
816
    filestruct *edittop_save, *begin;
817
    size_t beginx, pww_save;
818
    ssize_t numreplaced;
Chris Allegretta's avatar
Chris Allegretta committed
819

Chris Allegretta's avatar
Chris Allegretta committed
820
821
822
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
823
	return;
Chris Allegretta's avatar
Chris Allegretta committed
824
825
    }

826
    i = search_init(TRUE, FALSE);
827
828
    if (i == -1) {		/* Cancel, Go to Line, blank search
				 * string, or regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
829
	replace_abort();
830
	return;
831
    } else if (i == -2) {	/* No Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
832
	do_search();
833
	return;
834
835
836
837
838
    } else if (i == 1)		/* Case Sensitive, Backwards, or Regexp
				 * search toggle. */
	do_replace();

    if (i != 0)
839
	return;
Chris Allegretta's avatar
Chris Allegretta committed
840

841
842
843
    /* If answer is not "", add answer to the search history list and
     * copy answer into last_search. */
    if (answer[0] != '\0') {
844
#ifndef NANO_SMALL
845
	update_history(&search_history, answer);
846
#endif
Chris Allegretta's avatar
Chris Allegretta committed
847
	last_search = mallocstrcpy(last_search, answer);
848
    }
Chris Allegretta's avatar
Chris Allegretta committed
849

850
#ifndef NANO_SMALL
851
852
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
853
#endif
854

855
    i = statusq(FALSE, replace_list_2, last_replace,
856
#ifndef NANO_SMALL
857
	&replace_history,
858
#endif
859
	_("Replace with"));
860

861
#ifndef NANO_SMALL
862
863
864
    /* Add this replace string to the replace history list.  i == 0
     * means that the string is not "". */
    if (i == 0)
865
	update_history(&replace_history, answer);
866
867
868
869
870
871
#endif

    if (i != 0 && i != -2) {
	if (i == -1) {		/* Cancel. */
	    if (last_replace[0] != '\0')
		answer = mallocstrcpy(answer, last_replace);
872
	    statusbar(_("Cancelled"));
873
874
	}
	replace_abort();
875
	return;
876
877
878
    }

    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
879

880
    /* Save where we are. */
881
    edittop_save = edittop;
Chris Allegretta's avatar
Chris Allegretta committed
882
    begin = current;
883
    beginx = current_x;
884
    pww_save = placewewant;
Chris Allegretta's avatar
Chris Allegretta committed
885

886
887
    numreplaced = do_replace_loop(last_search, begin, &beginx, FALSE,
	NULL);
Chris Allegretta's avatar
Chris Allegretta committed
888

889
    /* Restore where we were. */
890
    edittop = edittop_save;
Chris Allegretta's avatar
Chris Allegretta committed
891
    current = begin;
892
    current_x = beginx;
893
    placewewant = pww_save;
894

Chris Allegretta's avatar
Chris Allegretta committed
895
    renumber_all();
896
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
897
898

    if (numreplaced >= 0)
899
900
	statusbar(P_("Replaced %ld occurrence", "Replaced %ld occurrences",
		(long)numreplaced), (long)numreplaced);
Chris Allegretta's avatar
Chris Allegretta committed
901

Chris Allegretta's avatar
Chris Allegretta committed
902
903
904
    replace_abort();
}

905
void do_gotoline(int line, bool save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
906
{
907
    if (line <= 0) {		/* Ask for it. */
908
	char *ans = mallocstrcpy(NULL, answer);
909
	int i = statusq(FALSE, gotoline_list, line < 0 ? ans : "",
Chris Allegretta's avatar
Chris Allegretta committed
910
#ifndef NANO_SMALL
911
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
912
#endif
913
914
915
		_("Enter line number"));

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
916
917

	/* Cancel, or Enter with blank string. */
918
	if (i < 0) {
919
	    statusbar(_("Cancelled"));
920
	    display_main_list();
921
922
923
	    return;
	}

924
	if (i == NANO_TOOTHERWHEREIS_KEY) {
925
926
927
	    /* Keep answer up on the statusbar. */
	    search_init(TRUE, TRUE);

928
	    do_search();
929
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
930
	}
931

932
933
	/* Do a bounds check.  Display a warning on an out-of-bounds
	 * line number only if we hit Enter at the statusbar prompt. */
934
	if (!parse_num(answer, &line) || line < 0) {
935
936
	    if (i == 0)
		statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
937
	    display_main_list();
938
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
939
940
941
	}
    }

942
943
944
945
946
947
948
949
950
    if (current->lineno > line) {
	for (; current->prev != NULL && current->lineno > line;
		current = current->prev)
	    ;
    } else {
	for (; current->next != NULL && current->lineno < line;
		current = current->next)
	    ;
    }
Chris Allegretta's avatar
Chris Allegretta committed
951

952
    current_x = 0;
953

954
    /* If save_pos is TRUE, don't change the cursor position when
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
955
     * updating the edit window. */
956
    edit_update(save_pos ? NONE : CENTER);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
957

958
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
959
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
960
961
}

962
void do_gotoline_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
963
{
964
    do_gotoline(0, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
965
}
966

967
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
968
void do_gotopos(int line, size_t pos_x, int pos_y, size_t pos_pww)
969
{
970
971
    /* Since do_gotoline() resets the x-coordinate but not the
     * y-coordinate, set the coordinates up this way. */
972
    current_y = pos_y;
973
    do_gotoline(line, TRUE);
974

975
976
977
978
    /* Make sure that the x-coordinate is sane here. */
    current_x = strlen(current->data);
    if (pos_x < current_x)
	current_x = pos_x;
979

980
    /* Set the rest of the coordinates up. */
981
    placewewant = pos_pww;
982
983
984
    update_line(current, pos_x);
}
#endif
985
986

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
987
void do_find_bracket(void)
988
989
{
    char ch_under_cursor, wanted_ch;
990
    const char *pos, *brackets = "([{<>}])";
991
    char regexp_pat[] = "[  ]";
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
992
    size_t current_x_save, pww_save;
993
    int count = 1;
994
    long flags_save;
995
996
997
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
998

999
1000
    pos = strchr(brackets, ch_under_cursor);
    if (ch_under_cursor == '\0' || pos == NULL) {
1001
	statusbar(_("Not a bracket"));
1002
	return;
1003
1004
    }

1005
1006
    assert(strlen(brackets) % 2 == 0);
    wanted_ch = brackets[(strlen(brackets) - 1) - (pos - brackets)];
1007
1008

    current_save = current;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1009
    current_x_save = current_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1010
    pww_save = placewewant;
1011
    flags_save = flags;
1012
1013
    SET(USE_REGEXP);

1014
1015
    /* Apparent near redundancy with regexp_pat[] here is needed.
     * "[][]" works, "[[]]" doesn't. */
1016

1017
1018
    if (pos < brackets + (strlen(brackets) / 2)) {
	/* On a left bracket. */
1019
1020
1021
	regexp_pat[1] = wanted_ch;
	regexp_pat[2] = ch_under_cursor;
	UNSET(REVERSE_SEARCH);
1022
1023
    } else {
	/* On a right bracket. */
1024
1025
1026
1027
1028
1029
	regexp_pat[1] = ch_under_cursor;
	regexp_pat[2] = wanted_ch;
	SET(REVERSE_SEARCH);
    }

    regexp_init(regexp_pat);
1030
    /* We constructed regexp_pat to be a valid expression. */
1031
    assert(regexp_compiled);
1032

1033
    while (TRUE) {
1034
	if (findnextstr(FALSE, FALSE, FALSE, current, current_x,
1035
		regexp_pat, NULL)) {
1036
	    /* Found identical bracket. */
Chris Allegretta's avatar
Chris Allegretta committed
1037
	    if (current->data[current_x] == ch_under_cursor)
1038
		count++;
1039
1040
1041
	    /* Found complementary bracket. */
	    else if (--count == 0) {
		placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1042
		edit_redraw(current_save, pww_save);
1043
		break;
1044
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1045
	} else {
1046
	    /* Didn't find either a left or right bracket. */
1047
1048
	    statusbar(_("No matching bracket"));
	    current = current_save;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1049
	    current_x = current_x_save;
Chris Allegretta's avatar
Chris Allegretta committed
1050
	    update_line(current, current_x);
1051
1052
1053
1054
	    break;
	}
    }

1055
    regexp_cleanup();
1056
    flags = flags_save;
1057
1058
}
#endif
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083

#ifndef NANO_SMALL
/*
 * search and replace history list support functions
 */

/* initialize search and replace history lists */
void history_init(void)
{
    search_history.next = (historytype *)&search_history.prev;
    search_history.prev = NULL;
    search_history.tail = (historytype *)&search_history.next;
    search_history.current = search_history.next;
    search_history.count = 0;
    search_history.len = 0;

    replace_history.next = (historytype *)&replace_history.prev;
    replace_history.prev = NULL;
    replace_history.tail = (historytype *)&replace_history.next;
    replace_history.current = replace_history.next;
    replace_history.count = 0;
    replace_history.len = 0;
}

/* find first node containing string *s in history list *h */
1084
historytype *find_node(historytype *h, const char *s)
1085
{
Chris Allegretta's avatar
Chris Allegretta committed
1086
    for (; h->next != NULL; h = h->next)
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
	if (strcmp(s, h->data) == 0)
	    return h;
    return NULL;
}

/* remove node *r */
void remove_node(historytype *r)
{
    r->prev->next = r->next;
    r->next->prev = r->prev;
    free(r->data);
    free(r);
}

/* add a node after node *h */
void insert_node(historytype *h, const char *s)
{
    historytype *a;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1106
    a = (historytype *)nmalloc(sizeof(historytype));
1107
    a->next = h->next;
1108
    a->prev = h;
1109
1110
1111
1112
1113
1114
    h->next->prev = a;
    h->next = a;
    a->data = mallocstrcpy(NULL, s);
}

/* update history list */
1115
void update_history(historyheadtype *h, const char *s)
1116
1117
1118
{
    historytype *p;

Chris Allegretta's avatar
Chris Allegretta committed
1119
1120
1121
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
1122
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
1123
	remove_node(p);			/* delete identical older string */
1124
1125
1126
1127
1128
1129
1130
1131
	h->count--;
    }
    if (h->count == MAX_SEARCH_HISTORY) {	/* list 'full', delete oldest */
	remove_node(h->tail);
	h->count--;
    }
    insert_node((historytype *)h, s);
    h->count++;
1132
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
1133
  up_hs:
1134
1135
1136
1137
1138
1139
    h->current = h->next;
}

/* return a pointer to either the next older history or NULL if no more */
char *get_history_older(historyheadtype *h)
{
Chris Allegretta's avatar
Chris Allegretta committed
1140
    if (h->current->next != NULL) {	/* any older entries? */
1141
1142
1143
1144
1145
1146
1147
1148
	h->current = h->current->next;	/* yes */
	return h->current->data;	/* return it */
    }
    return NULL;			/* end of list */
}

char *get_history_newer(historyheadtype *h)
{
Chris Allegretta's avatar
Chris Allegretta committed
1149
    if (h->current->prev != NULL) {
1150
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1151
	if (h->current->prev != NULL)
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
	    return h->current->data;
    }
    return NULL;
}

/* get a completion */
char *get_history_completion(historyheadtype *h, char *s)
{
    historytype *p;

Chris Allegretta's avatar
Chris Allegretta committed
1162
1163
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1164
1165
1166
1167
1168
1169
1170
1171
1172
	    h->current = p;
	    return p->data;
	}
    }
    h->current = (historytype*)h;
    null_at(&s, h->len);
    return s;
}

1173
#ifdef DEBUG
1174
1175
1176
/* free a history list */
void free_history(historyheadtype *h)
{
1177
    historytype *p;
1178

1179
    for (p = h->next; p->next != NULL; p = p->next)
1180
1181
	remove_node(p);
}
1182
#endif
1183
1184
1185

/* end of history support functions */
#endif /* !NANO_SMALL */