search.c 34 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();
460
461
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
462
	do_search();
463

464
    if (i != 0)
465
	return;
466
467

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

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

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

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

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

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

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

524
525
526
527
528
    search_init_globals();

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

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

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

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

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

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

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

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

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

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

604
605
	    /* Skip over the replacement expression. */
	    c += 2;
606

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

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

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

    return new_size;
}
625
#endif
626

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

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

646
    /* Create the buffer. */
647
    copy = charalloc(new_line_size);
648

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

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

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

663
    strcat(copy, current->data + current_x + search_match_count);
664
665

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
666
667
}

668
/* Step through each replace word and prompt user before replacing.
669
670
671
 * 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.
672
673
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
674
675
 * if needle isn't found, else the number of replacements performed.  If
 * canceled isn't NULL, set it to TRUE if we canceled. */
676
677
678
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
679
{
680
681
    ssize_t numreplaced = -1;
    size_t match_len;
682
    size_t pww_save = placewewant;
683
    bool replaceall = FALSE;
684
#ifdef HAVE_REGEX_H
685
    /* The starting-line match and bol/eol regex flags. */
686
    bool begin_line = FALSE, bol_or_eol = FALSE;
687
#endif
688
#ifndef NANO_SMALL
689
    bool old_mark_set = ISSET(MARK_ISSET);
690
691
692
693
694
    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
695

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

709
710
711
    if (canceled != NULL)
	*canceled = FALSE;

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

	int i = 0;
726

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

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

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

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

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

762
	    curs_set(0);
763
	    do_replace_highlight(TRUE, exp_word);
764

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

767
768
	    do_replace_highlight(FALSE, exp_word);
	    free(exp_word);
769
	    curs_set(1);
770

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

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

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

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

793
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
794

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

797
#ifndef NANO_SMALL
798
799
800
801
	    /* 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
802
803
		if (current == mark_beginbuf &&
			mark_beginx > current_x) {
804
805
806
807
808
		    if (mark_beginx < current_x + match_len)
			mark_beginx = current_x;
		    else
			mark_beginx += length_change;
		}
809
	    }
Chris Allegretta's avatar
Chris Allegretta committed
810

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

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

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

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

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

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

866
867
868
869
    /* 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
870
871
872
    return numreplaced;
}

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

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

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

    if (i != 0)
900
	return;
Chris Allegretta's avatar
Chris Allegretta committed
901

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

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
951
    renumber_all();
952
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
953
954

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

Chris Allegretta's avatar
Chris Allegretta committed
958
959
960
    replace_abort();
}

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

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
976
977

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

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

988
	    do_search();
989
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
990
	}
991

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

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

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

1013
1014
    current_x = actual_x(current->data, column - 1);
    placewewant = column - 1;
1015

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

Chris Allegretta's avatar
Chris Allegretta committed
1020
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1021
1022
}

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

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

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

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

    ch_under_cursor = current->data[current_x];
1055

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

1062
    assert(strlen(brackets) % 2 == 0);
1063

1064
    wanted_ch = brackets[(strlen(brackets) - 1) - (pos - brackets)];
1065
1066

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

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

    regexp_init(regexp_pat);
1087

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

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

1114
    regexp_cleanup();
1115
    flags = flags_save;
1116
1117
}
#endif
1118

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

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

1142
1143
1144
1145
1146
/* 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)
1147
{
1148
    filestruct *p;
1149

1150
1151
1152
    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
1153
    }
1154

1155
1156
1157
    return NULL;
}

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

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

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

1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
    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);
1193
1194
    }

1195
1196
1197
1198
1199
1200
1201
1202
1203
    /* 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);
1204
    }
1205
1206
1207
1208
1209
1210
1211

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

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

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

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

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

    *h = (*h)->prev;

    return (*h)->data;
1233
1234
}

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

1241
1242
    if ((*h)->next == NULL)
	return NULL;
1243

1244
1245
1246
1247
    *h = (*h)->next;

    return (*h)->data;
}
1248
1249
1250
1251

#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
1252
1253
 * string.  If there isn't one, or if len is 0, don't move h and return
 * s. */
1254
1255
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
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;
	}
    }

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