search.c 36.9 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) 1999-2005 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
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   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.                             *
Chris Allegretta's avatar
Chris Allegretta committed
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

23
24
25
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
26

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

34
35
static bool search_last_line = FALSE;
	/* Have we gone past the last line while searching? */
36
#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
37
38
39
static bool history_changed = FALSE;
	/* Have any of the history lists changed? */
#endif
40
#ifdef HAVE_REGEX_H
41
42
static bool regexp_compiled = FALSE;
	/* Have we compiled any regular expressions? */
43
44
45
46
47
48

/* 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. */
49
int regexp_init(const char *regexp)
50
{
51
    int rc = regcomp(&search_regexp, regexp, REG_EXTENDED
52
#ifndef NANO_TINY
53
54
55
	| (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE)
#endif
	);
56
57

    assert(!regexp_compiled);
58

59
60
61
62
63
64
65
    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);
66
	return 0;
67
    }
68

69
    regexp_compiled = TRUE;
70
    return 1;
71
72
}

73
void regexp_cleanup(void)
74
{
75
76
    if (regexp_compiled) {
	regexp_compiled = FALSE;
77
78
	regfree(&search_regexp);
    }
79
}
80
#endif
81

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

89
    disp = display_string(str, 0, (COLS / 2) + 1, FALSE);
90
    numchars = actual_x(disp, mbstrnlen(disp, COLS / 2));
91
92

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

    free(disp);
96
97
98
99
100
}

void search_abort(void)
{
    display_main_list();
101
#ifndef NANO_TINY
102
    if (openfile->mark_set)
103
104
	edit_refresh();
#endif
105
#ifdef HAVE_REGEX_H
106
    regexp_cleanup();
107
108
109
#endif
}

Chris Allegretta's avatar
Chris Allegretta committed
110
111
void search_init_globals(void)
{
112
113
114
115
    if (last_search == NULL)
	last_search = mallocstrcpy(NULL, "");
    if (last_replace == NULL)
	last_replace = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
116
117
}

118
119
120
121
122
123
/* 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
124
 *
125
126
127
 * 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
128
{
129
    int i = 0;
130
    char *buf;
131
    static char *backupstring = NULL;
132
133
134
135
136
137
138
139
140
141
142
	/* 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;
    }
143
144
145
146

    /* 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,
147
     * we should put the same search string back up. */
148

Chris Allegretta's avatar
Chris Allegretta committed
149
    search_init_globals();
150

151
    if (last_search[0] != '\0') {
152
	char *disp = display_string(last_search, 0, COLS / 3, FALSE);
153

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

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

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

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

185
#ifndef NANO_TINY
186
187
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
188
	ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
189
190
#endif
		"",
191

192
	replacing ?
193
#ifndef NANO_TINY
194
		openfile->mark_set ? _(" (to replace) in selection") :
195
#endif
196
		_(" (to replace)") : "",
197

Chris Allegretta's avatar
Chris Allegretta committed
198
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
199

200
    /* Release buf now that we don't need it anymore. */
Chris Allegretta's avatar
Chris Allegretta committed
201
202
    free(buf);

203
204
205
    free(backupstring);
    backupstring = NULL;

206
207
208
    /* 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')) {
209
	statusbar(_("Cancelled"));
Chris Allegretta's avatar
Chris Allegretta committed
210
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
211
212
    } else {
	switch (i) {
213
214
	    case -2:		/* It's an empty string. */
	    case 0:		/* It's a new string. */
215
#ifdef HAVE_REGEX_H
216
217
218
219
220
		/* Use last_search if answer is an empty string, or
		 * answer if it isn't. */
		if (ISSET(USE_REGEXP) &&
			regexp_init((i == -2) ? last_search :
			answer) == 0)
221
		    return -1;
222
#endif
223
		break;
224
#ifndef NANO_TINY
225
226
227
228
229
	    case TOGGLE_CASE_KEY:
		TOGGLE(CASE_SENSITIVE);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	    case TOGGLE_BACKWARDS_KEY:
230
		TOGGLE(BACKWARDS_SEARCH);
231
232
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
233
#endif
234
#ifdef HAVE_REGEX_H
235
	    case NANO_REGEXP_KEY:
236
237
238
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
239
#endif
240
241
242
243
	    case NANO_TOOTHERSEARCH_KEY:
		backupstring = mallocstrcpy(backupstring, answer);
		return -2;	/* Call the opposite search function. */
	    case NANO_TOGOTOLINE_KEY:
244
		do_gotolinecolumn(openfile->current->lineno,
245
246
			openfile->placewewant + 1, TRUE, TRUE, FALSE,
			TRUE);
247
248
				/* Put answer up on the statusbar and
				 * fall through. */
249
250
	    default:
		return -1;
Chris Allegretta's avatar
Chris Allegretta committed
251
	}
Chris Allegretta's avatar
Chris Allegretta committed
252
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
253

Chris Allegretta's avatar
Chris Allegretta committed
254
255
256
    return 0;
}

257
258
/* Look for needle, starting at (current, current_x).  If no_sameline is
 * TRUE, skip over begin when looking for needle.  begin is the line
259
260
261
262
263
 * where we first started searching, at column begin_x.  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. */
bool findnextstr(bool whole_word, bool no_sameline, const filestruct
	*begin, size_t begin_x, const char *needle, size_t *needle_len)
Chris Allegretta's avatar
Chris Allegretta committed
264
{
265
    filestruct *fileptr = openfile->current;
266
    const char *rev_start = NULL, *found = NULL;
267
268
    size_t found_len;
	/* The length of the match we found. */
269
    size_t current_x_find = 0;
270
	/* The location in the current line of the match we found. */
271
    ssize_t current_y_find = openfile->current_y;
272
273
274
275
276
277

    /* 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
278
    rev_start =
279
#ifndef NANO_TINY
280
281
	ISSET(BACKWARDS_SEARCH) ?
	fileptr->data + (openfile->current_x - 1) :
282
#endif
283
	fileptr->data + (openfile->current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
284

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
285
    /* Look for needle in the current line we're searching. */
286
    while (TRUE) {
287
	found = strstrwrapper(fileptr->data, needle, rev_start);
Chris Allegretta's avatar
Chris Allegretta committed
288

289
290
291
292
293
294
	/* 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. */
295
	    found_len =
296
#ifdef HAVE_REGEX_H
297
298
		ISSET(USE_REGEXP) ?
		regmatches[0].rm_eo - regmatches[0].rm_so :
299
#endif
300
		strlen(needle);
301
302
303

	    /* If we're searching for whole words, see if this potential
	     * match is a whole word. */
304
	    if (whole_word) {
305
		char *word = mallocstrncpy(NULL, found, found_len + 1);
306
307
308
309
310
311
312
313
314
315
316
		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. */
317
	    if ((!whole_word || found_whole) && (!no_sameline ||
318
		fileptr != openfile->current))
319
		break;
Chris Allegretta's avatar
Chris Allegretta committed
320
	}
321

322
	/* We've finished processing the file, so get out. */
323
	if (search_last_line) {
324
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
325
	    return FALSE;
326
	}
327

328
#ifndef NANO_TINY
329
	if (ISSET(BACKWARDS_SEARCH)) {
330
331
332
333
334
335
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
336
#ifndef NANO_TINY
337
	}
338
#endif
339

340
	/* Start or end of buffer reached, so wrap around. */
341
	if (fileptr == NULL) {
342
#ifndef NANO_TINY
343
	    if (ISSET(BACKWARDS_SEARCH)) {
344
		fileptr = openfile->filebot;
345
346
		current_y_find = editwinrows - 1;
	    } else {
347
#endif
348
		fileptr = openfile->fileage;
349
		current_y_find = 0;
350
#ifndef NANO_TINY
351
352
353
	    }
#endif

354
	    statusbar(_("Search Wrapped"));
355
	}
Chris Allegretta's avatar
Chris Allegretta committed
356

357
	/* Original start line reached. */
358
	if (fileptr == begin)
359
	    search_last_line = TRUE;
360

361
	rev_start = fileptr->data;
362
#ifndef NANO_TINY
363
	if (ISSET(BACKWARDS_SEARCH))
364
365
366
	    rev_start += strlen(fileptr->data);
#endif
    }
367

368
369
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
370

371
372
    /* Ensure we haven't wrapped around again! */
    if (search_last_line &&
373
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
374
375
	((!ISSET(BACKWARDS_SEARCH) && current_x_find > begin_x) ||
	(ISSET(BACKWARDS_SEARCH) && current_x_find < begin_x))
376
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
377
	current_x_find > begin_x
378
379
#endif
	) {
380
	not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
381
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
382
383
    }

384
385
386
387
    /* We've definitely found something. */
    openfile->current = fileptr;
    openfile->current_x = current_x_find;
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
388
    openfile->current_y = current_y_find;
389
390

    /* needle_len holds the length of needle. */
391
392
    if (needle_len != NULL)
	*needle_len = found_len;
393

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
394
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
395
396
}

397
398
399
400
401
void findnextstr_wrap_reset(void)
{
    search_last_line = FALSE;
}

Chris Allegretta's avatar
Chris Allegretta committed
402
/* Search for a string. */
403
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
404
{
405
    filestruct *fileptr = openfile->current;
406
    size_t fileptr_x = openfile->current_x;
407
    size_t old_pww = openfile->placewewant;
408
409
    int i;
    bool didfind;
Chris Allegretta's avatar
Chris Allegretta committed
410

411
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
412
    wrap_reset();
413
#endif
414

415
    i = search_init(FALSE, FALSE);
416
417
    if (i == -1)	/* Cancel, Go to Line, blank search string, or
			 * regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
418
	search_abort();
419
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
420
	do_replace();
421
#if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
422
423
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
424
	do_search();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
425
#endif
426

427
    if (i != 0)
428
	return;
429
430

    /* If answer is now "", copy last_search into answer. */
Chris Allegretta's avatar
Chris Allegretta committed
431
    if (answer[0] == '\0')
432
433
434
435
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

436
#ifndef NANO_TINY
437
438
    /* If answer is not "", add this search string to the search history
     * list. */
Chris Allegretta's avatar
Chris Allegretta committed
439
    if (answer[0] != '\0')
440
	update_history(&search_history, answer);
441
#endif
442

443
    findnextstr_wrap_reset();
444
    didfind = findnextstr(FALSE, FALSE, openfile->current,
445
	openfile->current_x, answer, NULL);
446

447
448
    /* Check to see if there's only one occurrence of the string and
     * we're on it now. */
449
450
    if (fileptr == openfile->current && fileptr_x ==
	openfile->current_x && didfind) {
451
452
453
454
455
456
#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. */
457
458
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
459
	    didfind = findnextstr(FALSE, TRUE, openfile->current,
460
461
462
		openfile->current_x, answer, NULL);
	    if (fileptr == openfile->current && fileptr_x ==
		openfile->current_x && !didfind)
463
464
465
466
467
468
469
470
		statusbar(_("This is the only occurrence"));
	} else {
#endif
	    statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	}
#endif
    }
471

472
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
473
    edit_redraw(fileptr, old_pww);
Chris Allegretta's avatar
Chris Allegretta committed
474
475
476
    search_abort();
}

477
#ifndef NANO_TINY
478
/* Search for the next string without prompting. */
479
void do_research(void)
480
{
481
    filestruct *fileptr = openfile->current;
482
    size_t fileptr_x = openfile->current_x;
483
    size_t old_pww = openfile->placewewant;
484
    bool didfind;
485

486
#ifndef DISABLE_WRAPPING
487
    wrap_reset();
488
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
489

490
491
492
493
    search_init_globals();

    if (last_search[0] != '\0') {
#ifdef HAVE_REGEX_H
494
	/* Since answer is "", use last_search! */
495
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
496
	    return;
497
498
#endif

499
	findnextstr_wrap_reset();
500
	didfind = findnextstr(FALSE, FALSE, openfile->current,
501
		openfile->current_x, last_search, NULL);
502

503
504
	/* Check to see if there's only one occurrence of the string and
	 * we're on it now. */
505
506
	if (fileptr == openfile->current && fileptr_x ==
		openfile->current_x && didfind) {
507
508
509
510
511
512
#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. */
513
514
	    if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
515
516
		didfind = findnextstr(FALSE, TRUE, openfile->current,
			openfile->current_x, answer, NULL);
517
518
		if (fileptr == openfile->current && fileptr_x ==
			openfile->current_x && !didfind)
519
520
521
522
523
524
525
526
		    statusbar(_("This is the only occurrence"));
	    } else {
#endif
		statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	    }
#endif
	}
527
528
529
    } else
        statusbar(_("No current search pattern"));

530
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
531
    edit_redraw(fileptr, old_pww);
532
533
    search_abort();
}
534
#endif
535

Chris Allegretta's avatar
Chris Allegretta committed
536
537
void replace_abort(void)
{
538
    /* For now, we do the same thing as search_abort(). */
539
    search_abort();
540
541
}

542
#ifdef HAVE_REGEX_H
543
int replace_regexp(char *string, bool create)
544
{
545
546
    /* We have a split personality here.  If create is FALSE, just
     * calculate the size of the replacement line (necessary because of
547
     * subexpressions \1 to \9 in the replaced text). */
548

549
    const char *c = last_replace;
550
551
552
553
    size_t search_match_count = regmatches[0].rm_eo -
	regmatches[0].rm_so;
    size_t new_line_size = strlen(openfile->current->data) + 1 -
	search_match_count;
554

Chris Allegretta's avatar
Chris Allegretta committed
555
556
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
557
    while (*c != '\0') {
558
559
	int num = (int)(*(c + 1) - '0');

560
561
	if (*c != '\\' || num < 1 || num > 9 || num >
		search_regexp.re_nsub) {
562
	    if (create)
563
564
		*string++ = *c;
	    c++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
565
	    new_line_size++;
566
	} else {
567
	    size_t i = regmatches[num].rm_eo - regmatches[num].rm_so;
568

569
570
	    /* Skip over the replacement expression. */
	    c += 2;
571

572
	    /* But add the length of the subexpression to new_size. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
573
	    new_line_size += i;
574

575
	    /* And if create is TRUE, append the result of the
576
	     * subexpression match to the new line. */
577
	    if (create) {
578
579
		strncpy(string, openfile->current->data +
			openfile->current_x + regmatches[num].rm_so, i);
580
		string += i;
581
582
	    }
	}
583
584
    }

585
    if (create)
Chris Allegretta's avatar
Chris Allegretta committed
586
	*string = '\0';
587

588
    return new_line_size;
589
}
590
#endif
591

592
char *replace_line(const char *needle)
593
{
594
    char *copy;
595
    size_t new_line_size, search_match_count;
596

597
    /* Calculate the size of the new line. */
598
#ifdef HAVE_REGEX_H
599
    if (ISSET(USE_REGEXP)) {
600
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
601
	new_line_size = replace_regexp(NULL, FALSE);
602
    } else {
603
#endif
604
	search_match_count = strlen(needle);
605
606
	new_line_size = strlen(openfile->current->data) -
		search_match_count + strlen(answer) + 1;
607
#ifdef HAVE_REGEX_H
608
    }
609
#endif
610

611
    /* Create the buffer. */
612
    copy = charalloc(new_line_size);
613

614
    /* The head of the original line. */
615
    strncpy(copy, openfile->current->data, openfile->current_x);
616

617
    /* The replacement text. */
618
#ifdef HAVE_REGEX_H
619
    if (ISSET(USE_REGEXP))
620
	replace_regexp(copy + openfile->current_x, TRUE);
621
    else
622
#endif
623
	strcpy(copy + openfile->current_x, answer);
624

625
    /* The tail of the original line. */
626
    assert(openfile->current_x + search_match_count <= strlen(openfile->current->data));
627

628
629
    strcat(copy, openfile->current->data + openfile->current_x +
	search_match_count);
630
631

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
632
633
}

634
/* Step through each replace word and prompt user before replacing.
635
636
637
 * 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.
638
639
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
640
641
 * if needle isn't found, else the number of replacements performed.  If
 * canceled isn't NULL, set it to TRUE if we canceled. */
642
643
644
ssize_t do_replace_loop(bool whole_word, bool *canceled, const
	filestruct *real_current, size_t *real_current_x, const char
	*needle)
Chris Allegretta's avatar
Chris Allegretta committed
645
{
646
647
    ssize_t numreplaced = -1;
    size_t match_len;
648
    bool replaceall = FALSE;
649
#ifdef HAVE_REGEX_H
650
    /* The starting-line match and bol/eol regex flags. */
651
    bool begin_line = FALSE, bol_or_eol = FALSE;
652
#endif
653
#ifndef NANO_TINY
654
655
    bool old_mark_set = openfile->mark_set;
    filestruct *edittop_save = openfile->edittop, *top, *bot;
656
657
    size_t top_x, bot_x;
    bool right_side_up = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
658
	/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
659
	 * FALSE if (current, current_x) is. */
Chris Allegretta's avatar
Chris Allegretta committed
660

661
    if (old_mark_set) {
662
	/* If the mark is on, partition the filestruct so that it
663
664
	 * contains only the marked text, set edittop to the top of the
	 * partition, turn the mark off, and refresh the screen. */
665
	mark_order((const filestruct **)&top, &top_x,
666
	    (const filestruct **)&bot, &bot_x, &right_side_up);
667
	filepart = partition_filestruct(top, top_x, bot, bot_x);
668
669
	openfile->edittop = openfile->fileage;
	openfile->mark_set = FALSE;
670
671
	edit_refresh();
    }
672
#endif
673

674
675
676
    if (canceled != NULL)
	*canceled = FALSE;

677
    findnextstr_wrap_reset();
678
    while (findnextstr(whole_word,
679
#ifdef HAVE_REGEX_H
680
681
682
683
684
	/* 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
685
#else
686
	FALSE
687
#endif
688
	, real_current, *real_current_x, needle, &match_len)) {
689
	int i = 0;
690

691
692
693
694
695
#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. */
696
697
	if (bol_or_eol && begin_line && openfile->current ==
		real_current)
698
699
700
701
702
	    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 {
703
	    if (openfile->current == real_current)
704
705
706
707
708
		begin_line = TRUE;
	    bol_or_eol = FALSE;
	}
#endif

709
710
	if (!replaceall)
	    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
711

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
712
	/* Indicate that we found the search string. */
713
714
	if (numreplaced == -1)
	    numreplaced = 0;
715

716
	if (!replaceall) {
717
	    size_t xpt = xplustabs();
718
719
720
	    char *exp_word = display_string(openfile->current->data,
		xpt, strnlenpt(openfile->current->data,
		openfile->current_x + match_len) - xpt, FALSE);
721

722
	    curs_set(0);
723

724
	    do_replace_highlight(TRUE, exp_word);
725

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

728
	    do_replace_highlight(FALSE, exp_word);
729

730
	    free(exp_word);
731

732
	    curs_set(1);
733

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

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

749
	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
750
	    char *copy;
751
	    size_t length_change;
752

753
	    if (i == 2)
754
		replaceall = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
755

756
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
757

758
759
	    length_change = strlen(copy) -
		strlen(openfile->current->data);
Chris Allegretta's avatar
Chris Allegretta committed
760

761
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
762
763
	    /* If the mark was on and (mark_begin, mark_begin_x) was the
	     * top of it, don't change mark_begin_x. */
764
	    if (!old_mark_set || !right_side_up) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
765
766
767
768
		/* Keep mark_begin_x in sync with the text changes. */
		if (openfile->current == openfile->mark_begin &&
			openfile->mark_begin_x > openfile->current_x) {
		    if (openfile->mark_begin_x < openfile->current_x +
769
			match_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
770
			openfile->mark_begin_x = openfile->current_x;
771
		    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
772
			openfile->mark_begin_x += length_change;
773
		}
774
	    }
Chris Allegretta's avatar
Chris Allegretta committed
775

776
777
778
	    /* If the mark was on and (current, current_x) was the top
	     * of it, don't change real_current_x. */
	    if (!old_mark_set || right_side_up) {
779
#endif
780
		/* Keep real_current_x in sync with the text changes. */
781
782
783
784
785
786
		if (openfile->current == real_current &&
			openfile->current_x <= *real_current_x) {
		    if (*real_current_x <
			openfile->current_x + match_len)
			*real_current_x = openfile->current_x +
				match_len;
787
788
		    *real_current_x += length_change;
		}
789
#ifndef NANO_TINY
790
	    }
791
#endif
792

793
	    /* Set the cursor at the last character of the replacement
794
	     * text, so searching will resume after the replacement
795
796
	     * text.  Note that current_x might be set to (size_t)-1
	     * here. */
797
#ifndef NANO_TINY
798
	    if (!ISSET(BACKWARDS_SEARCH))
799
#endif
800
		openfile->current_x += match_len + length_change - 1;
801

802
	    /* Cleanup. */
803
804
805
	    openfile->totsize += length_change;
	    free(openfile->current->data);
	    openfile->current->data = copy;
806

807
808
	    if (!replaceall) {
#ifdef ENABLE_COLOR
809
810
811
812
		/* If color syntaxes are available and turned on, we
		 * need to call edit_refresh(). */
		if (openfile->colorstrings != NULL &&
			!ISSET(NO_COLOR_SYNTAX))
813
814
815
		    edit_refresh();
		else
#endif
816
		    update_line(openfile->current, openfile->current_x);
817
818
	    }

Chris Allegretta's avatar
Chris Allegretta committed
819
820
	    set_modified();
	    numreplaced++;
821
	}
Chris Allegretta's avatar
Chris Allegretta committed
822
823
    }

824
#ifndef NANO_TINY
825
    if (old_mark_set) {
826
827
828
	/* If the mark was on, unpartition the filestruct so that it
	 * contains all the text again, set edittop back to what it was
	 * before, turn the mark back on, and refresh the screen. */
829
	unpartition_filestruct(&filepart);
830
831
	openfile->edittop = edittop_save;
	openfile->mark_set = TRUE;
832
833
	edit_refresh();
    }
834
835
#endif

836
837
838
    /* If the NO_NEWLINES flag isn't set, and text has been added to the
     * magicline, make a new magicline. */
    if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
839
840
	new_magicline();

Chris Allegretta's avatar
Chris Allegretta committed
841
842
843
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
844
/* Replace a string. */
845
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
846
{
847
    filestruct *edittop_save, *begin;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
848
    size_t begin_x, pww_save;
849
    ssize_t numreplaced;
850
    int i;
Chris Allegretta's avatar
Chris Allegretta committed
851

Chris Allegretta's avatar
Chris Allegretta committed
852
853
854
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
855
	return;
Chris Allegretta's avatar
Chris Allegretta committed
856
857
    }

858
    i = search_init(TRUE, FALSE);
859
860
    if (i == -1) {		/* Cancel, Go to Line, blank search
				 * string, or regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
861
	replace_abort();
862
	return;
863
    } else if (i == -2) {	/* No Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
864
	do_search();
865
	return;
866
867
868
869
870
    } else if (i == 1)		/* Case Sensitive, Backwards, or Regexp
				 * search toggle. */
	do_replace();

    if (i != 0)
871
	return;
Chris Allegretta's avatar
Chris Allegretta committed
872

873
874
875
    /* If answer is not "", add answer to the search history list and
     * copy answer into last_search. */
    if (answer[0] != '\0') {
876
#ifndef NANO_TINY
877
	update_history(&search_history, answer);
878
#endif
Chris Allegretta's avatar
Chris Allegretta committed
879
	last_search = mallocstrcpy(last_search, answer);
880
    }
Chris Allegretta's avatar
Chris Allegretta committed
881

882
883
    last_replace = mallocstrcpy(last_replace, "");

884
    i = do_prompt(FALSE, replace_list_2, last_replace,
885
#ifndef NANO_TINY
886
	&replace_history,
887
#endif
888
	_("Replace with"));
889

890
#ifndef NANO_TINY
891
892
893
    /* Add this replace string to the replace history list.  i == 0
     * means that the string is not "". */
    if (i == 0)
894
	update_history(&replace_history, answer);
895
896
897
898
899
900
#endif

    if (i != 0 && i != -2) {
	if (i == -1) {		/* Cancel. */
	    if (last_replace[0] != '\0')
		answer = mallocstrcpy(answer, last_replace);
901
	    statusbar(_("Cancelled"));
902
903
	}
	replace_abort();
904
	return;
905
906
907
    }

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

909
    /* Save where we are. */
910
911
    edittop_save = openfile->edittop;
    begin = openfile->current;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
912
    begin_x = openfile->current_x;
913
    pww_save = openfile->placewewant;
Chris Allegretta's avatar
Chris Allegretta committed
914

915
916
    numreplaced = do_replace_loop(FALSE, NULL, begin, &begin_x,
	last_search);
Chris Allegretta's avatar
Chris Allegretta committed
917

918
    /* Restore where we were. */
919
920
    openfile->edittop = edittop_save;
    openfile->current = begin;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
921
    openfile->current_x = begin_x;
922
    openfile->placewewant = pww_save;
923

924
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
925
926

    if (numreplaced >= 0)
927
928
929
	statusbar(P_("Replaced %lu occurrence",
		"Replaced %lu occurrences", (unsigned long)numreplaced),
		(unsigned long)numreplaced);
Chris Allegretta's avatar
Chris Allegretta committed
930

Chris Allegretta's avatar
Chris Allegretta committed
931
932
933
    replace_abort();
}

934
935
/* Go to the specified line and column, or ask for them if interactive
 * is TRUE.  Save the x-coordinate and y-coordinate if save_pos is TRUE.
936
937
 * Update the screen afterwards if allow_update is TRUE.  Note that both
 * the line and column numbers should be one-based. */
938
void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
939
	bool interactive, bool save_pos, bool allow_update)
Chris Allegretta's avatar
Chris Allegretta committed
940
{
941
    if (interactive) {
942
	char *ans = mallocstrcpy(NULL, answer);
943
944

	/* Ask for it. */
945
	int i = do_prompt(FALSE, gotoline_list, use_answer ? ans : "",
946
#ifndef NANO_TINY
947
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
948
#endif
949
		_("Enter line number, column number"));
950
951

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
952
953

	/* Cancel, or Enter with blank string. */
954
	if (i < 0) {
955
	    statusbar(_("Cancelled"));
956
	    display_main_list();
957
958
959
	    return;
	}

960
	if (i == NANO_TOOTHERWHEREIS_KEY) {
961
962
963
	    /* Keep answer up on the statusbar. */
	    search_init(TRUE, TRUE);

964
	    do_search();
965
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
966
	}
967

968
	/* Do a bounds check.  Display a warning on an out-of-bounds
969
970
	 * line or column number only if we hit Enter at the statusbar
	 * prompt. */
971
	if (!parse_line_column(answer, &line, &column) || line < 1 ||
972
		column < 1) {
973
974
	    if (i == 0)
		statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
975
	    display_main_list();
976
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
977
	}
978
979
    } else {
	if (line < 1)
980
	    line = openfile->current->lineno;
981

982
	if (column < 1)
983
	    column = openfile->placewewant + 1;
Chris Allegretta's avatar
Chris Allegretta committed
984
985
    }

986
    for (openfile->current = openfile->fileage;
987
	openfile->current != openfile->filebot && line > 1; line--)
988
	openfile->current = openfile->current->next;
Chris Allegretta's avatar
Chris Allegretta committed
989

990
991
    openfile->current_x = actual_x(openfile->current->data, column - 1);
    openfile->placewewant = column - 1;
992

993
994
995
996
997
998
    /* Put the top line of the edit window in range of the current line.
     * If save_pos is TRUE, don't change the cursor position when doing
     * it. */
    edit_update(save_pos ? NONE : CENTER);

    /* If allow_update is TRUE, update the screen. */
999
    if (allow_update)
1000
	edit_refresh();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1001

Chris Allegretta's avatar
Chris Allegretta committed
1002
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1003
1004
}

1005
void do_gotolinecolumn_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1006
{
1007
    do_gotolinecolumn(openfile->current->lineno,
1008
	openfile->placewewant + 1, FALSE, TRUE, FALSE, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
1009
}
1010

1011
#ifndef DISABLE_SPELLER
1012
1013
void do_gotopos(ssize_t line, size_t pos_x, ssize_t pos_y, size_t
	pos_pww)
1014
{
1015
    /* Since do_gotolinecolumn() resets the x-coordinate but not the
1016
     * y-coordinate, set the coordinates up this way. */
1017
    openfile->current_y = pos_y;
1018
    do_gotolinecolumn(line, pos_x + 1, FALSE, FALSE, TRUE, TRUE);
1019

1020
    /* Set the rest of the coordinates up. */
1021
1022
    openfile->placewewant = pos_pww;
    update_line(openfile->current, pos_x);
1023
1024
}
#endif
1025

1026
#ifndef NANO_TINY
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
/* Search for a match to one of the two characters in bracket_set.  If
 * reverse is TRUE, search backwards.  Otherwise, search forwards. */
bool find_bracket_match(bool reverse, const char *bracket_set)
{
    filestruct *fileptr = openfile->current;
    const char *rev_start = NULL, *found = NULL;
    ssize_t current_y_find = openfile->current_y;

    assert(strlen(bracket_set) == 2);

    /* rev_start might end up 1 character before the start or after the
     * end of the line.  This won't be a problem because we'll skip over
     * it below in that case, and rev_start will be properly set when
     * the search continues on the previous or next line. */
    rev_start = reverse ? fileptr->data + (openfile->current_x - 1) :
	fileptr->data + (openfile->current_x + 1);

    /* Look for either of the two characters in bracket_set.  rev_start
     * can be 1 character before the start or after the end of the line.
     * In either case, just act as though no match is found. */
    while (TRUE) {
	found = ((rev_start > fileptr->data && *(rev_start - 1) ==
		'\0') || rev_start < fileptr->data) ? NULL : (reverse ?
		revstrpbrk(fileptr->data, bracket_set, rev_start) :
		strpbrk(rev_start, bracket_set));

	/* We've found a potential match. */
	if (found != NULL)
	    break;

	if (reverse) {
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
	    fileptr = fileptr->next;
	    current_y_find++;
	}

	/* Start or end of buffer reached, so get out. */
	if (fileptr == NULL)
	    return FALSE;

	rev_start = fileptr->data;
	if (reverse)
	    rev_start += strlen(fileptr->data);
    }

    /* We've definitely found something. */
    openfile->current = fileptr;
1076
    openfile->current_x = found - fileptr->data;
1077
1078
1079
1080
1081
1082
1083
1084
    openfile->placewewant = xplustabs();
    openfile->current_y = current_y_find;

    return TRUE;
}

/* Search for a match to the bracket at the current cursor position, if
 * there is one. */
1085
void do_find_bracket(void)
1086
{
1087
    filestruct *current_save;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1088
    size_t current_x_save, pww_save;
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
    const char *bracket_list = "()<>[]{}";
	/* The list of brackets we can find matches to. */
    const char *pos;
	/* The location in bracket_list of the bracket at the current
	 * cursor position. */
    char ch;
	/* The bracket at the current cursor position. */
    char wanted_ch;
	/* The bracket complementing the bracket at the current cursor
	 * position. */
    char bracket_set[3];
	/* The pair of characters in ch and wanted_ch. */
    size_t count = 1;
	/* The initial bracket count. */
    bool reverse;
	/* The direction we search. */

    assert(strlen(bracket_list) % 2 == 0);
1107

1108
    ch = openfile->current->data[openfile->current_x];
1109

1110
    if (ch == '\0' || (pos = strchr(bracket_list, ch)) == NULL) {
1111
	statusbar(_("Not a bracket"));
1112
	return;
1113
1114
    }

1115
    /* Save where we are. */
1116
1117
1118
    current_save = openfile->current;
    current_x_save = openfile->current_x;
    pww_save = openfile->placewewant;
1119
1120
1121

    /* If we're on an opening bracket, we want to search forwards for a
     * closing bracket, and if we're on a closing bracket, we want to
1122
1123
1124
1125
1126
1127
     * search backwards for an opening bracket. */
    reverse = ((pos - bracket_list) % 2 != 0);
    wanted_ch = reverse ? *(pos - 1) : *(pos + 1);
    bracket_set[0] = ch;
    bracket_set[1] = wanted_ch;
    bracket_set[2] = '\0';
1128

1129
    while (TRUE) {
1130
	if (find_bracket_match(reverse, bracket_set)) {
1131
1132
1133
1134
1135
1136
1137
1138
	    /* If we found an identical bracket, increment count.  If we
	     * found a complementary bracket, decrement it. */
	    count += (openfile->current->data[openfile->current_x] ==
		ch) ? 1 : -1;

	    /* If count is zero, we've found a matching bracket.  Update
	     * the screen and get out. */
	    if (count == 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1139
		edit_redraw(current_save, pww_save);
1140
		break;
1141
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1142
	} else {
1143
1144
	    /* We didn't find either an opening or closing bracket.
	     * Indicate this, restore where we were, and get out. */
1145
	    statusbar(_("No matching bracket"));
1146
1147
	    openfile->current = current_save;
	    openfile->current_x = current_x_save;
1148
	    openfile->placewewant = pww_save;
1149
1150
1151
1152
	    break;
	}
    }
}
1153

1154
#ifdef ENABLE_NANORC
1155
1156
1157
1158
1159
/* Indicate whether any of the history lists have changed. */
bool history_has_changed(void)
{
    return history_changed;
}
1160
#endif
1161

1162
/* Initialize the search and replace history lists. */
1163
1164
void history_init(void)
{
1165
1166
1167
1168
1169
1170
1171
1172
1173
    search_history = make_new_node(NULL);
    search_history->data = mallocstrcpy(NULL, "");
    searchage = search_history;
    searchbot = search_history;

    replace_history = make_new_node(NULL);
    replace_history->data = mallocstrcpy(NULL, "");
    replaceage = replace_history;
    replacebot = replace_history;
1174
1175
}

1176
1177
1178
1179
1180
1181
1182
1183
1184
/* Set the current position in the history list h to the bottom. */
void history_reset(const filestruct *h)
{
    if (h == search_history)
	search_history = searchbot;
    else if (h == replace_history)
	replace_history = replacebot;
}

1185
1186
1187
/* Return the first node containing the first len characters of the
 * string s in the history list, starting at h_start and ending at
 * h_end, or NULL if there isn't one. */
1188
1189
filestruct *find_history(const filestruct *h_start, const filestruct
	*h_end, const char *s, size_t len)
1190
{
1191
    const filestruct *p;
1192

1193
1194
    for (p = h_start; p != h_end->next && p != NULL; p = p->next) {
	if (strncmp(s, p->data, len) == 0)
1195
	    return (filestruct *)p;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1196
    }
1197

1198
1199
1200
    return NULL;
}

1201
1202
1203
/* Update a history list.  h should be the current position in the
 * list. */
void update_history(filestruct **h, const char *s)
1204
{
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
    filestruct **hage = NULL, **hbot = NULL, *p;

    assert(h != NULL && s != NULL);

    if (*h == search_history) {
	hage = &searchage;
	hbot = &searchbot;
    } else if (*h == replace_history) {
	hage = &replaceage;
	hbot = &replacebot;
    }
1216

1217
    assert(hage != NULL && hbot != NULL);
1218

1219
    /* If this string is already in the history, delete it. */
1220
    p = find_history(*hage, *hbot, s, (size_t)-1);
1221

1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
    if (p != NULL) {
	filestruct *foo, *bar;

	/* If the string is at the beginning, move the beginning down to
	 * the next string. */
	if (p == *hage)
	    *hage = (*hage)->next;

	/* Delete the string. */
	foo = p;
	bar = p->next;
	unlink_node(foo);
	delete_node(foo);
1235
1236
	if (bar != NULL)
	    renumber(bar);
1237
1238
    }

1239
    /* If the history is full, delete the beginning entry to make room
1240
1241
     * for the new entry at the end.  We assume that MAX_SEARCH_HISTORY
     * is greater than zero. */
1242
1243
1244
1245
1246
1247
1248
    if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) {
	filestruct *foo = *hage;

	*hage = (*hage)->next;
	unlink_node(foo);
	delete_node(foo);
	renumber(*hage);
1249
    }
1250
1251
1252
1253
1254
1255
1256

    /* Add the new entry to the end. */
    (*hbot)->data = mallocstrcpy(NULL, s);
    splice_node(*hbot, make_new_node(*hbot), (*hbot)->next);
    *hbot = (*hbot)->next;
    (*hbot)->data = mallocstrcpy(NULL, "");

1257
#ifdef ENABLE_NANORC
1258
1259
    /* Indicate that the history's been changed. */
    history_changed = TRUE;
1260
#endif
1261
1262
1263

    /* Set the current position in the list to the bottom. */
    *h = *hbot;
1264
1265
}

1266
1267
/* Move h to the string in the history list just before it, and return
 * that string.  If there isn't one, don't move h and return NULL. */
1268
char *get_history_older(filestruct **h)
1269
{
1270
    assert(h != NULL);
1271

1272
1273
1274
1275
1276
1277
    if ((*h)->prev == NULL)
	return NULL;

    *h = (*h)->prev;

    return (*h)->data;
1278
1279
}

1280
1281
/* Move h to the string in the history list just after it, and return
 * that string.  If there isn't one, don't move h and return NULL. */
1282
char *get_history_newer(filestruct **h)
1283
{
1284
    assert(h != NULL);
1285

1286
1287
    if ((*h)->next == NULL)
	return NULL;
1288

1289
1290
1291
1292
    *h = (*h)->next;

    return (*h)->data;
}
1293
1294
1295
1296

#ifndef DISABLE_TABCOMP
/* Move h to the next string that's a tab completion of the string s,
 * looking at only the first len characters of s, and return that
1297
1298
 * string.  If there isn't one, or if len is 0, don't move h and return
 * s. */
1299
char *get_history_completion(filestruct **h, const char *s, size_t len)
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
{
    assert(s != NULL);

    if (len > 0) {
	filestruct *hage = NULL, *hbot = NULL, *p;

	assert(h != NULL);

	if (*h == search_history) {
	    hage = searchage;
	    hbot = searchbot;
	} else if (*h == replace_history) {
	    hage = replaceage;
	    hbot = replacebot;
	}

	assert(hage != NULL && hbot != NULL);

1318
1319
1320
	/* Search the history list from the current position to the
	 * bottom for a match of len characters.  Skip over an exact
	 * match. */
1321
1322
	p = find_history((*h)->next, hbot, s, len);

1323
1324
1325
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, hbot, s, len);

1326
1327
1328
1329
1330
1331
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}

	/* Search the history list from the top to the current position
1332
	 * for a match of len characters.  Skip over an exact match. */
1333
1334
	p = find_history(hage, *h, s, len);

1335
1336
1337
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, *h, s, len);

1338
1339
1340
1341
1342
1343
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}
    }

1344
1345
    /* If we're here, we didn't find a match, we didn't find an inexact
     * match, or len is 0.  Return s. */
1346
    return (char *)s;
1347
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1348
#endif /* !DISABLE_TABCOMP */
1349
#endif /* !NANO_TINY */