search.c 37.5 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
6
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
 *   Copyright (C) 2005 David Lawrence Ramsey                             *
Chris Allegretta's avatar
Chris Allegretta committed
7
8
 *   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 *
9
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   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
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
21
22
23
 *                                                                        *
 **************************************************************************/

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

Chris Allegretta's avatar
Chris Allegretta committed
28
29
#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 "proto.h"
Chris Allegretta's avatar
Chris Allegretta committed
34

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

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

    assert(!regexp_compiled);
59

60
61
62
63
64
65
66
    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_TINY
103
    if (openfile->mark_set)
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);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
156
	/* We use (COLS / 3) here because we need to see more on the
157
	 * line. */
158
	sprintf(buf, " [%s%s]", disp,
159
		(strlenpt(last_search) > COLS / 3) ? "..." : "");
160
	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
    i = do_prompt(FALSE, replacing ? replace_list : whereis_list,
166
	backupstring,
167
#ifndef NANO_TINY
168
	&search_history,
169
#endif
170
	"%s%s%s%s%s%s", _("Search"),
171

172
#ifndef NANO_TINY
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_TINY
187
188
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
189
	ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
190
191
#endif
		"",
192

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

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

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

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

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

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

258
259
/* Look for needle, starting at (current, current_x).  If no_sameline is
 * TRUE, skip over begin when looking for needle.  begin is the line
260
261
262
 * where we first started searching, at column begin_x.  The return
 * value specifies whether we found anything.  If we did, set needle_len
 * to the length of the string we found if it isn't NULL. */
263
264
265
266
267
268
bool findnextstr(
#ifndef DISABLE_SPELLER
	bool whole_word,
#endif
	bool no_sameline, const filestruct *begin, size_t begin_x, const
	char *needle, size_t *needle_len)
Chris Allegretta's avatar
Chris Allegretta committed
269
{
270
    filestruct *fileptr = openfile->current;
271
    const char *rev_start = NULL, *found = NULL;
272
273
    size_t found_len;
	/* The length of the match we found. */
274
    size_t current_x_find = 0;
275
	/* The location in the current line of the match we found. */
276
    ssize_t current_y_find = openfile->current_y;
277
278
279
280
281
282

    /* 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
283
    rev_start =
284
#ifndef NANO_TINY
285
286
	ISSET(BACKWARDS_SEARCH) ?
	fileptr->data + (openfile->current_x - 1) :
287
#endif
288
	fileptr->data + (openfile->current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
289

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

294
295
	/* We've found a potential match. */
	if (found != NULL) {
296
#ifndef DISABLE_SPELLER
297
298
	    bool found_whole = FALSE;
		/* Is this potential match a whole word? */
299
#endif
300
301

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

309
#ifndef DISABLE_SPELLER
310
311
	    /* If we're searching for whole words, see if this potential
	     * match is a whole word. */
312
	    if (whole_word) {
313
		char *word = mallocstrncpy(NULL, found, found_len + 1);
314
315
316
317
318
319
		word[found_len] = '\0';

		found_whole = is_whole_word(found - fileptr->data,
			fileptr->data, word);
		free(word);
	    }
320
#endif
321
322
323
324
325

	    /* 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. */
326
327
328
329
330
	    if (
#ifndef DISABLE_SPELLER
		(!whole_word || found_whole) &&
#endif
		(!no_sameline || fileptr != openfile->current))
331
		break;
Chris Allegretta's avatar
Chris Allegretta committed
332
	}
333

334
	/* We've finished processing the file, so get out. */
335
	if (search_last_line) {
336
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
337
	    return FALSE;
338
	}
339

340
#ifndef NANO_TINY
341
	if (ISSET(BACKWARDS_SEARCH)) {
342
343
344
345
346
347
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
348
#ifndef NANO_TINY
349
	}
350
#endif
351

352
353
	/* We've reached the start or end of the buffer, so wrap
	 * around. */
354
	if (fileptr == NULL) {
355
#ifndef NANO_TINY
356
	    if (ISSET(BACKWARDS_SEARCH)) {
357
		fileptr = openfile->filebot;
358
359
		current_y_find = editwinrows - 1;
	    } else {
360
#endif
361
		fileptr = openfile->fileage;
362
		current_y_find = 0;
363
#ifndef NANO_TINY
364
365
	    }
#endif
366
	    statusbar(_("Search Wrapped"));
367
	}
Chris Allegretta's avatar
Chris Allegretta committed
368

369
	/* We've reached the original starting line. */
370
	if (fileptr == begin)
371
	    search_last_line = TRUE;
372

373
	rev_start = fileptr->data;
374
#ifndef NANO_TINY
375
	if (ISSET(BACKWARDS_SEARCH))
376
377
378
	    rev_start += strlen(fileptr->data);
#endif
    }
379

380
381
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
382

383
384
    /* Ensure we haven't wrapped around again! */
    if (search_last_line &&
385
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
386
387
	((!ISSET(BACKWARDS_SEARCH) && current_x_find > begin_x) ||
	(ISSET(BACKWARDS_SEARCH) && current_x_find < begin_x))
388
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
389
	current_x_find > begin_x
390
391
#endif
	) {
392
	not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
393
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
394
395
    }

396
397
398
399
    /* We've definitely found something. */
    openfile->current = fileptr;
    openfile->current_x = current_x_find;
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
400
    openfile->current_y = current_y_find;
401
402

    /* needle_len holds the length of needle. */
403
404
    if (needle_len != NULL)
	*needle_len = found_len;
405

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
406
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
407
408
}

409
410
411
412
413
void findnextstr_wrap_reset(void)
{
    search_last_line = FALSE;
}

Chris Allegretta's avatar
Chris Allegretta committed
414
/* Search for a string. */
415
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
416
{
417
    filestruct *fileptr = openfile->current;
418
    size_t fileptr_x = openfile->current_x;
419
    size_t old_pww = openfile->placewewant;
420
421
    int i;
    bool didfind;
Chris Allegretta's avatar
Chris Allegretta committed
422

423
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
424
    wrap_reset();
425
#endif
426

427
    i = search_init(FALSE, FALSE);
428
429
    if (i == -1)	/* Cancel, Go to Line, blank search string, or
			 * regcomp() failed. */
Chris Allegretta's avatar
Chris Allegretta committed
430
	search_abort();
431
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
432
	do_replace();
433
#if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
434
435
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
436
	do_search();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
437
#endif
438

439
    if (i != 0)
440
	return;
441
442

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

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

455
    findnextstr_wrap_reset();
456
457
458
459
460
    didfind = findnextstr(
#ifndef DISABLE_SPELLER
	FALSE,
#endif
	FALSE, openfile->current, openfile->current_x, answer, NULL);
461

462
463
    /* Check to see if there's only one occurrence of the string and
     * we're on it now. */
464
465
    if (fileptr == openfile->current && fileptr_x ==
	openfile->current_x && didfind) {
466
467
468
469
470
471
#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. */
472
473
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
474
475
476
477
478
	    didfind = findnextstr(
#ifndef DISABLE_SPELLER
		FALSE,
#endif
		TRUE, openfile->current,
479
480
481
		openfile->current_x, answer, NULL);
	    if (fileptr == openfile->current && fileptr_x ==
		openfile->current_x && !didfind)
482
483
484
485
486
487
488
489
		statusbar(_("This is the only occurrence"));
	} else {
#endif
	    statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	}
#endif
    }
490

491
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
492
    edit_redraw(fileptr, old_pww);
Chris Allegretta's avatar
Chris Allegretta committed
493
494
495
    search_abort();
}

496
#ifndef NANO_TINY
497
/* Search for the next string without prompting. */
498
void do_research(void)
499
{
500
    filestruct *fileptr = openfile->current;
501
    size_t fileptr_x = openfile->current_x;
502
    size_t old_pww = openfile->placewewant;
503
    bool didfind;
504

505
#ifndef DISABLE_WRAPPING
506
    wrap_reset();
507
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
508

509
510
511
512
    search_init_globals();

    if (last_search[0] != '\0') {
#ifdef HAVE_REGEX_H
513
	/* Since answer is "", use last_search! */
514
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
515
	    return;
516
517
#endif

518
	findnextstr_wrap_reset();
519
520
521
522
523
524
	didfind = findnextstr(
#ifndef DISABLE_SPELLER
		FALSE,
#endif
		FALSE, openfile->current, openfile->current_x,
		last_search, NULL);
525

526
527
	/* Check to see if there's only one occurrence of the string and
	 * we're on it now. */
528
529
	if (fileptr == openfile->current && fileptr_x ==
		openfile->current_x && didfind) {
530
531
532
533
534
535
#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. */
536
537
	    if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
538
539
540
541
542
543
		didfind = findnextstr(
#ifndef DISABLE_SPELLER
			FALSE,
#endif
			TRUE, openfile->current, openfile->current_x,
			answer, NULL);
544
545
		if (fileptr == openfile->current && fileptr_x ==
			openfile->current_x && !didfind)
546
547
548
549
550
551
552
553
		    statusbar(_("This is the only occurrence"));
	    } else {
#endif
		statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	    }
#endif
	}
554
555
556
    } else
        statusbar(_("No current search pattern"));

557
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
558
    edit_redraw(fileptr, old_pww);
559
560
    search_abort();
}
561
#endif
562

Chris Allegretta's avatar
Chris Allegretta committed
563
564
void replace_abort(void)
{
565
    /* For now, we do the same thing as search_abort(). */
566
    search_abort();
567
568
}

569
#ifdef HAVE_REGEX_H
570
int replace_regexp(char *string, bool create)
571
{
572
573
    /* We have a split personality here.  If create is FALSE, just
     * calculate the size of the replacement line (necessary because of
574
     * subexpressions \1 to \9 in the replaced text). */
575

576
    const char *c = last_replace;
577
578
579
580
    size_t search_match_count = regmatches[0].rm_eo -
	regmatches[0].rm_so;
    size_t new_line_size = strlen(openfile->current->data) + 1 -
	search_match_count;
581

Chris Allegretta's avatar
Chris Allegretta committed
582
583
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
584
    while (*c != '\0') {
585
586
	int num = (int)(*(c + 1) - '0');

587
588
	if (*c != '\\' || num < 1 || num > 9 || num >
		search_regexp.re_nsub) {
589
	    if (create)
590
591
		*string++ = *c;
	    c++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
592
	    new_line_size++;
593
	} else {
594
	    size_t i = regmatches[num].rm_eo - regmatches[num].rm_so;
595

596
597
	    /* Skip over the replacement expression. */
	    c += 2;
598

599
	    /* But add the length of the subexpression to new_size. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
600
	    new_line_size += i;
601

602
	    /* And if create is TRUE, append the result of the
603
	     * subexpression match to the new line. */
604
	    if (create) {
605
606
		strncpy(string, openfile->current->data +
			openfile->current_x + regmatches[num].rm_so, i);
607
		string += i;
608
609
	    }
	}
610
611
    }

612
    if (create)
Chris Allegretta's avatar
Chris Allegretta committed
613
	*string = '\0';
614

615
    return new_line_size;
616
}
617
#endif
618

619
char *replace_line(const char *needle)
620
{
621
    char *copy;
622
    size_t new_line_size, search_match_count;
623

624
    /* Calculate the size of the new line. */
625
#ifdef HAVE_REGEX_H
626
    if (ISSET(USE_REGEXP)) {
627
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
628
	new_line_size = replace_regexp(NULL, FALSE);
629
    } else {
630
#endif
631
	search_match_count = strlen(needle);
632
633
	new_line_size = strlen(openfile->current->data) -
		search_match_count + strlen(answer) + 1;
634
#ifdef HAVE_REGEX_H
635
    }
636
#endif
637

638
    /* Create the buffer. */
639
    copy = charalloc(new_line_size);
640

641
    /* The head of the original line. */
642
    strncpy(copy, openfile->current->data, openfile->current_x);
643

644
    /* The replacement text. */
645
#ifdef HAVE_REGEX_H
646
    if (ISSET(USE_REGEXP))
647
	replace_regexp(copy + openfile->current_x, TRUE);
648
    else
649
#endif
650
	strcpy(copy + openfile->current_x, answer);
651

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

655
656
    strcat(copy, openfile->current->data + openfile->current_x +
	search_match_count);
657
658

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
659
660
}

661
/* Step through each replace word and prompt user before replacing.
662
663
664
 * 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.
665
666
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
667
668
 * if needle isn't found, else the number of replacements performed.  If
 * canceled isn't NULL, set it to TRUE if we canceled. */
669
670
671
672
673
674
ssize_t do_replace_loop(
#ifndef DISABLE_SPELLER
	bool whole_word,
#endif
	bool *canceled, const filestruct *real_current, size_t
	*real_current_x, const char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
675
{
676
677
    ssize_t numreplaced = -1;
    size_t match_len;
678
    bool replaceall = FALSE;
679
#ifdef HAVE_REGEX_H
680
    /* The starting-line match and bol/eol regex flags. */
681
    bool begin_line = FALSE, bol_or_eol = FALSE;
682
#endif
683
#ifndef NANO_TINY
684
685
    bool old_mark_set = openfile->mark_set;
    filestruct *edittop_save = openfile->edittop, *top, *bot;
686
687
    size_t top_x, bot_x;
    bool right_side_up = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
688
	/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
689
	 * FALSE if (current, current_x) is. */
Chris Allegretta's avatar
Chris Allegretta committed
690

691
    if (old_mark_set) {
692
	/* If the mark is on, partition the filestruct so that it
693
694
	 * contains only the marked text, set edittop to the top of the
	 * partition, turn the mark off, and refresh the screen. */
695
	mark_order((const filestruct **)&top, &top_x,
696
	    (const filestruct **)&bot, &bot_x, &right_side_up);
697
	filepart = partition_filestruct(top, top_x, bot, bot_x);
698
699
	openfile->edittop = openfile->fileage;
	openfile->mark_set = FALSE;
700
701
	edit_refresh();
    }
702
#endif
703

704
705
706
    if (canceled != NULL)
	*canceled = FALSE;

707
    findnextstr_wrap_reset();
708
709
710
711
    while (findnextstr(
#ifndef DISABLE_SPELLER
	whole_word,
#endif
712
#ifdef HAVE_REGEX_H
713
714
715
716
717
	/* 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
718
#else
719
	FALSE
720
#endif
721
	, real_current, *real_current_x, needle, &match_len)) {
722
	int i = 0;
723

724
725
726
727
728
#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. */
729
730
	if (bol_or_eol && begin_line && openfile->current ==
		real_current)
731
732
733
734
735
	    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 {
736
	    if (openfile->current == real_current)
737
738
739
740
741
		begin_line = TRUE;
	    bol_or_eol = FALSE;
	}
#endif

742
743
	if (!replaceall)
	    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
744

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
745
	/* Indicate that we found the search string. */
746
747
	if (numreplaced == -1)
	    numreplaced = 0;
748

749
	if (!replaceall) {
750
	    size_t xpt = xplustabs();
751
752
753
	    char *exp_word = display_string(openfile->current->data,
		xpt, strnlenpt(openfile->current->data,
		openfile->current_x + match_len) - xpt, FALSE);
754

755
	    curs_set(0);
756

757
	    do_replace_highlight(TRUE, exp_word);
758

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

761
	    do_replace_highlight(FALSE, exp_word);
762

763
	    free(exp_word);
764

765
	    curs_set(1);
766

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

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

782
	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
783
	    char *copy;
784
	    size_t length_change;
785

786
	    if (i == 2)
787
		replaceall = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
788

789
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
790

791
792
	    length_change = strlen(copy) -
		strlen(openfile->current->data);
Chris Allegretta's avatar
Chris Allegretta committed
793

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

809
810
811
	    /* 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) {
812
#endif
813
		/* Keep real_current_x in sync with the text changes. */
814
815
816
817
818
819
		if (openfile->current == real_current &&
			openfile->current_x <= *real_current_x) {
		    if (*real_current_x <
			openfile->current_x + match_len)
			*real_current_x = openfile->current_x +
				match_len;
820
821
		    *real_current_x += length_change;
		}
822
#ifndef NANO_TINY
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_TINY
831
	    if (!ISSET(BACKWARDS_SEARCH))
832
#endif
833
		openfile->current_x += match_len + length_change - 1;
834

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

840
841
	    if (!replaceall) {
#ifdef ENABLE_COLOR
842
843
844
845
		/* If color syntaxes are available and turned on, we
		 * need to call edit_refresh(). */
		if (openfile->colorstrings != NULL &&
			!ISSET(NO_COLOR_SYNTAX))
846
847
848
		    edit_refresh();
		else
#endif
849
		    update_line(openfile->current, openfile->current_x);
850
851
	    }

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
874
875
876
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
877
/* Replace a string. */
878
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
879
{
880
    filestruct *edittop_save, *begin;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
881
    size_t begin_x, pww_save;
882
    ssize_t numreplaced;
883
    int i;
Chris Allegretta's avatar
Chris Allegretta committed
884

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

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

    if (i != 0)
904
	return;
Chris Allegretta's avatar
Chris Allegretta committed
905

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

915
916
    last_replace = mallocstrcpy(last_replace, "");

917
    i = do_prompt(FALSE, replace_list_2, last_replace,
918
#ifndef NANO_TINY
919
	&replace_history,
920
#endif
921
	_("Replace with"));
922

923
#ifndef NANO_TINY
924
925
926
    /* Add this replace string to the replace history list.  i == 0
     * means that the string is not "". */
    if (i == 0)
927
	update_history(&replace_history, answer);
928
929
930
931
932
933
#endif

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

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

942
    /* Save where we are. */
943
944
    edittop_save = openfile->edittop;
    begin = openfile->current;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
945
    begin_x = openfile->current_x;
946
    pww_save = openfile->placewewant;
Chris Allegretta's avatar
Chris Allegretta committed
947

948
949
950
951
952
    numreplaced = do_replace_loop(
#ifndef DISABLE_SPELLER
	FALSE,
#endif
	NULL, begin, &begin_x, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
953

954
    /* Restore where we were. */
955
956
    openfile->edittop = edittop_save;
    openfile->current = begin;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
957
    openfile->current_x = begin_x;
958
    openfile->placewewant = pww_save;
959

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

    if (numreplaced >= 0)
963
964
965
	statusbar(P_("Replaced %lu occurrence",
		"Replaced %lu occurrences", (unsigned long)numreplaced),
		(unsigned long)numreplaced);
Chris Allegretta's avatar
Chris Allegretta committed
966

Chris Allegretta's avatar
Chris Allegretta committed
967
968
969
    replace_abort();
}

970
971
/* 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.
972
973
 * Update the screen afterwards if allow_update is TRUE.  Note that both
 * the line and column numbers should be one-based. */
974
void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
975
	bool interactive, bool save_pos, bool allow_update)
Chris Allegretta's avatar
Chris Allegretta committed
976
{
977
    if (interactive) {
978
	char *ans = mallocstrcpy(NULL, answer);
979
980

	/* Ask for it. */
981
	int i = do_prompt(FALSE, gotoline_list, use_answer ? ans : "",
982
#ifndef NANO_TINY
983
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
984
#endif
985
		_("Enter line number, column number"));
986
987

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
988
989

	/* Cancel, or Enter with blank string. */
990
	if (i < 0) {
991
	    statusbar(_("Cancelled"));
992
	    display_main_list();
993
994
995
	    return;
	}

996
	if (i == NANO_TOOTHERWHEREIS_KEY) {
997
998
999
	    /* Keep answer up on the statusbar. */
	    search_init(TRUE, TRUE);

1000
	    do_search();
1001
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
1002
	}
1003

1004
	/* Do a bounds check.  Display a warning on an out-of-bounds
1005
1006
	 * line or column number only if we hit Enter at the statusbar
	 * prompt. */
1007
	if (!parse_line_column(answer, &line, &column) || line < 1 ||
1008
		column < 1) {
1009
1010
	    if (i == 0)
		statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
1011
	    display_main_list();
1012
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
1013
	}
1014
1015
    } else {
	if (line < 1)
1016
	    line = openfile->current->lineno;
1017

1018
	if (column < 1)
1019
	    column = openfile->placewewant + 1;
Chris Allegretta's avatar
Chris Allegretta committed
1020
1021
    }

1022
    for (openfile->current = openfile->fileage;
1023
	openfile->current != openfile->filebot && line > 1; line--)
1024
	openfile->current = openfile->current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1025

1026
1027
    openfile->current_x = actual_x(openfile->current->data, column - 1);
    openfile->placewewant = column - 1;
1028

1029
1030
1031
1032
1033
1034
    /* Put the top line of the edit window in range of the current line.
     * If save_pos is TRUE, don't change the cursor position when doing
     * it. */
    edit_update(save_pos ? NONE : CENTER);

    /* If allow_update is TRUE, update the screen. */
1035
    if (allow_update)
1036
	edit_refresh();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1037

Chris Allegretta's avatar
Chris Allegretta committed
1038
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1039
1040
}

1041
void do_gotolinecolumn_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1042
{
1043
    do_gotolinecolumn(openfile->current->lineno,
1044
	openfile->placewewant + 1, FALSE, TRUE, FALSE, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
1045
}
1046

1047
#ifndef DISABLE_SPELLER
1048
1049
void do_gotopos(ssize_t line, size_t pos_x, ssize_t pos_y, size_t
	pos_pww)
1050
{
1051
    /* Since do_gotolinecolumn() resets the x-coordinate but not the
1052
     * y-coordinate, set the coordinates up this way. */
1053
    openfile->current_y = pos_y;
1054
    do_gotolinecolumn(line, pos_x + 1, FALSE, FALSE, TRUE, TRUE);
1055

1056
    /* Set the rest of the coordinates up. */
1057
1058
    openfile->placewewant = pos_pww;
    update_line(openfile->current, pos_x);
1059
1060
}
#endif
1061

1062
#ifndef NANO_TINY
1063
/* Search for a match to one of the two characters in bracket_set.  If
1064
1065
 * reverse is TRUE, search backwards.  Otherwise, search forwards.
 * Return TRUE if we found a match, or FALSE otherwise. */
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
bool find_bracket_match(bool reverse, const char *bracket_set)
{
    filestruct *fileptr = openfile->current;
    const char *rev_start = NULL, *found = NULL;
    ssize_t current_y_find = openfile->current_y;

    assert(strlen(bracket_set) == 2);

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

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

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

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

1102
	/* We've reached the start or end of the buffer, so get out. */
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
	if (fileptr == NULL)
	    return FALSE;

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

    /* We've definitely found something. */
    openfile->current = fileptr;
1113
    openfile->current_x = found - fileptr->data;
1114
1115
1116
1117
1118
1119
1120
1121
    openfile->placewewant = xplustabs();
    openfile->current_y = current_y_find;

    return TRUE;
}

/* Search for a match to the bracket at the current cursor position, if
 * there is one. */
1122
void do_find_bracket(void)
1123
{
1124
    filestruct *current_save;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1125
    size_t current_x_save, pww_save;
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
    const char *bracket_list = "()<>[]{}";
	/* The list of brackets we can find matches to. */
    const char *pos;
	/* The location in bracket_list of the bracket at the current
	 * cursor position. */
    char ch;
	/* The bracket at the current cursor position. */
    char wanted_ch;
	/* The bracket complementing the bracket at the current cursor
	 * position. */
    char bracket_set[3];
	/* The pair of characters in ch and wanted_ch. */
    size_t count = 1;
	/* The initial bracket count. */
    bool reverse;
	/* The direction we search. */

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

1145
    ch = openfile->current->data[openfile->current_x];
1146

1147
    if (ch == '\0' || (pos = strchr(bracket_list, ch)) == NULL) {
1148
	statusbar(_("Not a bracket"));
1149
	return;
1150
1151
    }

1152
    /* Save where we are. */
1153
1154
1155
    current_save = openfile->current;
    current_x_save = openfile->current_x;
    pww_save = openfile->placewewant;
1156
1157
1158

    /* If we're on an opening bracket, we want to search forwards for a
     * closing bracket, and if we're on a closing bracket, we want to
1159
1160
1161
1162
1163
1164
     * search backwards for an opening bracket. */
    reverse = ((pos - bracket_list) % 2 != 0);
    wanted_ch = reverse ? *(pos - 1) : *(pos + 1);
    bracket_set[0] = ch;
    bracket_set[1] = wanted_ch;
    bracket_set[2] = '\0';
1165

1166
    while (TRUE) {
1167
	if (find_bracket_match(reverse, bracket_set)) {
1168
1169
1170
1171
1172
1173
1174
1175
	    /* If we found an identical bracket, increment count.  If we
	     * found a complementary bracket, decrement it. */
	    count += (openfile->current->data[openfile->current_x] ==
		ch) ? 1 : -1;

	    /* If count is zero, we've found a matching bracket.  Update
	     * the screen and get out. */
	    if (count == 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1176
		edit_redraw(current_save, pww_save);
1177
		break;
1178
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1179
	} else {
1180
1181
	    /* We didn't find either an opening or closing bracket.
	     * Indicate this, restore where we were, and get out. */
1182
	    statusbar(_("No matching bracket"));
1183
1184
	    openfile->current = current_save;
	    openfile->current_x = current_x_save;
1185
	    openfile->placewewant = pww_save;
1186
1187
1188
1189
	    break;
	}
    }
}
1190

1191
#ifdef ENABLE_NANORC
1192
1193
1194
1195
1196
/* Indicate whether any of the history lists have changed. */
bool history_has_changed(void)
{
    return history_changed;
}
1197
#endif
1198

1199
/* Initialize the search and replace history lists. */
1200
1201
void history_init(void)
{
1202
1203
1204
1205
1206
1207
1208
1209
1210
    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;
1211
1212
}

1213
1214
1215
1216
1217
1218
1219
1220
1221
/* Set the current position in the history list h to the bottom. */
void history_reset(const filestruct *h)
{
    if (h == search_history)
	search_history = searchbot;
    else if (h == replace_history)
	replace_history = replacebot;
}

1222
1223
1224
/* 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. */
1225
1226
filestruct *find_history(const filestruct *h_start, const filestruct
	*h_end, const char *s, size_t len)
1227
{
1228
    const filestruct *p;
1229

1230
1231
    for (p = h_start; p != h_end->next && p != NULL; p = p->next) {
	if (strncmp(s, p->data, len) == 0)
1232
	    return (filestruct *)p;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1233
    }
1234

1235
1236
1237
    return NULL;
}

1238
1239
1240
/* Update a history list.  h should be the current position in the
 * list. */
void update_history(filestruct **h, const char *s)
1241
{
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
    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;
    }
1253

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

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

1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
    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);
1272
1273
	if (bar != NULL)
	    renumber(bar);
1274
1275
    }

1276
    /* If the history is full, delete the beginning entry to make room
1277
1278
     * for the new entry at the end.  We assume that MAX_SEARCH_HISTORY
     * is greater than zero. */
1279
1280
1281
1282
1283
1284
1285
    if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) {
	filestruct *foo = *hage;

	*hage = (*hage)->next;
	unlink_node(foo);
	delete_node(foo);
	renumber(*hage);
1286
    }
1287
1288
1289
1290
1291
1292
1293

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

1294
#ifdef ENABLE_NANORC
1295
1296
    /* Indicate that the history's been changed. */
    history_changed = TRUE;
1297
#endif
1298
1299
1300

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

1303
1304
/* 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. */
1305
char *get_history_older(filestruct **h)
1306
{
1307
    assert(h != NULL);
1308

1309
1310
1311
1312
1313
1314
    if ((*h)->prev == NULL)
	return NULL;

    *h = (*h)->prev;

    return (*h)->data;
1315
1316
}

1317
1318
/* 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. */
1319
char *get_history_newer(filestruct **h)
1320
{
1321
    assert(h != NULL);
1322

1323
1324
    if ((*h)->next == NULL)
	return NULL;
1325

1326
1327
1328
1329
    *h = (*h)->next;

    return (*h)->data;
}
1330
1331
1332
1333

#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
1334
1335
 * string.  If there isn't one, or if len is 0, don't move h and return
 * s. */
1336
char *get_history_completion(filestruct **h, const char *s, size_t len)
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
{
    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);

1355
1356
1357
	/* Search the history list from the current position to the
	 * bottom for a match of len characters.  Skip over an exact
	 * match. */
1358
1359
	p = find_history((*h)->next, hbot, s, len);

1360
1361
1362
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, hbot, s, len);

1363
1364
1365
1366
1367
1368
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}

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

1372
1373
1374
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, *h, s, len);

1375
1376
1377
1378
1379
1380
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}
    }

1381
1382
    /* If we're here, we didn't find a match, we didn't find an inexact
     * match, or len is 0.  Return s. */
1383
    return (char *)s;
1384
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1385
#endif /* !DISABLE_TABCOMP */
1386
#endif /* !NANO_TINY */