search.c 34.1 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-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
29
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
30
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
#include <ctype.h>
32
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
33
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
34
#include "proto.h"
Chris Allegretta's avatar
Chris Allegretta committed
35

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

/* 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. */
51
int regexp_init(const char *regexp)
52
{
53
54
55
56
57
    int rc = regcomp(&search_regexp, regexp, REG_EXTENDED
#ifndef NANO_SMALL
	| (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE)
#endif
	);
58
59
60
61
62
63
64
65
66

    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);
67
	return 0;
68
    }
69

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

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

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

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

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

    free(disp);
97
98
99
100
101
}

void search_abort(void)
{
    display_main_list();
102
#ifndef NANO_SMALL
103
    if (ISSET(MARK_ISSET))
104
105
	edit_refresh();
#endif
106
#ifdef HAVE_REGEX_H
107
    regexp_cleanup();
108
109
110
#endif
}

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

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

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

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

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

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

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

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

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

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

193
194
195
196
197
198
199
200
201
202
	replacing ?
#ifndef NANO_SMALL
		(ISSET(MARK_ISSET) ? _(" (to replace) in selection") :
#endif
		_(" (to replace)")
#ifndef NANO_SMALL
		)
#endif
		: "",

Chris Allegretta's avatar
Chris Allegretta committed
203
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
204

205
    /* Release buf now that we don't need it anymore. */
Chris Allegretta's avatar
Chris Allegretta committed
206
207
    free(buf);

208
209
210
    free(backupstring);
    backupstring = NULL;

211
212
213
    /* 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')) {
214
	statusbar(_("Cancelled"));
Chris Allegretta's avatar
Chris Allegretta committed
215
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
216
217
    } else {
	switch (i) {
218
	    case -2:		/* It's the same string. */
219
#ifdef HAVE_REGEX_H
220
221
222
		/* Since answer is "", use last_search! */
		if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
		    return -1;
223
#endif
224
225
226
		break;
	    case 0:		/* They entered something new. */
		last_replace[0] = '\0';
227
#ifdef HAVE_REGEX_H
228
229
		if (ISSET(USE_REGEXP) && regexp_init(answer) == 0)
		    return -1;
230
#endif
231
		break;
Chris Allegretta's avatar
Chris Allegretta committed
232
#ifndef NANO_SMALL
233
234
235
236
237
238
239
240
	    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;
241
#endif
242
#ifdef HAVE_REGEX_H
243
	    case NANO_REGEXP_KEY:
244
245
246
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
247
#endif
248
249
250
251
	    case NANO_TOOTHERSEARCH_KEY:
		backupstring = mallocstrcpy(backupstring, answer);
		return -2;	/* Call the opposite search function. */
	    case NANO_TOGOTOLINE_KEY:
252
253
		do_gotolinecolumn(current->lineno, placewewant + 1,
			TRUE, TRUE, FALSE);
254
255
				/* Put answer up on the statusbar and
				 * fall through. */
256
257
	    default:
		return -1;
Chris Allegretta's avatar
Chris Allegretta committed
258
	}
Chris Allegretta's avatar
Chris Allegretta committed
259
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
260

Chris Allegretta's avatar
Chris Allegretta committed
261
262
263
    return 0;
}

264
bool is_whole_word(size_t pos, const char *buf, const char *word)
265
{
266
267
268
    char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max());
    size_t word_end = pos + strlen(word);
    bool retval;
269

270
271
272
273
274
275
276
277
278
    assert(buf != NULL && pos <= strlen(buf) && word != NULL);

    parse_mbchar(buf + move_mbleft(buf, pos), p, NULL, NULL);
    parse_mbchar(buf + word_end, r, NULL, NULL);

    /* If we're at the beginning of the line or the character before the
     * word isn't an alphanumeric character, and if we're at the end of
     * the line or the character after the word isn't an alphanumeric
     * character, we have a whole word. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
279
    retval = (pos == 0 || !is_alnum_mbchar(p)) &&
280
281
282
283
284
285
	(word_end == strlen(buf) || !is_alnum_mbchar(r));

    free(p);
    free(r);

    return retval;
286
287
}

288
289
290
/* Look for needle, starting at (current, current_x).  If no_sameline is
 * TRUE, skip over begin when looking for needle.  begin is the line
 * where we first started searching, at column beginx.  If
291
 * can_display_wrap is TRUE, we put messages on the statusbar, wrap
292
293
294
 * 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. */
295
296
bool findnextstr(bool can_display_wrap, bool wholeword, bool
	no_sameline, const filestruct *begin, size_t beginx, const char
297
	*needle, size_t *needle_len)
Chris Allegretta's avatar
Chris Allegretta committed
298
{
Chris Allegretta's avatar
Chris Allegretta committed
299
    filestruct *fileptr = current;
300
    const char *rev_start = NULL, *found = NULL;
301
302
    size_t found_len;
	/* The length of the match we found. */
303
    size_t current_x_find = 0;
304
	/* The location of the match we found. */
305
    int current_y_find = current_y;
306
307
308
309
310
311

    /* 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
312
    rev_start =
313
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
314
	ISSET(REVERSE_SEARCH) ? fileptr->data + (current_x - 1) :
315
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
316
	fileptr->data + (current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
317

318
    /* Look for needle in searchstr. */
319
    while (TRUE) {
320
	found = strstrwrapper(fileptr->data, needle, rev_start);
Chris Allegretta's avatar
Chris Allegretta committed
321

322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
	/* 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) {
338
		char *word = mallocstrncpy(NULL, found, found_len + 1);
339
340
341
342
343
344
345
346
347
348
349
350
351
		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))
352
		break;
Chris Allegretta's avatar
Chris Allegretta committed
353
	}
354

355
	/* We've finished processing the file, so get out. */
356
357
	if (search_last_line) {
	    if (can_display_wrap)
Chris Allegretta's avatar
Chris Allegretta committed
358
		not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
359
	    return FALSE;
360
	}
361
362
363
364
365
366
367
368
369

#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH)) {
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
370
#ifndef NANO_SMALL
371
	}
372
#endif
373

374
	/* Start or end of buffer reached, so wrap around. */
375
376
	if (fileptr == NULL) {
	    if (!can_display_wrap)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
377
		return FALSE;
378

379
#ifndef NANO_SMALL
380
381
382
383
	    if (ISSET(REVERSE_SEARCH)) {
		fileptr = filebot;
		current_y_find = editwinrows - 1;
	    } else {
384
#endif
385
386
387
388
389
390
		fileptr = fileage;
		current_y_find = 0;
#ifndef NANO_SMALL
	    }
#endif

391
392
393
	    if (can_display_wrap)
		statusbar(_("Search Wrapped"));
	}
Chris Allegretta's avatar
Chris Allegretta committed
394

395
	/* Original start line reached. */
396
	if (fileptr == begin)
397
	    search_last_line = TRUE;
398

399
400
401
402
403
404
	rev_start = fileptr->data;
#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH))
	    rev_start += strlen(fileptr->data);
#endif
    }
405

406
407
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
408

409
410
411
412
413
414
415
416
417
    /* 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
418

419
420
	if (can_display_wrap)
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
421
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
422
423
    }

424
    /* Set globals now that we are sure we found something. */
425
426
    current = fileptr;
    current_x = current_x_find;
427
    current_y = current_y_find;
428
    placewewant = xplustabs();
429
430

    /* needle_len holds the length of needle. */
431
432
    if (needle_len != NULL)
	*needle_len = found_len;
433

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
434
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
435
436
}

437
438
439
440
441
void findnextstr_wrap_reset(void)
{
    search_last_line = FALSE;
}

Chris Allegretta's avatar
Chris Allegretta committed
442
/* Search for a string. */
443
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
444
{
445
    size_t old_pww = placewewant, fileptr_x = current_x;
446
447
    int i;
    bool didfind;
448
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
449

450
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
451
    wrap_reset();
452
#endif
453

454
    i = search_init(FALSE, FALSE);
455
456
    if (i == -1)	/* Cancel, Go to Line, blank search string, or
			 * regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
457
	search_abort();
458
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
459
	do_replace();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
460
#if !defined(NANO_SMALL) || defined(HAVE_REGEX_H)
461
462
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
463
	do_search();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
464
#endif
465

466
    if (i != 0)
467
	return;
468
469

    /* If answer is now "", copy last_search into answer. */
Chris Allegretta's avatar
Chris Allegretta committed
470
    if (answer[0] == '\0')
471
472
473
474
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

475
#ifndef NANO_SMALL
476
477
    /* If answer is not "", add this search string to the search history
     * list. */
Chris Allegretta's avatar
Chris Allegretta committed
478
    if (answer[0] != '\0')
479
	update_history(&search_history, answer);
480
#endif
481

482
    findnextstr_wrap_reset();
483
    didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
484
	answer, NULL);
485

486
487
488
489
490
491
492
493
494
    /* 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. */
495
496
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
497
	    didfind = findnextstr(TRUE, FALSE, TRUE, current, current_x,
498
		answer, NULL);
499
500
501
502
503
504
505
506
507
	    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
    }
508

509
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
510
    edit_redraw(fileptr, old_pww);
Chris Allegretta's avatar
Chris Allegretta committed
511
512
513
    search_abort();
}

514
#ifndef NANO_SMALL
515
/* Search for the next string without prompting. */
516
void do_research(void)
517
{
518
    size_t old_pww = placewewant, fileptr_x = current_x;
519
    bool didfind;
520
    filestruct *fileptr = current;
521

522
#ifndef DISABLE_WRAPPING
523
    wrap_reset();
524
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
525

526
527
528
529
530
    search_init_globals();

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

#ifdef HAVE_REGEX_H
531
	/* Since answer is "", use last_search! */
532
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
533
	    return;
534
535
#endif

536
	findnextstr_wrap_reset();
537
	didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
538
		last_search, NULL);
539

540
541
542
543
544
545
546
547
548
	/* 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. */
549
550
	    if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
551
		didfind = findnextstr(TRUE, FALSE, TRUE, current,
552
			current_x, answer, NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
553
554
		if (fileptr == current && fileptr_x == current_x &&
			!didfind)
555
556
557
558
559
560
561
562
		    statusbar(_("This is the only occurrence"));
	    } else {
#endif
		statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	    }
#endif
	}
563
564
565
    } else
        statusbar(_("No current search pattern"));

566
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
567
    edit_redraw(fileptr, old_pww);
568
569
    search_abort();
}
570
#endif
571

Chris Allegretta's avatar
Chris Allegretta committed
572
573
void replace_abort(void)
{
574
575
576
    /* 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. */
577
    search_abort();
578
    placewewant = xplustabs();
579
580
}

581
#ifdef HAVE_REGEX_H
582
int replace_regexp(char *string, bool create)
583
{
584
585
    /* We have a split personality here.  If create is FALSE, just
     * calculate the size of the replacement line (necessary because of
586
     * subexpressions \1 to \9 in the replaced text). */
587

588
    const char *c = last_replace;
589
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
590
    int new_size = strlen(current->data) + 1 - search_match_count;
591

Chris Allegretta's avatar
Chris Allegretta committed
592
593
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
594
    while (*c != '\0') {
595
596
	int num = (int)(*(c + 1) - '0');

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
597
598
	if (*c != '\\' || num < 1 || num > 9 ||
		num > search_regexp.re_nsub) {
599
	    if (create)
600
601
602
603
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
604
	    int i = regmatches[num].rm_eo - regmatches[num].rm_so;
605

606
607
	    /* Skip over the replacement expression. */
	    c += 2;
608

609
610
	    /* But add the length of the subexpression to new_size. */
	    new_size += i;
611

612
	    /* And if create is TRUE, append the result of the
613
	     * subexpression match to the new line. */
614
615
	    if (create) {
		charcpy(string, current->data + current_x +
616
617
			regmatches[num].rm_so, i);
		string += i;
618
619
	    }
	}
620
621
    }

622
    if (create)
Chris Allegretta's avatar
Chris Allegretta committed
623
	*string = '\0';
624
625
626

    return new_size;
}
627
#endif
628

629
char *replace_line(const char *needle)
630
{
631
    char *copy;
632
    size_t new_line_size, search_match_count;
633

634
    /* Calculate the size of the new line. */
635
#ifdef HAVE_REGEX_H
636
    if (ISSET(USE_REGEXP)) {
637
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
638
	new_line_size = replace_regexp(NULL, FALSE);
639
    } else {
640
#endif
641
642
643
644
	search_match_count = strlen(needle);
	new_line_size = strlen(current->data) - search_match_count +
	    strlen(answer) + 1;
#ifdef HAVE_REGEX_H
645
    }
646
#endif
647

648
    /* Create the buffer. */
649
    copy = charalloc(new_line_size);
650

651
    /* The head of the original line. */
652
    charcpy(copy, current->data, current_x);
653

654
    /* The replacement text. */
655
#ifdef HAVE_REGEX_H
656
657
    if (ISSET(USE_REGEXP))
	replace_regexp(copy + current_x, TRUE);
658
    else
659
#endif
660
	strcpy(copy + current_x, answer);
661

662
663
    /* The tail of the original line. */
    assert(current_x + search_match_count <= strlen(current->data));
664

665
    strcat(copy, current->data + current_x + search_match_count);
666
667

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
668
669
}

670
/* Step through each replace word and prompt user before replacing.
671
672
673
 * 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.
674
675
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
676
677
 * if needle isn't found, else the number of replacements performed.  If
 * canceled isn't NULL, set it to TRUE if we canceled. */
678
679
680
ssize_t do_replace_loop(const char *needle, const filestruct
	*real_current, size_t *real_current_x, bool wholewords, bool
	*canceled)
Chris Allegretta's avatar
Chris Allegretta committed
681
{
682
683
    ssize_t numreplaced = -1;
    size_t match_len;
684
    size_t pww_save = placewewant;
685
    bool replaceall = FALSE;
686
#ifdef HAVE_REGEX_H
687
    /* The starting-line match and bol/eol regex flags. */
688
    bool begin_line = FALSE, bol_or_eol = FALSE;
689
#endif
690
#ifndef NANO_SMALL
691
    bool old_mark_set = ISSET(MARK_ISSET);
692
693
694
695
696
    filestruct *edittop_save = edittop, *top, *bot;
    size_t top_x, bot_x;
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
Chris Allegretta's avatar
Chris Allegretta committed
697

698
    if (old_mark_set) {
699
	/* If the mark is on, partition the filestruct so that it
700
701
	 * contains only the marked text, set edittop to the top of the
	 * partition, turn the mark off, and refresh the screen. */
702
	mark_order((const filestruct **)&top, &top_x,
703
	    (const filestruct **)&bot, &bot_x, &right_side_up);
704
705
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	edittop = fileage;
706
707
708
	UNSET(MARK_ISSET);
	edit_refresh();
    }
709
#endif
710

711
712
713
    if (canceled != NULL)
	*canceled = FALSE;

714
    findnextstr_wrap_reset();
715
    while (findnextstr(TRUE, wholewords,
716
#ifdef HAVE_REGEX_H
717
718
719
720
721
	/* 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
722
#else
723
	FALSE
724
#endif
725
	, real_current, *real_current_x, needle, &match_len)) {
726
727

	int i = 0;
728

729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
#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

746
	if (!replaceall) {
747
	    edit_redraw(real_current, pww_save);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
748
	    pww_save = placewewant;
749
	}
Chris Allegretta's avatar
Chris Allegretta committed
750

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
751
752
	/* Record for the return value that we found the search
	 * string. */
753
754
	if (numreplaced == -1)
	    numreplaced = 0;
755

756
	if (!replaceall) {
757
758
759
760
	    char *exp_word;
	    size_t xpt = xplustabs();

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

764
	    curs_set(0);
765
	    do_replace_highlight(TRUE, exp_word);
766

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

769
770
	    do_replace_highlight(FALSE, exp_word);
	    free(exp_word);
771
	    curs_set(1);
772

773
774
775
	    if (i == -1) {	/* We canceled the replace. */
		if (canceled != NULL)
		    *canceled = TRUE;
776
		break;
777
	    }
778
779
	}

780
#ifdef HAVE_REGEX_H
781
	/* Set the bol_or_eol flag if we're doing a bol and/or eol regex
782
	 * replace ("^", "$", or "^$"). */
783
784
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		needle))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
785
	    bol_or_eol = TRUE;
786
787
#endif

788
	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
789
	    char *copy;
790
	    size_t length_change;
791

792
	    if (i == 2)
793
		replaceall = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
794

795
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
796

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

799
#ifndef NANO_SMALL
800
801
802
803
	    /* If the mark was on and (mark_beginbuf, mark_begin_x) was
	     * the top of it, don't change mark_beginx. */
	    if (!old_mark_set || !right_side_up) {
		/* Keep mark_beginx in sync with the text changes. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
804
805
		if (current == mark_beginbuf &&
			mark_beginx > current_x) {
806
807
808
809
810
		    if (mark_beginx < current_x + match_len)
			mark_beginx = current_x;
		    else
			mark_beginx += length_change;
		}
811
	    }
Chris Allegretta's avatar
Chris Allegretta committed
812

813
814
815
	    /* 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) {
816
#endif
817
		/* Keep real_current_x in sync with the text changes. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
818
819
		if (current == real_current &&
			current_x <= *real_current_x) {
820
821
822
823
		    if (*real_current_x < current_x + match_len)
			*real_current_x = current_x + match_len;
		    *real_current_x += length_change;
		}
824
#ifndef NANO_SMALL
825
	    }
826
#endif
827

828
	    /* Set the cursor at the last character of the replacement
829
	     * text, so searching will resume after the replacement
830
831
	     * text.  Note that current_x might be set to (size_t)-1
	     * here. */
832
#ifndef NANO_SMALL
833
	    if (!ISSET(REVERSE_SEARCH))
834
835
#endif
		current_x += match_len + length_change - 1;
836

837
	    /* Cleanup. */
838
839
840
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
841

842
843
	    if (!replaceall) {
#ifdef ENABLE_COLOR
844
		if (!ISSET(NO_COLOR_SYNTAX))
845
846
847
848
849
850
		    edit_refresh();
		else
#endif
		    update_line(current, current_x);
	    }

Chris Allegretta's avatar
Chris Allegretta committed
851
852
	    set_modified();
	    numreplaced++;
853
	}
Chris Allegretta's avatar
Chris Allegretta committed
854
855
    }

856
#ifndef NANO_SMALL
857
    if (old_mark_set) {
858
859
860
	/* 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. */
861
	unpartition_filestruct(&filepart);
862
	edittop = edittop_save;
863
	SET(MARK_ISSET);
864
865
	edit_refresh();
    }
866
867
#endif

868
869
870
871
    /* If text has been added to the magicline, make a new magicline. */
    if (filebot->data[0] != '\0')
	new_magicline();

Chris Allegretta's avatar
Chris Allegretta committed
872
873
874
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
875
/* Replace a string. */
876
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
877
{
878
    int i;
879
    filestruct *edittop_save, *begin;
880
    size_t beginx, pww_save;
881
    ssize_t numreplaced;
Chris Allegretta's avatar
Chris Allegretta committed
882

Chris Allegretta's avatar
Chris Allegretta committed
883
884
885
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
886
	return;
Chris Allegretta's avatar
Chris Allegretta committed
887
888
    }

889
    i = search_init(TRUE, FALSE);
890
891
    if (i == -1) {		/* Cancel, Go to Line, blank search
				 * string, or regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
892
	replace_abort();
893
	return;
894
    } else if (i == -2) {	/* No Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
895
	do_search();
896
	return;
897
898
899
900
901
    } else if (i == 1)		/* Case Sensitive, Backwards, or Regexp
				 * search toggle. */
	do_replace();

    if (i != 0)
902
	return;
Chris Allegretta's avatar
Chris Allegretta committed
903

904
905
906
    /* If answer is not "", add answer to the search history list and
     * copy answer into last_search. */
    if (answer[0] != '\0') {
907
#ifndef NANO_SMALL
908
	update_history(&search_history, answer);
909
#endif
Chris Allegretta's avatar
Chris Allegretta committed
910
	last_search = mallocstrcpy(last_search, answer);
911
    }
Chris Allegretta's avatar
Chris Allegretta committed
912

913
    i = statusq(FALSE, replace_list_2, last_replace,
914
#ifndef NANO_SMALL
915
	&replace_history,
916
#endif
917
	_("Replace with"));
918

919
#ifndef NANO_SMALL
920
921
922
    /* Add this replace string to the replace history list.  i == 0
     * means that the string is not "". */
    if (i == 0)
923
	update_history(&replace_history, answer);
924
925
926
927
928
929
#endif

    if (i != 0 && i != -2) {
	if (i == -1) {		/* Cancel. */
	    if (last_replace[0] != '\0')
		answer = mallocstrcpy(answer, last_replace);
930
	    statusbar(_("Cancelled"));
931
932
	}
	replace_abort();
933
	return;
934
935
936
    }

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

938
    /* Save where we are. */
939
    edittop_save = edittop;
Chris Allegretta's avatar
Chris Allegretta committed
940
    begin = current;
941
    beginx = current_x;
942
    pww_save = placewewant;
Chris Allegretta's avatar
Chris Allegretta committed
943

944
945
    numreplaced = do_replace_loop(last_search, begin, &beginx, FALSE,
	NULL);
Chris Allegretta's avatar
Chris Allegretta committed
946

947
    /* Restore where we were. */
948
    edittop = edittop_save;
Chris Allegretta's avatar
Chris Allegretta committed
949
    current = begin;
950
    current_x = beginx;
951
    placewewant = pww_save;
952

Chris Allegretta's avatar
Chris Allegretta committed
953
    renumber_all();
954
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
955
956

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

Chris Allegretta's avatar
Chris Allegretta committed
960
961
962
    replace_abort();
}

963
964
965
/* 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.
 * Note that both the line and column numbers should be one-based. */
966
967
void do_gotolinecolumn(int line, ssize_t column, bool use_answer, bool
	interactive, bool save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
968
{
969
    if (interactive) {		/* Ask for it. */
970
	char *ans = mallocstrcpy(NULL, answer);
971
	int i = statusq(FALSE, gotoline_list, use_answer ? ans : "",
Chris Allegretta's avatar
Chris Allegretta committed
972
#ifndef NANO_SMALL
973
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
974
#endif
975
		_("Enter line number, column number"));
976
977

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
978
979

	/* Cancel, or Enter with blank string. */
980
	if (i < 0) {
981
	    statusbar(_("Cancelled"));
982
	    display_main_list();
983
984
985
	    return;
	}

986
	if (i == NANO_TOOTHERWHEREIS_KEY) {
987
988
989
	    /* Keep answer up on the statusbar. */
	    search_init(TRUE, TRUE);

990
	    do_search();
991
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
992
	}
993

994
	/* Do a bounds check.  Display a warning on an out-of-bounds
995
996
	 * line or column number only if we hit Enter at the statusbar
	 * prompt. */
997
	if (!parse_line_column(answer, &line, &column) || line < 1 ||
998
		column < 1) {
999
1000
	    if (i == 0)
		statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
1001
	    display_main_list();
1002
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
1003
	}
1004
1005
    } else {
	if (line < 1)
1006
	    line = current->lineno;
1007

1008
1009
	if (column < 1)
	    column = placewewant + 1;
Chris Allegretta's avatar
Chris Allegretta committed
1010
1011
    }

1012
1013
    for (current = fileage; current->next != NULL && line > 1; line--)
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1014

1015
1016
    current_x = actual_x(current->data, column - 1);
    placewewant = column - 1;
1017

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

Chris Allegretta's avatar
Chris Allegretta committed
1022
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1023
1024
}

1025
void do_gotolinecolumn_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1026
{
1027
1028
    do_gotolinecolumn(current->lineno, placewewant + 1, FALSE, TRUE,
	FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1029
}
1030

1031
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
1032
void do_gotopos(int line, size_t pos_x, int pos_y, size_t pos_pww)
1033
{
1034
    /* Since do_gotolinecolumn() resets the x-coordinate but not the
1035
     * y-coordinate, set the coordinates up this way. */
1036
    current_y = pos_y;
1037
    do_gotolinecolumn(line, pos_x + 1, FALSE, FALSE, TRUE);
1038

1039
    /* Set the rest of the coordinates up. */
1040
    placewewant = pos_pww;
1041
1042
1043
    update_line(current, pos_x);
}
#endif
1044
1045

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
1046
void do_find_bracket(void)
1047
1048
{
    char ch_under_cursor, wanted_ch;
1049
    const char *pos, *brackets = "([{<>}])";
1050
    char regexp_pat[] = "[  ]";
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1051
    size_t current_x_save, pww_save;
1052
    int count = 1;
1053
    unsigned long flags_save;
1054
1055
1056
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
1057

1058
1059
    pos = strchr(brackets, ch_under_cursor);
    if (ch_under_cursor == '\0' || pos == NULL) {
1060
	statusbar(_("Not a bracket"));
1061
	return;
1062
1063
    }

1064
    assert(strlen(brackets) % 2 == 0);
1065

1066
    wanted_ch = brackets[(strlen(brackets) - 1) - (pos - brackets)];
1067
1068

    current_save = current;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1069
    current_x_save = current_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1070
    pww_save = placewewant;
1071
    flags_save = flags;
1072
1073
    SET(USE_REGEXP);

1074
1075
    /* Apparent near redundancy with regexp_pat[] here is needed.
     * "[][]" works, "[[]]" doesn't. */
1076
1077
    if (pos < brackets + (strlen(brackets) / 2)) {
	/* On a left bracket. */
1078
1079
1080
	regexp_pat[1] = wanted_ch;
	regexp_pat[2] = ch_under_cursor;
	UNSET(REVERSE_SEARCH);
1081
1082
    } else {
	/* On a right bracket. */
1083
1084
1085
1086
1087
1088
	regexp_pat[1] = ch_under_cursor;
	regexp_pat[2] = wanted_ch;
	SET(REVERSE_SEARCH);
    }

    regexp_init(regexp_pat);
1089

1090
    /* We constructed regexp_pat to be a valid expression. */
1091
    assert(regexp_compiled);
1092

1093
    findnextstr_wrap_reset();
1094
    while (TRUE) {
1095
	if (findnextstr(FALSE, FALSE, FALSE, current, current_x,
1096
		regexp_pat, NULL)) {
1097
	    /* Found identical bracket. */
Chris Allegretta's avatar
Chris Allegretta committed
1098
	    if (current->data[current_x] == ch_under_cursor)
1099
		count++;
1100
1101
1102
	    /* Found complementary bracket. */
	    else if (--count == 0) {
		placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1103
		edit_redraw(current_save, pww_save);
1104
		break;
1105
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1106
	} else {
1107
	    /* Didn't find either a left or right bracket. */
1108
1109
	    statusbar(_("No matching bracket"));
	    current = current_save;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1110
	    current_x = current_x_save;
Chris Allegretta's avatar
Chris Allegretta committed
1111
	    update_line(current, current_x);
1112
1113
1114
1115
	    break;
	}
    }

1116
    regexp_cleanup();
1117
    flags = flags_save;
1118
1119
}
#endif
1120

1121
1122
#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
1123
1124
1125
1126
1127
/* Indicate whether any of the history lists have changed. */
bool history_has_changed(void)
{
    return history_changed;
}
1128
#endif
1129

1130
/* Initialize the search and replace history lists. */
1131
1132
void history_init(void)
{
1133
1134
1135
1136
1137
1138
1139
1140
1141
    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;
1142
1143
}

1144
1145
1146
1147
1148
/* 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. */
filestruct *find_history(filestruct *h_start, filestruct *h_end, const
	char *s, size_t len)
1149
{
1150
    filestruct *p;
1151

1152
1153
1154
    for (p = h_start; p != h_end->next && p != NULL; p = p->next) {
	if (strncmp(s, p->data, len) == 0)
	    return p;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1155
    }
1156

1157
1158
1159
    return NULL;
}

1160
1161
1162
/* Update a history list.  h should be the current position in the
 * list. */
void update_history(filestruct **h, const char *s)
1163
{
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
    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;
    }
1175

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

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

1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
    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);
	renumber(bar);
1195
1196
    }

1197
1198
1199
1200
1201
1202
1203
1204
1205
    /* If the history is full, delete the beginning entry to make room
     * for the new entry at the end. */
    if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) {
	filestruct *foo = *hage;

	*hage = (*hage)->next;
	unlink_node(foo);
	delete_node(foo);
	renumber(*hage);
1206
    }
1207
1208
1209
1210
1211
1212
1213

    /* 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, "");

1214
#ifdef ENABLE_NANORC
1215
1216
    /* Indicate that the history's been changed. */
    history_changed = TRUE;
1217
#endif
1218
1219
1220

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

1223
1224
/* 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. */
1225
char *get_history_older(filestruct **h)
1226
{
1227
    assert(h != NULL);
1228

1229
1230
1231
1232
1233
1234
    if ((*h)->prev == NULL)
	return NULL;

    *h = (*h)->prev;

    return (*h)->data;
1235
1236
}

1237
1238
/* 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. */
1239
char *get_history_newer(filestruct **h)
1240
{
1241
    assert(h != NULL);
1242

1243
1244
    if ((*h)->next == NULL)
	return NULL;
1245

1246
1247
1248
1249
    *h = (*h)->next;

    return (*h)->data;
}
1250
1251
1252
1253

#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
1254
1255
 * string.  If there isn't one, or if len is 0, don't move h and return
 * s. */
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
char *get_history_completion(filestruct **h, char *s, size_t len)
{
    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);

	/* Search the history list from the entry after the current
	 * position to the bottom for a match of len characters. */
	p = find_history((*h)->next, hbot, s, len);

	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}

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

	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}
    }

1294
    /* If we're here, we didn't find a match, or len is 0.  Return s. */
1295
1296
1297
    return s;
}
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1298
#endif /* !NANO_SMALL && ENABLE_NANORC */