search.c 39.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
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
6
 *   Copyright (C) 2005-2006 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
#include "proto.h"
25

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

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

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

    assert(!regexp_compiled);
56

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

67
    regexp_compiled = TRUE;
68
    return 1;
69
70
}

71
72
/* Decompile the compiled regular expression we used in the last
 * search, if any. */
73
void regexp_cleanup(void)
74
{
75
76
    if (regexp_compiled) {
	regexp_compiled = FALSE;
77
78
	regfree(&search_regexp);
    }
79
}
80
#endif
81

82
83
/* Indicate on the statusbar that the string at str was not found by the
 * last search. */
84
85
void not_found_msg(const char *str)
{
86
87
88
    char *disp;
    int numchars;
 
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
89
    assert(str != NULL);
90

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

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

    free(disp);
98
99
}

100
101
102
103
104
/* Abort the current search or replace.  Clean up by displaying the main
 * shortcut list, updating the screen if the mark was on before, and
 * decompiling the compiled regular expression we used in the last
 * search, if any. */
void search_replace_abort(void)
105
106
{
    display_main_list();
107
#ifndef NANO_TINY
108
    if (openfile->mark_set)
109
110
	edit_refresh();
#endif
111
#ifdef HAVE_REGEX_H
112
    regexp_cleanup();
113
114
115
#endif
}

116
/* Initialize the global search and replace strings. */
Chris Allegretta's avatar
Chris Allegretta committed
117
118
void search_init_globals(void)
{
119
120
121
122
    if (last_search == NULL)
	last_search = mallocstrcpy(NULL, "");
    if (last_replace == NULL)
	last_replace = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
123
124
}

125
126
127
128
129
130
/* 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
131
 *
132
133
134
 * 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
135
{
136
    int i = 0;
137
    char *buf;
138
    static char *backupstring = NULL;
139
140
141
142
143
144
145
146
147
148
149
	/* 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;
    }
150
151
152
153

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

Chris Allegretta's avatar
Chris Allegretta committed
156
    search_init_globals();
157

158
    if (last_search[0] != '\0') {
159
	char *disp = display_string(last_search, 0, COLS / 3, FALSE);
160

161
	buf = charalloc(strlen(disp) + 7);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
162
	/* We use (COLS / 3) here because we need to see more on the
163
	 * line. */
164
	sprintf(buf, " [%s%s]", disp,
165
		(strlenpt(last_search) > COLS / 3) ? "..." : "");
166
	free(disp);
167
168
    } else
	buf = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
169

170
    /* This is now one simple call.  It just does a lot. */
171
    i = do_prompt(FALSE, replacing ? replace_list : whereis_list,
172
	backupstring,
173
#ifndef NANO_TINY
174
	&search_history,
175
#endif
176
	"%s%s%s%s%s%s", _("Search"),
177

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

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

192
#ifndef NANO_TINY
193
194
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
195
	ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
196
197
#endif
		"",
198

199
	replacing ?
200
#ifndef NANO_TINY
201
		openfile->mark_set ? _(" (to replace) in selection") :
202
#endif
203
		_(" (to replace)") : "",
204

Chris Allegretta's avatar
Chris Allegretta committed
205
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
206

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

210
211
212
    free(backupstring);
    backupstring = NULL;

213
214
215
    /* 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')) {
216
	statusbar(_("Cancelled"));
Chris Allegretta's avatar
Chris Allegretta committed
217
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
218
219
    } else {
	switch (i) {
220
221
	    case -2:		/* It's an empty string. */
	    case 0:		/* It's a new string. */
222
#ifdef HAVE_REGEX_H
223
224
225
226
227
		/* 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)
228
		    return -1;
229
#endif
230
		break;
231
#ifndef NANO_TINY
232
233
234
235
236
	    case TOGGLE_CASE_KEY:
		TOGGLE(CASE_SENSITIVE);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	    case TOGGLE_BACKWARDS_KEY:
237
		TOGGLE(BACKWARDS_SEARCH);
238
239
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
240
#endif
241
#ifdef HAVE_REGEX_H
242
	    case NANO_REGEXP_KEY:
243
244
245
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
246
#endif
247
248
249
250
	    case NANO_TOOTHERSEARCH_KEY:
		backupstring = mallocstrcpy(backupstring, answer);
		return -2;	/* Call the opposite search function. */
	    case NANO_TOGOTOLINE_KEY:
251
		do_gotolinecolumn(openfile->current->lineno,
252
253
			openfile->placewewant + 1, TRUE, TRUE, FALSE,
			TRUE);
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
265
/* Look for needle, starting at (current, current_x).  If no_sameline is
 * TRUE, skip over begin when looking for needle.  begin is the line
266
267
268
 * 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. */
269
270
271
272
273
274
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
275
{
276
    filestruct *fileptr = openfile->current;
277
    const char *rev_start = NULL, *found = NULL;
278
    size_t found_len;
279
	/* The length of the match we find. */
280
    size_t current_x_find = 0;
281
	/* The location in the current line of the match we find. */
282
    ssize_t current_y_find = openfile->current_y;
283
284
285
286
287
288

    /* 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
289
    rev_start =
290
#ifndef NANO_TINY
291
292
	ISSET(BACKWARDS_SEARCH) ?
	fileptr->data + (openfile->current_x - 1) :
293
#endif
294
	fileptr->data + (openfile->current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
295

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

300
301
	/* We've found a potential match. */
	if (found != NULL) {
302
#ifndef DISABLE_SPELLER
303
304
	    bool found_whole = FALSE;
		/* Is this potential match a whole word? */
305
#endif
306
307

	    /* Set found_len to the length of the potential match. */
308
	    found_len =
309
#ifdef HAVE_REGEX_H
310
311
		ISSET(USE_REGEXP) ?
		regmatches[0].rm_eo - regmatches[0].rm_so :
312
#endif
313
		strlen(needle);
314

315
#ifndef DISABLE_SPELLER
316
317
	    /* If we're searching for whole words, see if this potential
	     * match is a whole word. */
318
	    if (whole_word) {
319
		char *word = mallocstrncpy(NULL, found, found_len + 1);
320
321
322
323
324
325
		word[found_len] = '\0';

		found_whole = is_whole_word(found - fileptr->data,
			fileptr->data, word);
		free(word);
	    }
326
#endif
327
328
329
330
331

	    /* 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. */
332
333
334
335
336
	    if (
#ifndef DISABLE_SPELLER
		(!whole_word || found_whole) &&
#endif
		(!no_sameline || fileptr != openfile->current))
337
		break;
Chris Allegretta's avatar
Chris Allegretta committed
338
	}
339

340
	/* We've finished processing the file, so get out. */
341
	if (search_last_line) {
342
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
343
	    return FALSE;
344
	}
345

346
#ifndef NANO_TINY
347
	if (ISSET(BACKWARDS_SEARCH)) {
348
349
350
351
352
353
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
354
#ifndef NANO_TINY
355
	}
356
#endif
357

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

375
	/* We've reached the original starting line. */
376
	if (fileptr == begin)
377
	    search_last_line = TRUE;
378

379
	rev_start = fileptr->data;
380
#ifndef NANO_TINY
381
	if (ISSET(BACKWARDS_SEARCH))
382
383
384
	    rev_start += strlen(fileptr->data);
#endif
    }
385

386
387
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
388

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

402
403
404
405
    /* 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
406
    openfile->current_y = current_y_find;
407
408

    /* needle_len holds the length of needle. */
409
410
    if (needle_len != NULL)
	*needle_len = found_len;
411

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
412
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
413
414
}

415
416
/* Clear the flag indicating that a search reached the last line of the
 * file.  We need to do this just before a new search. */
417
418
419
420
421
void findnextstr_wrap_reset(void)
{
    search_last_line = FALSE;
}

Chris Allegretta's avatar
Chris Allegretta committed
422
/* Search for a string. */
423
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
424
{
425
    filestruct *fileptr = openfile->current;
426
    size_t fileptr_x = openfile->current_x;
427
    size_t old_pww = openfile->placewewant;
428
429
    int i;
    bool didfind;
Chris Allegretta's avatar
Chris Allegretta committed
430

431
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
432
    wrap_reset();
433
#endif
434

435
    i = search_init(FALSE, FALSE);
436
437
    if (i == -1)	/* Cancel, Go to Line, blank search string, or
			 * regcomp() failed. */
438
	search_replace_abort();
439
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
440
	do_replace();
441
#if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
442
443
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
444
	do_search();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
445
#endif
446

447
    if (i != 0)
448
	return;
449
450

    /* If answer is now "", copy last_search into answer. */
Chris Allegretta's avatar
Chris Allegretta committed
451
    if (answer[0] == '\0')
452
453
454
455
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

456
#ifndef NANO_TINY
457
458
    /* If answer is not "", add this search string to the search history
     * list. */
Chris Allegretta's avatar
Chris Allegretta committed
459
    if (answer[0] != '\0')
460
	update_history(&search_history, answer);
461
#endif
462

463
    findnextstr_wrap_reset();
464
465
466
467
468
    didfind = findnextstr(
#ifndef DISABLE_SPELLER
	FALSE,
#endif
	FALSE, openfile->current, openfile->current_x, answer, NULL);
469

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

499
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
500
    edit_redraw(fileptr, old_pww);
501
    search_replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
502
503
}

504
#ifndef NANO_TINY
505
/* Search for the last string without prompting. */
506
void do_research(void)
507
{
508
    filestruct *fileptr = openfile->current;
509
    size_t fileptr_x = openfile->current_x;
510
    size_t old_pww = openfile->placewewant;
511
    bool didfind;
512

513
#ifndef DISABLE_WRAPPING
514
    wrap_reset();
515
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
516

517
518
519
520
    search_init_globals();

    if (last_search[0] != '\0') {
#ifdef HAVE_REGEX_H
521
	/* Since answer is "", use last_search! */
522
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
523
	    return;
524
525
#endif

526
	findnextstr_wrap_reset();
527
528
529
530
531
532
	didfind = findnextstr(
#ifndef DISABLE_SPELLER
		FALSE,
#endif
		FALSE, openfile->current, openfile->current_x,
		last_search, NULL);
533

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

565
    openfile->placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
566
    edit_redraw(fileptr, old_pww);
567
    search_replace_abort();
568
}
569
#endif
570

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

578
    const char *c = last_replace;
579
580
581
582
    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;
583

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

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

598
599
	    /* Skip over the replacement expression. */
	    c += 2;
600

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

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

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

617
    return new_line_size;
618
}
619
#endif
620

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

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

640
    /* Create the buffer. */
641
    copy = charalloc(new_line_size);
642

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

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

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

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

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
661
662
}

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

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

706
707
708
    if (canceled != NULL)
	*canceled = FALSE;

709
    findnextstr_wrap_reset();
710
711
712
713
    while (findnextstr(
#ifndef DISABLE_SPELLER
	whole_word,
#endif
714
#ifdef HAVE_REGEX_H
715
716
717
718
719
	/* We should find a bol and/or eol regex only once per line.  If
	 * the bol_or_eol flag is set, it means that the last search
	 * found one on the beginning line, so we should skip over the
	 * beginning line when doing this search. */
	bol_or_eol
720
#else
721
	FALSE
722
#endif
723
	, real_current, *real_current_x, needle, &match_len)) {
724
	int i = 0;
725

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

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

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

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

757
	    curs_set(0);
758

759
	    do_replace_highlight(TRUE, exp_word);
760

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

763
	    do_replace_highlight(FALSE, exp_word);
764

765
	    free(exp_word);
766

767
	    curs_set(1);
768

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

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

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

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

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

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

796
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
797
798
	    /* If the mark was on and (mark_begin, mark_begin_x) was the
	     * top of it, don't change mark_begin_x. */
799
	    if (!old_mark_set || !right_side_up) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
800
801
802
803
		/* 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 +
804
			match_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
805
			openfile->mark_begin_x = openfile->current_x;
806
		    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
807
			openfile->mark_begin_x += length_change;
808
		}
809
	    }
Chris Allegretta's avatar
Chris Allegretta committed
810

811
812
813
	    /* If the mark was on and (current, current_x) was the top
	     * of it, don't change real_current_x. */
	    if (!old_mark_set || right_side_up) {
814
#endif
815
		/* Keep real_current_x in sync with the text changes. */
816
817
818
819
820
821
		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;
822
823
		    *real_current_x += length_change;
		}
824
#ifndef NANO_TINY
825
	    }
826
#endif
827

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

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

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

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

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

871
872
873
    /* 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')
874
875
	new_magicline();

Chris Allegretta's avatar
Chris Allegretta committed
876
877
878
    return numreplaced;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
887
888
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
889
	search_replace_abort();
890
	return;
Chris Allegretta's avatar
Chris Allegretta committed
891
892
    }

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

    if (i != 0)
906
	return;
Chris Allegretta's avatar
Chris Allegretta committed
907

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

917
918
    last_replace = mallocstrcpy(last_replace, "");

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

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

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

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

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

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

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

962
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
963
964

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

969
    search_replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
970
971
}

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

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

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
990
991

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

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

1002
	    do_search();
1003
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
1004
	}
1005

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

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

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

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

1031
1032
1033
1034
1035
1036
    /* 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. */
1037
    if (allow_update)
1038
	edit_refresh();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1039

Chris Allegretta's avatar
Chris Allegretta committed
1040
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1041
1042
}

1043
/* Go to the specified line and column, asking for them beforehand. */
1044
void do_gotolinecolumn_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1045
{
1046
    do_gotolinecolumn(openfile->current->lineno,
1047
	openfile->placewewant + 1, FALSE, TRUE, FALSE, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
1048
}
1049

1050
#ifndef DISABLE_SPELLER
1051
1052
1053
1054
/* Go to the line with the number specified in pos_line, the
 * x-coordinate specified in pos_x, the y-coordinate specified in pos_y,
 * and the place we want specified in pos_pww. */
void do_gotopos(ssize_t pos_line, size_t pos_x, ssize_t pos_y, size_t
1055
	pos_pww)
1056
{
1057
    /* Since do_gotolinecolumn() resets the x-coordinate but not the
1058
     * y-coordinate, set the coordinates up this way. */
1059
    openfile->current_y = pos_y;
1060
    do_gotolinecolumn(pos_line, pos_x + 1, FALSE, FALSE, TRUE, TRUE);
1061

1062
    /* Set the rest of the coordinates up. */
1063
1064
    openfile->placewewant = pos_pww;
    update_line(openfile->current, pos_x);
1065
1066
}
#endif
1067

1068
#ifndef NANO_TINY
1069
/* Search for a match to one of the two characters in bracket_set.  If
1070
1071
1072
 * reverse is TRUE, search backwards for the leftmost bracket.
 * Otherwise, search forwards for the rightmost bracket.  Return TRUE if
 * we found a match, and FALSE otherwise. */
1073
1074
1075
1076
1077
1078
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;

1079
    assert(mbstrlen(bracket_set) == 2);
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093

    /* 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 ?
1094
1095
		mbrevstrpbrk(fileptr->data, bracket_set, rev_start) :
		mbstrpbrk(rev_start, bracket_set));
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108

	/* 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++;
	}

1109
	/* We've reached the start or end of the buffer, so get out. */
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
	if (fileptr == NULL)
	    return FALSE;

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

    /* We've definitely found something. */
    openfile->current = fileptr;
1120
    openfile->current_x = found - fileptr->data;
1121
1122
1123
1124
1125
1126
1127
1128
    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. */
1129
void do_find_bracket(void)
1130
{
1131
    filestruct *current_save;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1132
    size_t current_x_save, pww_save;
1133
    const char *bracket_list = "(<[{)>]}";
1134
	/* The list of brackets we can find matches to. */
1135
    const char *ch;
1136
1137
	/* The location in bracket_list of the bracket at the current
	 * cursor position. */
1138
1139
1140
1141
1142
1143
1144
1145
    int ch_len;
	/* The length of ch in bytes. */
    const char *wanted_ch;
	/* The location in bracket_list of the bracket complementing the
	 * bracket at the current cursor position. */
    int wanted_ch_len;
	/* The length of wanted_ch in bytes. */
    char *bracket_set;
1146
	/* The pair of characters in ch and wanted_ch. */
1147
1148
    size_t bracket_halflist;
	/* The number of characters in one half of bracket_list. */
1149
1150
1151
1152
    size_t count = 1;
	/* The initial bracket count. */
    bool reverse;
	/* The direction we search. */
1153
1154
    char *found_ch;
	/* The character we find. */
1155

1156
    assert(mbstrlen(bracket_list) % 2 == 0);
1157

1158
    ch = openfile->current->data + openfile->current_x;
1159

1160
    if (ch == '\0' || (ch = mbstrchr(bracket_list, ch)) == NULL) {
1161
	statusbar(_("Not a bracket"));
1162
	return;
1163
1164
    }

1165
    /* Save where we are. */
1166
1167
1168
    current_save = openfile->current;
    current_x_save = openfile->current_x;
    pww_save = openfile->placewewant;
1169

1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
    /* If we're on an opening bracket, which must be in the first half
     * of bracket_list, we want to search forwards for a closing
     * bracket.  If we're on a closing bracket, which must be in the
     * second half of bracket_list, we want to search backwards for an
     * opening bracket. */
    bracket_halflist = mbstrlen(bracket_list) / 2;
    reverse = ((ch - bracket_list) > bracket_halflist);

    /* If we're on an opening bracket, set wanted_ch to the character
     * that's bracket_halflist characters after ch.  If we're on a
     * closing bracket, set wanted_ch to the character that's
     * bracket_halflist characters before ch. */
    wanted_ch = ch;

    while (bracket_halflist > 0) {
	if (reverse)
	    wanted_ch = bracket_list + move_mbleft(bracket_list,
		wanted_ch - bracket_list);
	else
	    wanted_ch += move_mbright(wanted_ch, 0);

	bracket_halflist--;
    }

    ch_len = parse_mbchar(ch, NULL, NULL);
    wanted_ch_len = parse_mbchar(wanted_ch, NULL, NULL);

    /* Fill bracket_set in with the values of ch and wanted_ch. */
    bracket_set = charalloc((mb_cur_max() * 2) + 1);
    strncpy(bracket_set, ch, ch_len);
    strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len);
    null_at(&bracket_set, ch_len + wanted_ch_len);

    found_ch = charalloc(mb_cur_max() + 1);
1204

1205
    while (TRUE) {
1206
	if (find_bracket_match(reverse, bracket_set)) {
1207
1208
	    /* If we found an identical bracket, increment count.  If we
	     * found a complementary bracket, decrement it. */
1209
1210
1211
	    parse_mbchar(openfile->current->data + openfile->current_x,
		found_ch, NULL);
	    count += (strncmp(found_ch, ch, ch_len) == 0) ? 1 : -1;
1212
1213
1214
1215

	    /* 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
1216
		edit_redraw(current_save, pww_save);
1217
		break;
1218
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1219
	} else {
1220
1221
	    /* We didn't find either an opening or closing bracket.
	     * Indicate this, restore where we were, and get out. */
1222
	    statusbar(_("No matching bracket"));
1223
1224
	    openfile->current = current_save;
	    openfile->current_x = current_x_save;
1225
	    openfile->placewewant = pww_save;
1226
1227
1228
	    break;
	}
    }
1229
1230
1231
1232

    /* Clean up. */
    free(bracket_set);
    free(found_ch);
1233
}
1234

1235
#ifdef ENABLE_NANORC
1236
1237
1238
1239
1240
/* Indicate whether any of the history lists have changed. */
bool history_has_changed(void)
{
    return history_changed;
}
1241
#endif
1242

1243
/* Initialize the search and replace history lists. */
1244
1245
void history_init(void)
{
1246
1247
1248
1249
1250
1251
1252
1253
1254
    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;
1255
1256
}

1257
1258
1259
1260
1261
1262
1263
1264
1265
/* 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;
}

1266
1267
1268
/* 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. */
1269
1270
filestruct *find_history(const filestruct *h_start, const filestruct
	*h_end, const char *s, size_t len)
1271
{
1272
    const filestruct *p;
1273

1274
1275
    for (p = h_start; p != h_end->next && p != NULL; p = p->next) {
	if (strncmp(s, p->data, len) == 0)
1276
	    return (filestruct *)p;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1277
    }
1278

1279
1280
1281
    return NULL;
}

1282
1283
1284
/* Update a history list.  h should be the current position in the
 * list. */
void update_history(filestruct **h, const char *s)
1285
{
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
    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;
    }
1297

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

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

1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
    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);
1316
1317
	if (bar != NULL)
	    renumber(bar);
1318
1319
    }

1320
    /* If the history is full, delete the beginning entry to make room
1321
1322
     * for the new entry at the end.  We assume that MAX_SEARCH_HISTORY
     * is greater than zero. */
1323
1324
1325
1326
1327
1328
1329
    if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) {
	filestruct *foo = *hage;

	*hage = (*hage)->next;
	unlink_node(foo);
	delete_node(foo);
	renumber(*hage);
1330
    }
1331
1332
1333
1334
1335
1336
1337

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

1338
#ifdef ENABLE_NANORC
1339
1340
    /* Indicate that the history's been changed. */
    history_changed = TRUE;
1341
#endif
1342
1343
1344

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

1347
1348
/* 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. */
1349
char *get_history_older(filestruct **h)
1350
{
1351
    assert(h != NULL);
1352

1353
1354
1355
1356
1357
1358
    if ((*h)->prev == NULL)
	return NULL;

    *h = (*h)->prev;

    return (*h)->data;
1359
1360
}

1361
1362
/* 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. */
1363
char *get_history_newer(filestruct **h)
1364
{
1365
    assert(h != NULL);
1366

1367
1368
    if ((*h)->next == NULL)
	return NULL;
1369

1370
1371
1372
1373
    *h = (*h)->next;

    return (*h)->data;
}
1374
1375
1376
1377

#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
1378
1379
 * string.  If there isn't one, or if len is 0, don't move h and return
 * s. */
1380
char *get_history_completion(filestruct **h, const char *s, size_t len)
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
{
    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);

1399
1400
1401
	/* Search the history list from the current position to the
	 * bottom for a match of len characters.  Skip over an exact
	 * match. */
1402
1403
	p = find_history((*h)->next, hbot, s, len);

1404
1405
1406
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, hbot, s, len);

1407
1408
1409
1410
1411
1412
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}

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

1416
1417
1418
	while (p != NULL && strcmp(p->data, s) == 0)
	    p = find_history(p->next, *h, s, len);

1419
1420
1421
1422
1423
1424
	if (p != NULL) {
	    *h = p;
	    return (*h)->data;
	}
    }

1425
1426
    /* If we're here, we didn't find a match, we didn't find an inexact
     * match, or len is 0.  Return s. */
1427
    return (char *)s;
1428
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1429
#endif /* !DISABLE_TABCOMP */
1430
#endif /* !NANO_TINY */