search.c 31.2 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   search.c  --  This file is part of GNU nano.                         *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999-2011, 2013-2018 Free Software Foundation, Inc.    *
5
 *   Copyright (C) 2015-2017 Benno Schulenberg                            *
6
 *                                                                        *
7
8
9
10
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
11
 *                                                                        *
12
13
14
15
 *   GNU nano 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
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
18
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
19
20
21
 *                                                                        *
 **************************************************************************/

22
#include "proto.h"
23

Chris Allegretta's avatar
Chris Allegretta committed
24
#include <string.h>
25
#ifdef DEBUG
26
#include <time.h>
27
#endif
Chris Allegretta's avatar
Chris Allegretta committed
28

29
static bool came_full_circle = FALSE;
30
		/* Have we reached the starting line again while searching? */
31
static bool regexp_compiled = FALSE;
32
		/* Have we compiled any regular expressions? */
33

34
35
/* Compile the given regular expression and store it in search_regexp.
 * Return TRUE if the expression is valid, and FALSE otherwise. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
36
bool regexp_init(const char *regexp)
37
{
38
39
	int value = regcomp(&search_regexp, fixbounds(regexp),
				NANO_REG_EXTENDED | (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE));
40

41
42
43
44
	/* If regex compilation failed, show the error message. */
	if (value != 0) {
		size_t len = regerror(value, &search_regexp, NULL, 0);
		char *str = charalloc(len);
45

46
47
48
		regerror(value, &search_regexp, str, len);
		statusline(ALERT, _("Bad regex \"%s\": %s"), regexp, str);
		free(str);
49

50
51
		return FALSE;
	}
52

53
	regexp_compiled = TRUE;
54

55
	return TRUE;
56
57
}

58
59
/* Decompile the compiled regular expression we used in the last
 * search, if any. */
60
void regexp_cleanup(void)
61
{
62
63
64
65
	if (regexp_compiled) {
		regexp_compiled = FALSE;
		regfree(&search_regexp);
	}
66
67
}

68
/* Report on the status bar that the given string was not found. */
69
70
void not_found_msg(const char *str)
{
71
72
	char *disp = display_string(str, 0, (COLS / 2) + 1, FALSE);
	size_t numchars = actual_x(disp, strnlenpt(disp, COLS / 2));
73

74
75
76
	statusline(HUSH, _("\"%.*s%s\" not found"), numchars, disp,
				(disp[numchars] == '\0') ? "" : "...");
	free(disp);
77
78
}

79
80
81
82
83
/* 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)
84
{
85
#ifndef NANO_TINY
86
87
	if (openfile->mark)
		refresh_needed = TRUE;
88
#endif
89
	regexp_cleanup();
90
91
}

92
/* Set up the system variables for a search or replace.  If use_answer
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
93
94
95
96
97
 * is TRUE, only set backupstring to answer.  Return -2 to run the
 * opposite program (search -> replace, replace -> search), return -1 if
 * the search should be canceled (due to Cancel, a blank search string,
 * 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
98
 *
99
100
101
 * 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
102
{
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
	int i = 0;
	char *buf;
	static char *backupstring = NULL;
		/* The search string we'll be using. */
	functionptrtype func;

	/* If use_answer is TRUE, set backupstring to answer and get out. */
	if (use_answer) {
		backupstring = mallocstrcpy(backupstring, answer);
		return 0;
	}

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

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

		buf = charalloc(strlen(disp) + 7);
		/* We use (COLS / 3) here because we need to see more on the line. */
		sprintf(buf, " [%s%s]", disp,
				(strlenpt(last_search) > COLS / 3) ? "..." : "");
		free(disp);
	} else
		buf = mallocstrcpy(NULL, "");

	/* This is now one simple call.  It just does a lot. */
	i = do_prompt(FALSE, FALSE,
				inhelp ? MFINDINHELP : (replacing ? MREPLACE : MWHEREIS),
				backupstring, &search_history,
				/* TRANSLATORS: This is the main search prompt. */
				edit_refresh, "%s%s%s%s%s%s", _("Search"),
				/* TRANSLATORS: The next three modify the search prompt. */
				ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : "",
				ISSET(USE_REGEXP) ? _(" [Regexp]") : "",
				ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") : "", replacing ?
141
#ifndef NANO_TINY
142
143
				/* TRANSLATORS: The next two modify the search prompt. */
				openfile->mark ? _(" (to replace) in selection") :
144
#endif
145
				_(" (to replace)") : "", buf);
Chris Allegretta's avatar
Chris Allegretta committed
146

147
148
	/* Release buf now that we don't need it anymore. */
	free(buf);
Chris Allegretta's avatar
Chris Allegretta committed
149

150
151
	free(backupstring);
	backupstring = NULL;
152

153
154
155
156
157
158
	/* If the search was cancelled, or we have a blank answer and
	 * nothing was searched for yet during this session, get out. */
	if (i == -1 || (i == -2 && *last_search == '\0')) {
		statusbar(_("Cancelled"));
		return -1;
	}
159

160
161
162
163
164
	/* If Enter was pressed, see what we got. */
	if (i == 0 || i == -2) {
		/* If an answer was given, remember it. */
		if (*answer != '\0') {
			last_search = mallocstrcpy(last_search, answer);
165
#ifdef ENABLE_HISTORIES
166
			update_history(&search_history, answer);
167
#endif
168
169
170
171
		}
		if (ISSET(USE_REGEXP) && !regexp_init(last_search))
			return -1;
		else
172
			return 0;    /* We have a valid string or regex. */
173
174
	}

175
	func = func_from_key(&i);
176

177
178
179
180
181
182
183
184
185
186
187
188
189
190
	if (func == case_sens_void) {
		TOGGLE(CASE_SENSITIVE);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	} else if (func == backwards_void) {
		TOGGLE(BACKWARDS_SEARCH);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	} else if (func == regexp_void) {
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	} else if (func == flip_replace) {
		backupstring = mallocstrcpy(backupstring, answer);
191
		return -2;    /* Call the opposite search function. */
192
	} else if (func == flip_goto) {
193
194
195
196
		do_gotolinecolumn(openfile->current->lineno,
						openfile->placewewant + 1, TRUE, TRUE);
		return 3;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
197

198
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
199
200
}

201
/* Look for needle, starting at (current, current_x).  begin is the line
202
203
204
 * where we first started searching, at column begin_x.  Return 1 when we
 * found something, 0 when nothing, and -2 on cancel.  When match_len is
 * not NULL, set it to the length of the found string, if any. */
205
int findnextstr(const char *needle, bool whole_word_only, int modus,
206
		size_t *match_len, bool skipone, const filestruct *begin, size_t begin_x)
Chris Allegretta's avatar
Chris Allegretta committed
207
{
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
	size_t found_len = strlen(needle);
		/* The length of a match -- will be recomputed for a regex. */
	int feedback = 0;
		/* When bigger than zero, show and wipe the "Searching..." message. */
	filestruct *line = openfile->current;
		/* The line that we will search through now. */
	const char *from = line->data + openfile->current_x;
		/* The point in the line from where we start searching. */
	const char *found = NULL;
		/* A pointer to the location of the match, if any. */
	size_t found_x;
		/* The x coordinate of a found occurrence. */
	time_t lastkbcheck = time(NULL);
		/* The time we last looked at the keyboard. */

	/* Set non-blocking input so that we can just peek for a Cancel. */
	disable_waiting();

	if (begin == NULL)
		came_full_circle = FALSE;

	/* Start searching through the lines, looking for the needle. */
	while (TRUE) {
		/* Glance at the keyboard once every second. */
		if (time(NULL) - lastkbcheck > 0) {
			int input = parse_kbinput(edit);

			lastkbcheck = time(NULL);

			/* Consume all waiting keystrokes until a Cancel. */
			while (input) {
				if (func_from_key(&input) == do_cancel) {
					statusbar(_("Cancelled"));
					enable_waiting();
					return -2;
				}
				input = parse_kbinput(NULL);
			}

			if (++feedback > 0)
				/* TRANSLATORS: This is shown when searching takes
				 * more than half a second. */
				statusbar(_("Searching..."));
251
		}
252

253
254
		/* When starting a new search, skip the first character, then
		 * (in either case) search for the needle in the current line. */
255
		if (skipone) {
256
257
258
			skipone = FALSE;
			if (ISSET(BACKWARDS_SEARCH) && from != line->data) {
				from = line->data + move_mbleft(line->data, from - line->data);
259
				found = strstrwrapper(line->data, needle, from);
260
261
			} else if (!ISSET(BACKWARDS_SEARCH) && *from != '\0') {
				from += move_mbright(from, 0);
262
				found = strstrwrapper(line->data, needle, from);
263
			}
264
265
		} else
			found = strstrwrapper(line->data, needle, from);
266

267
268
269
270
		if (found != NULL) {
			/* When doing a regex search, compute the length of the match. */
			if (ISSET(USE_REGEXP))
				found_len = regmatches[0].rm_eo - regmatches[0].rm_so;
271
#ifdef ENABLE_SPELLER
272
273
274
275
276
277
278
			/* When we're spell checking, a match should be a separate word;
			 * if it's not, continue looking in the rest of the line. */
			if (whole_word_only && !is_separate_word(found - line->data,
												found_len, line->data)) {
				from = found + move_mbright(found, 0);
				continue;
			}
279
#endif
280
281
282
			/* The match is valid. */
			break;
		}
283

284
285
286
287
288
		/* If we're back at the beginning, then there is no needle. */
		if (came_full_circle) {
			enable_waiting();
			return 0;
		}
289

290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
		/* Move to the previous or next line in the file. */
		if (ISSET(BACKWARDS_SEARCH))
			line = line->prev;
		else
			line = line->next;

		/* If we've reached the start or end of the buffer, wrap around;
		 * but stop when spell-checking or replacing in a region. */
		if (line == NULL) {
			if (whole_word_only || modus == INREGION) {
				enable_waiting();
				return 0;
			}

			if (ISSET(BACKWARDS_SEARCH))
				line = openfile->filebot;
			else
				line = openfile->fileage;

			if (modus == JUSTFIND) {
				statusbar(_("Search Wrapped"));
				/* Delay the "Searching..." message for at least two seconds. */
				feedback = -2;
			}
		}
Chris Allegretta's avatar
Chris Allegretta committed
315

316
317
318
		/* If we've reached the original starting line, take note. */
		if (line == begin)
			came_full_circle = TRUE;
319

320
321
322
323
324
		/* Set the starting x to the start or end of the line. */
		from = line->data;
		if (ISSET(BACKWARDS_SEARCH))
			from += strlen(line->data);
	}
325

326
	found_x = found - line->data;
327

328
	enable_waiting();
329

330
	/* Ensure that the found occurrence is not beyond the starting x. */
331
332
	if (came_full_circle && ((!ISSET(BACKWARDS_SEARCH) && (found_x > begin_x ||
						(modus == REPLACING && found_x == begin_x))) ||
333
334
						(ISSET(BACKWARDS_SEARCH) && found_x < begin_x)))
		return 0;
335

336
337
338
	/* Set the current position to point at what we found. */
	openfile->current = line;
	openfile->current_x = found_x;
339

340
341
342
	/* When requested, pass back the length of the match. */
	if (match_len != NULL)
		*match_len = found_len;
343

344
345
346
347
348
	/* Wipe the "Searching..." message and unset the suppression flag. */
	if (feedback > 0) {
		wipe_statusbar();
		suppress_cursorpos = FALSE;
	}
349

350
	return 1;
Chris Allegretta's avatar
Chris Allegretta committed
351
352
}

353
/* Ask what to search for and then go looking for it. */
354
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
355
{
356
	int i = search_init(FALSE, FALSE);
357

358
	if (i == -1)    /* Cancelled, or some other exit reason. */
359
		search_replace_abort();
360
	else if (i == -2)    /* Do a replace instead. */
361
		do_replace();
362
	else if (i == 1)    /* Toggled something. */
363
		do_search();
364

365
366
	if (i == 0)
		go_looking();
Chris Allegretta's avatar
Chris Allegretta committed
367
368
}

369
370
371
/* Search forward for a string. */
void do_search_forward(void)
{
372
373
	UNSET(BACKWARDS_SEARCH);
	do_search();
374
375
376
377
378
}

/* Search backwards for a string. */
void do_search_backward(void)
{
379
380
	SET(BACKWARDS_SEARCH);
	do_search();
381
382
}

383
384
385
386
#ifndef NANO_TINY
/* Search in the backward direction for the next occurrence. */
void do_findprevious(void)
{
387
388
	SET(BACKWARDS_SEARCH);
	do_research();
389
390
391
392
393
}

/* Search in the forward direction for the next occurrence. */
void do_findnext(void)
{
394
395
	UNSET(BACKWARDS_SEARCH);
	do_research();
396
}
397
#endif /* !NANO_TINY */
398

399
/* Search for the last string without prompting. */
400
void do_research(void)
401
{
402
#ifdef ENABLE_HISTORIES
403
404
405
406
	/* If nothing was searched for yet during this run of nano, but
	 * there is a search history, take the most recent item. */
	if (*last_search == '\0' && searchbot->prev != NULL)
		last_search = mallocstrcpy(last_search, searchbot->prev->data);
407
408
#endif

409
410
411
412
	if (*last_search == '\0') {
		statusbar(_("No current search pattern"));
		return;
	}
413

414
415
	if (ISSET(USE_REGEXP) && !regexp_init(last_search))
		return;
416

417
418
	/* Use the search-menu key bindings, to allow cancelling. */
	currmenu = MWHEREIS;
419

420
	go_looking();
421
422
423
424
425
426
}

/* Search for the global string 'last_search'.  Inform the user when
 * the string occurs only once. */
void go_looking(void)
{
427
428
	filestruct *was_current = openfile->current;
	size_t was_current_x = openfile->current_x;
429
#ifdef DEBUG
430
	clock_t start = clock();
431
#endif
432

433
	came_full_circle = FALSE;
434

435
	didfind = findnextstr(last_search, FALSE, JUSTFIND, NULL, TRUE,
436
								openfile->current, openfile->current_x);
437

438
439
440
441
442
443
444
	/* If we found something, and we're back at the exact same spot
	 * where we started searching, then this is the only occurrence. */
	if (didfind == 1 && openfile->current == was_current &&
				openfile->current_x == was_current_x)
		statusbar(_("This is the only occurrence"));
	else if (didfind == 0)
		not_found_msg(last_search);
445

446
#ifdef DEBUG
447
	statusline(HUSH, "Took: %.2f", (double)(clock() - start) / CLOCKS_PER_SEC);
448
449
#endif

450
451
	edit_redraw(was_current, CENTERING);
	search_replace_abort();
452
453
}

454
/* Calculate the size of the replacement text, taking possible
455
456
 * subexpressions \1 to \9 into account.  Return the replacement
 * text in the passed string only when create is TRUE. */
457
int replace_regexp(char *string, bool create)
458
{
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
	const char *c = answer;
	size_t replacement_size = 0;

	/* Iterate through the replacement text to handle subexpression
	 * replacement using \1, \2, \3, etc. */
	while (*c != '\0') {
		int num = (*(c + 1) - '0');

		if (*c != '\\' || num < 1 || num > 9 || num > search_regexp.re_nsub) {
			if (create)
				*string++ = *c;
			c++;
			replacement_size++;
		} else {
			size_t i = regmatches[num].rm_eo - regmatches[num].rm_so;

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

			/* But add the length of the subexpression to new_size. */
			replacement_size += i;

			/* And if create is TRUE, append the result of the
			 * subexpression match to the new line. */
			if (create) {
				strncpy(string, openfile->current->data +
										regmatches[num].rm_so, i);
				string += i;
			}
		}
489
	}
490

491
492
	if (create)
		*string = '\0';
493

494
	return replacement_size;
495
}
496

497
/* Return a copy of the current line with one needle replaced. */
498
char *replace_line(const char *needle)
499
{
500
501
502
503
504
505
506
507
508
509
510
511
	char *copy;
	size_t match_len;
	size_t new_line_size = strlen(openfile->current->data) + 1;

	/* First adjust the size of the new line for the change. */
	if (ISSET(USE_REGEXP)) {
		match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
		new_line_size += replace_regexp(NULL, FALSE) - match_len;
	} else {
		match_len = strlen(needle);
		new_line_size += strlen(answer) - match_len;
	}
512

513
514
	/* Create the buffer. */
	copy = charalloc(new_line_size);
515

516
517
	/* Copy the head of the original line. */
	strncpy(copy, openfile->current->data, openfile->current_x);
518

519
520
521
522
523
	/* Add the replacement text. */
	if (ISSET(USE_REGEXP))
		replace_regexp(copy + openfile->current_x, TRUE);
	else
		strcpy(copy + openfile->current_x, answer);
524

525
	assert(openfile->current_x + match_len <= strlen(openfile->current->data));
526

527
528
	/* Copy the tail of the original line. */
	strcat(copy, openfile->current->data + openfile->current_x + match_len);
529

530
	return copy;
Chris Allegretta's avatar
Chris Allegretta committed
531
532
}

533
534
535
/* Step through each occurrence of the search string and prompt the user
 * before replacing it.  We seek for needle, and replace it with answer.
 * The parameters real_current and real_current_x are needed in order to
536
 * allow the cursor position to be updated when a word before the cursor
537
538
 * is replaced by a shorter word.  Return -1 if needle isn't found, -2 if
 * the seeking is aborted, else the number of replacements performed. */
539
ssize_t do_replace_loop(const char *needle, bool whole_word_only,
540
		const filestruct *real_current, size_t *real_current_x)
Chris Allegretta's avatar
Chris Allegretta committed
541
{
542
543
544
	ssize_t numreplaced = -1;
	size_t match_len;
	bool replaceall = FALSE;
545
	bool skipone = ISSET(BACKWARDS_SEARCH);
546
	int modus = REPLACING;
547
#ifndef NANO_TINY
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
	filestruct *was_mark = openfile->mark;
	filestruct *top, *bot;
	size_t top_x, bot_x;
	bool right_side_up = FALSE;
		/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
		 * FALSE if (current, current_x) is. */

	/* If the mark is on, frame the region, and turn the mark off. */
	if (openfile->mark) {
		mark_order((const filestruct **)&top, &top_x,
						(const filestruct **)&bot, &bot_x, &right_side_up);
		openfile->mark = NULL;
		modus = INREGION;

		/* Start either at the top or the bottom of the marked region. */
		if (!ISSET(BACKWARDS_SEARCH)) {
			openfile->current = top;
			openfile->current_x = top_x;
		} else {
			openfile->current = bot;
			openfile->current_x = bot_x;
		}
570
	}
571
#endif /* !NANO_TINY */
572

573
	came_full_circle = FALSE;
574

575
576
577
578
579
580
581
582
583
584
585
	while (TRUE) {
		int i = 0;
		int result = findnextstr(needle, whole_word_only, modus,
						&match_len, skipone, real_current, *real_current_x);

		/* If nothing more was found, or the user aborted, stop looping. */
		if (result < 1) {
			if (result < 0)
				numreplaced = -2;  /* It's a Cancel instead of Not found. */
			break;
		}
586

587
#ifndef NANO_TINY
588
589
590
591
592
593
594
595
		/* An occurrence outside of the marked region means we're done. */
		if (was_mark && (openfile->current->lineno > bot->lineno ||
								openfile->current->lineno < top->lineno ||
								(openfile->current == bot &&
								openfile->current_x + match_len > bot_x) ||
								(openfile->current == top &&
								openfile->current_x < top_x)))
			break;
596
597
#endif

598
599
600
		/* Indicate that we found the search string. */
		if (numreplaced == -1)
			numreplaced = 0;
601

602
603
604
605
		if (!replaceall) {
			size_t from_col = xplustabs();
			size_t to_col = strnlenpt(openfile->current->data,
										openfile->current_x + match_len);
606

607
608
			/* Refresh the edit window, scrolling it if necessary. */
			edit_refresh();
609

610
			spotlight(TRUE, from_col, to_col);
611

612
613
			/* TRANSLATORS: This is a prompt. */
			i = do_yesno_prompt(TRUE, _("Replace this instance?"));
Chris Allegretta's avatar
Chris Allegretta committed
614

615
			spotlight(FALSE, from_col, to_col);
616

617
618
619
620
			if (i == -1)  /* The replacing was cancelled. */
				break;
			else if (i == 2)
				replaceall = TRUE;
621

622
623
624
625
			/* When "No" or moving backwards, the search routine should
			 * first move one character further before continuing. */
			skipone = (i == 0 || ISSET(BACKWARDS_SEARCH));
		}
626

627
628
629
		if (i == 1 || replaceall) {  /* Yes, replace it. */
			char *copy;
			size_t length_change;
630

631
#ifndef NANO_TINY
632
			add_undo(REPLACE);
633
#endif
634
			copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
635

636
			length_change = strlen(copy) - strlen(openfile->current->data);
Chris Allegretta's avatar
Chris Allegretta committed
637

638
#ifndef NANO_TINY
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
			/* If the mark was on and it was located after the cursor,
			 * then adjust its x position for any text length changes. */
			if (was_mark && !right_side_up) {
				if (openfile->current == was_mark &&
						openfile->mark_x > openfile->current_x) {
					if (openfile->mark_x < openfile->current_x + match_len)
						openfile->mark_x = openfile->current_x;
					else
						openfile->mark_x += length_change;
					bot_x = openfile->mark_x;
				}
			}

			/* If the mark was not on or it was before the cursor, then
			 * adjust the cursor's x position for any text length changes. */
			if (!was_mark || right_side_up) {
655
#endif
656
657
658
659
660
				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;
					*real_current_x += length_change;
661
#ifndef NANO_TINY
662
663
					bot_x = *real_current_x;
				}
664
#endif
665
			}
666

667
668
669
			/* Don't find the same zero-length or BOL match again. */
			if (match_len == 0 || (*needle == '^' && ISSET(USE_REGEXP)))
				skipone = TRUE;
670

671
672
673
674
			/* When moving forward, put the cursor just after the replacement
			 * text, so that searching will continue there. */
			if (!ISSET(BACKWARDS_SEARCH))
				openfile->current_x += match_len + length_change;
675

676
677
678
679
			/* Update the file size, and put the changed line into place. */
			openfile->totsize += mbstrlen(copy) - mbstrlen(openfile->current->data);
			free(openfile->current->data);
			openfile->current->data = copy;
680

681
			if (!replaceall) {
682
#ifdef ENABLE_COLOR
683
684
685
686
687
				/* When doing syntax coloring, the replacement might require
				 * a change of colors, so refresh the whole edit window. */
				if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX))
					edit_refresh();
				else
688
#endif
689
690
					update_line(openfile->current, openfile->current_x);
			}
691

692
693
694
695
			set_modified();
			as_an_at = TRUE;
			numreplaced++;
		}
696
	}
Chris Allegretta's avatar
Chris Allegretta committed
697

698
699
	if (numreplaced == -1)
		not_found_msg(needle);
700
#ifdef ENABLE_COLOR
701
702
	else if (numreplaced > 0)
		refresh_needed = TRUE;
703
#endif
704
#ifndef NANO_TINY
705
	openfile->mark = was_mark;
706
707
#endif

708
709
710
711
	/* If "automatic newline" is enabled, and text has been added to the
	 * magicline, make a new magicline. */
	if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
		new_magicline();
712

713
	return numreplaced;
Chris Allegretta's avatar
Chris Allegretta committed
714
715
}

Chris Allegretta's avatar
Chris Allegretta committed
716
/* Replace a string. */
717
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
718
{
719
720
721
722
723
724
725
726
727
	filestruct *edittop_save, *begin;
	size_t firstcolumn_save, begin_x;
	ssize_t numreplaced;
	int i;

	if (ISSET(VIEW_MODE)) {
		print_view_warning();
		return;
	}
Chris Allegretta's avatar
Chris Allegretta committed
728

729
	i = search_init(TRUE, FALSE);
730

731
	if (i == -1)    /* Cancelled, or some other exit reason. */
732
		search_replace_abort();
733
	else if (i == -2)    /* Do a search instead. */
734
		do_search();
735
	else if (i == 1)    /* Toggled something. */
736
		do_replace();
737

738
739
	if (i != 0)
		return;
Chris Allegretta's avatar
Chris Allegretta committed
740

741
742
743
	i = do_prompt(FALSE, FALSE, MREPLACEWITH, NULL, &replace_history,
				/* TRANSLATORS: This is a prompt. */
				edit_refresh, _("Replace with"));
744

745
#ifdef ENABLE_HISTORIES
746
747
748
	/* If the replace string is not "", add it to the replace history list. */
	if (i == 0)
		update_history(&replace_history, answer);
749
750
#endif

751
752
753
754
755
756
757
	/* When cancelled, or when a function was run, get out. */
	if (i == -1 || i > 0) {
		if (i == -1)
			statusbar(_("Cancelled"));
		search_replace_abort();
		return;
	}
758

759
760
761
762
763
	/* Save where we are. */
	edittop_save = openfile->edittop;
	firstcolumn_save = openfile->firstcolumn;
	begin = openfile->current;
	begin_x = openfile->current_x;
Chris Allegretta's avatar
Chris Allegretta committed
764

765
	numreplaced = do_replace_loop(last_search, FALSE, begin, &begin_x);
Chris Allegretta's avatar
Chris Allegretta committed
766

767
768
769
770
771
772
	/* Restore where we were. */
	openfile->edittop = edittop_save;
	openfile->firstcolumn = firstcolumn_save;
	openfile->current = begin;
	openfile->current_x = begin_x;
	refresh_needed = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
773

774
775
776
	if (numreplaced >= 0)
		statusline(HUSH, P_("Replaced %zd occurrence",
				"Replaced %zd occurrences", numreplaced), numreplaced);
Chris Allegretta's avatar
Chris Allegretta committed
777

778
	search_replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
779
780
}

781
782
783
/* Go to the specified line and x position. */
void goto_line_posx(ssize_t line, size_t pos_x)
{
784
785
786
	for (openfile->current = openfile->fileage; line > 1 &&
				openfile->current != openfile->filebot; line--)
		openfile->current = openfile->current->next;
787

788
789
	openfile->current_x = pos_x;
	openfile->placewewant = xplustabs();
790

791
	refresh_needed = TRUE;
792
793
}

794
/* Go to the specified line and column, or ask for them if interactive
795
 * is TRUE.  In the latter case also update the screen afterwards.
796
 * Note that both the line and column number should be one-based. */
797
void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
798
		bool interactive)
Chris Allegretta's avatar
Chris Allegretta committed
799
{
800
801
802
803
804
805
806
807
808
809
810
811
	if (interactive) {
		/* Ask for the line and column. */
		int i = do_prompt(FALSE, FALSE, MGOTOLINE,
				use_answer ? answer : NULL, NULL,
				/* TRANSLATORS: This is a prompt. */
				edit_refresh, _("Enter line number, column number"));

		/* If the user cancelled or gave a blank answer, get out. */
		if (i < 0) {
			statusbar(_("Cancelled"));
			return;
		}
812

813
		if (func_from_key(&i) == flip_goto) {
814
815
816
817
			/* Retain what the user typed so far and switch to searching. */
			search_init(TRUE, TRUE);
			do_search();
		}
818

819
820
821
822
823
824
825
826
827
828
829
830
		/* If a function was executed, we're done here. */
		if (i > 0)
			return;

		/* Try to extract one or two numbers from the user's response. */
		if (!parse_line_column(answer, &line, &column)) {
			statusline(ALERT, _("Invalid line or column number"));
			return;
		}
	} else {
		if (line == 0)
			line = openfile->current->lineno;
831

832
833
		if (column == 0)
			column = openfile->placewewant + 1;
Chris Allegretta's avatar
Chris Allegretta committed
834
	}
835

836
837
838
839
840
	/* Take a negative line number to mean: from the end of the file. */
	if (line < 0)
		line = openfile->filebot->lineno + line + 1;
	if (line < 1)
		line = 1;
841

842
843
844
845
846
847
848
849
850
851
852
853
854
855
	/* Iterate to the requested line. */
	for (openfile->current = openfile->fileage; line > 1 &&
				openfile->current != openfile->filebot; line--)
		openfile->current = openfile->current->next;

	/* Take a negative column number to mean: from the end of the line. */
	if (column < 0)
		column = strlenpt(openfile->current->data) + column + 2;
	if (column < 1)
		column = 1;

	/* Set the x position that corresponds to the requested column. */
	openfile->current_x = actual_x(openfile->current->data, column - 1);
	openfile->placewewant = column - 1;
856

857
#ifndef NANO_TINY
858
859
860
	if (ISSET(SOFTWRAP) && openfile->placewewant / editwincols >
						strlenpt(openfile->current->data) / editwincols)
		openfile->placewewant = strlenpt(openfile->current->data);
861
862
#endif

863
864
865
866
867
868
	/* When the position was manually given, center the target line. */
	if (interactive) {
		adjust_viewport(CENTERING);
		refresh_needed = TRUE;
	} else {
		int rows_from_tail;
869
870

#ifndef NANO_TINY
871
872
873
		if (ISSET(SOFTWRAP)) {
			filestruct *line = openfile->current;
			size_t leftedge = leftedge_for(xplustabs(), openfile->current);
874

875
876
877
			rows_from_tail = (editwinrows / 2) -
						go_forward_chunks(editwinrows / 2, &line, &leftedge);
		} else
878
#endif
879
880
881
882
883
884
885
886
887
888
889
890
			rows_from_tail = openfile->filebot->lineno -
								openfile->current->lineno;

		/* If the target line is close to the tail of the file, put the last
		 * line or chunk on the bottom line of the screen; otherwise, just
		 * center the target line. */
		if (rows_from_tail < editwinrows / 2 && ISSET(SMOOTH_SCROLL)) {
			openfile->current_y = editwinrows - 1 - rows_from_tail;
			adjust_viewport(STATIONARY);
		} else
			adjust_viewport(CENTERING);
	}
Chris Allegretta's avatar
Chris Allegretta committed
891
892
}

893
/* Go to the specified line and column, asking for them beforehand. */
894
void do_gotolinecolumn_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
895
{
896
897
	do_gotolinecolumn(openfile->current->lineno,
		openfile->placewewant + 1, FALSE, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
898
}
899

900
#ifndef NANO_TINY
901
/* Search for a match to one of the two characters in bracket_set.  If
902
903
904
 * 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. */
905
906
bool find_bracket_match(bool reverse, const char *bracket_set)
{
907
908
	filestruct *fileptr = openfile->current;
	const char *rev_start = NULL, *found = NULL;
909

910
	assert(mbstrlen(bracket_set) == 2);
911

912
913
914
915
	/* 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. */
916
	if (reverse)
917
		rev_start = fileptr->data + (openfile->current_x - 1);
918
	else
919
920
921
922
923
924
925
926
927
928
929
930
931
		rev_start = 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) {
		if ((rev_start > fileptr->data && *(rev_start - 1) == '\0') ||
						rev_start < fileptr->data)
			found = NULL;
		else if (reverse)
			found = mbrevstrpbrk(fileptr->data, bracket_set, rev_start);
		else
			found = mbstrpbrk(rev_start, bracket_set);
932

933
934
		if (found)
			break;
935

936
937
938
939
940
941
942
943
		if (reverse)
			fileptr = fileptr->prev;
		else
			fileptr = fileptr->next;

		/* If we've reached the start or end of the buffer, get out. */
		if (fileptr == NULL)
			return FALSE;
944

945
946
947
948
949
950
951
952
		rev_start = fileptr->data;
		if (reverse)
			rev_start += strlen(fileptr->data);
	}

	/* Set the current position to the found matching bracket. */
	openfile->current = fileptr;
	openfile->current_x = found - fileptr->data;
953

954
	return TRUE;
955
956
957
958
}

/* Search for a match to the bracket at the current cursor position, if
 * there is one. */
959
void do_find_bracket(void)
960
{
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
	filestruct *current_save;
	size_t current_x_save;
	const char *ch;
		/* The location in matchbrackets of the bracket at the current
		 * cursor position. */
	int ch_len;
		/* The length of ch in bytes. */
	const char *wanted_ch;
		/* The location in matchbrackets 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[MAXCHARLEN * 2 + 1];
		/* The pair of characters in ch and wanted_ch. */
	size_t i;
		/* Generic loop variable. */
	size_t matchhalf;
		/* The number of single-byte characters in one half of
		 * matchbrackets. */
	size_t mbmatchhalf;
		/* The number of multibyte characters in one half of
		 * matchbrackets. */
	size_t count = 1;
		/* The initial bracket count. */
	bool reverse;
		/* The direction we search. */

	assert(mbstrlen(matchbrackets) % 2 == 0);

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

	if ((ch = mbstrchr(matchbrackets, ch)) == NULL) {
		statusbar(_("Not a bracket"));
		return;
	}

	/* Save where we are. */
	current_save = openfile->current;
	current_x_save = openfile->current_x;

	/* If we're on an opening bracket, which must be in the first half
	 * of matchbrackets, we want to search forwards for a closing
	 * bracket.  If we're on a closing bracket, which must be in the
	 * second half of matchbrackets, we want to search backwards for an
	 * opening bracket. */
	matchhalf = 0;
	mbmatchhalf = mbstrlen(matchbrackets) / 2;

	for (i = 0; i < mbmatchhalf; i++)
		matchhalf += parse_mbchar(matchbrackets + matchhalf, NULL, NULL);

	reverse = ((ch - matchbrackets) >= matchhalf);

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

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

		mbmatchhalf--;
	}

	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. */
	strncpy(bracket_set, ch, ch_len);
	strncpy(bracket_set + ch_len, wanted_ch, wanted_ch_len);
	bracket_set[ch_len + wanted_ch_len] = '\0';

	while (TRUE) {
		if (find_bracket_match(reverse, bracket_set)) {
			/* If we found an identical bracket, increment count.  If we
			 * found a complementary bracket, decrement it. */
			count += (strncmp(openfile->current->data + openfile->current_x,
1043
								ch, ch_len) == 0) ? 1 : -1;
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058

			/* If count is zero, we've found a matching bracket.  Update
			 * the screen and get out. */
			if (count == 0) {
				edit_redraw(current_save, FLOWING);
				break;
			}
		} else {
			/* We didn't find either an opening or closing bracket.
			 * Indicate this, restore where we were, and get out. */
			statusbar(_("No matching bracket"));
			openfile->current = current_save;
			openfile->current_x = current_x_save;
			break;
		}
1059
1060
	}
}
1061
#endif /* !NANO_TINY */