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
    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
276
277
278
279
280
     * word isn't a  "word" character, and if we're at the end of the
     * line or the character after the word isn't a "word" character, we
     * have a whole word. */
    retval = (pos == 0 || !is_word_mbchar(p)) &&
	(word_end == strlen(buf) || !is_word_mbchar(r));
281
282
283
284
285

    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
    bool replaceall = FALSE;
685
#ifdef HAVE_REGEX_H
686
    /* The starting-line match and bol/eol regex flags. */
687
    bool begin_line = FALSE, bol_or_eol = FALSE;
688
#endif
689
#ifndef NANO_SMALL
690
    bool old_mark_set = ISSET(MARK_ISSET);
691
692
693
694
695
    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
696

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

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

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

	int i = 0;
727

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

745
746
	if (!replaceall)
	    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
747

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ch_under_cursor = current->data[current_x];
1054

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

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

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

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

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

    regexp_init(regexp_pat);
1086

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

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

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

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

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

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

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

1154
1155
1156
    return NULL;
}

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

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

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

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

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

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

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

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

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

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

    *h = (*h)->prev;

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

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

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

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

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

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

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