search.c 32.2 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   search.c                                                             *
 *                                                                        *
5
 *   Copyright (C) 2000-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   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.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
27
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <ctype.h>
29
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
32
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
33

34
#ifdef HAVE_REGEX_H
35
36
static bool regexp_compiled = FALSE;
	/* Have we compiled any regular expressions? */
37
38
39
40
41
42

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

    assert(!regexp_compiled);
    if (rc != 0) {
	size_t len = regerror(rc, &search_regexp, NULL, 0);
	char *str = charalloc(len);

	regerror(rc, &search_regexp, str, len);
	statusbar(_("Bad regex \"%s\": %s"), regexp, str);
	free(str);
59
	return 0;
60
    }
61

62
    regexp_compiled = TRUE;
63
    return 1;
64
65
}

66
void regexp_cleanup(void)
67
{
68
69
    if (regexp_compiled) {
	regexp_compiled = FALSE;
70
71
	regfree(&search_regexp);
    }
72
}
73
#endif
74

75
76
void not_found_msg(const char *str)
{
77
78
79
    char *disp;
    int numchars;
 
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
80
    assert(str != NULL);
81
82
83
84
85
86
87
88

    disp = display_string(str, 0, (COLS / 2) + 1);
    numchars = strnlen(disp, COLS / 2);

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

    free(disp);
89
90
91
92
93
}

void search_abort(void)
{
    display_main_list();
94
#ifndef NANO_SMALL
95
    if (ISSET(MARK_ISSET))
96
97
	edit_refresh();
#endif
98
#ifdef HAVE_REGEX_H
99
    regexp_cleanup();
100
101
102
#endif
}

Chris Allegretta's avatar
Chris Allegretta committed
103
104
void search_init_globals(void)
{
105
106
107
108
    if (last_search == NULL)
	last_search = mallocstrcpy(NULL, "");
    if (last_replace == NULL)
	last_replace = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
109
110
}

111
112
113
114
115
116
/* 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
117
 *
118
119
120
 * 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
121
{
122
    int i = 0;
123
    char *buf;
124
    static char *backupstring = NULL;
125
126
127
128
129
130
131
132
133
134
135
	/* 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;
    }
136
137
138
139

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

Chris Allegretta's avatar
Chris Allegretta committed
142
    search_init_globals();
143

144
#ifndef NANO_SMALL
145
    search_history.current = (historytype *)&search_history.next;
146
#endif
147
148

    if (last_search[0] != '\0') {
149
150
	char *disp = display_string(last_search, 0, COLS / 3);

Chris Allegretta's avatar
Chris Allegretta committed
151
	buf = charalloc(COLS / 3 + 7);
152
153
	/* We use COLS / 3 here because we need to see more on the
	 * line. */
154
155
156
	sprintf(buf, " [%s%s]", disp,
		strlenpt(last_search) > COLS / 3 ? "..." : "");
	free(disp);
157
158
    } else
	buf = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
159

160
    /* This is now one simple call.  It just does a lot. */
161
162
    i = statusq(FALSE, replacing ? replace_list : whereis_list,
	backupstring,
163
164
165
#ifndef NANO_SMALL
	&search_history,
#endif
166
	"%s%s%s%s%s%s", _("Search"),
167

168
#ifndef NANO_SMALL
169
170
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
171
172
173
	ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
#endif
		"",
174

175
#ifdef HAVE_REGEX_H
176
177
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
178
179
180
	ISSET(USE_REGEXP) ? _(" [Regexp]") :
#endif
		"",
181

182
#ifndef NANO_SMALL
183
184
	/* This string is just a modifier for the search prompt; no
	 * grammar is implied. */
185
186
187
	ISSET(REVERSE_SEARCH) ? _(" [Backwards]") :
#endif
		"",
188

Chris Allegretta's avatar
Chris Allegretta committed
189
190
	replacing ? _(" (to replace)") : "",
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
191

192
    /* Release buf now that we don't need it anymore. */
Chris Allegretta's avatar
Chris Allegretta committed
193
194
    free(buf);

195
196
197
    free(backupstring);
    backupstring = NULL;

198
199
200
    /* 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')) {
201
	statusbar(_("Cancelled"));
202
203
204
#ifndef NANO_SMALL
	search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
205
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
206
207
    } else {
	switch (i) {
208
	    case -2:		/* It's the same string. */
209
#ifdef HAVE_REGEX_H
210
211
212
		/* Since answer is "", use last_search! */
		if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
		    return -1;
213
#endif
214
215
216
		break;
	    case 0:		/* They entered something new. */
		last_replace[0] = '\0';
217
#ifdef HAVE_REGEX_H
218
219
		if (ISSET(USE_REGEXP) && regexp_init(answer) == 0)
		    return -1;
220
#endif
221
		break;
Chris Allegretta's avatar
Chris Allegretta committed
222
#ifndef NANO_SMALL
223
224
225
226
227
228
229
230
	    case TOGGLE_CASE_KEY:
		TOGGLE(CASE_SENSITIVE);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
	    case TOGGLE_BACKWARDS_KEY:
		TOGGLE(REVERSE_SEARCH);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
231
#ifdef HAVE_REGEX_H
232
233
234
235
	    case TOGGLE_REGEXP_KEY:
		TOGGLE(USE_REGEXP);
		backupstring = mallocstrcpy(backupstring, answer);
		return 1;
236
#endif
Chris Allegretta's avatar
Chris Allegretta committed
237
#endif /* !NANO_SMALL */
238
239
240
241
	    case NANO_TOOTHERSEARCH_KEY:
		backupstring = mallocstrcpy(backupstring, answer);
		return -2;	/* Call the opposite search function. */
	    case NANO_TOGOTOLINE_KEY:
242
#ifndef NANO_SMALL
243
		search_history.current = search_history.next;
244
#endif
245
246
247
248
249
		/* Put answer up on the statusbar. */
		do_gotoline(-1, FALSE);
		/* Fall through. */
	    default:
		return -1;
Chris Allegretta's avatar
Chris Allegretta committed
250
	}
Chris Allegretta's avatar
Chris Allegretta committed
251
252
253
254
    }
    return 0;
}

255
bool is_whole_word(int curr_pos, const char *datastr, const char
256
	*searchword)
257
{
Chris Allegretta's avatar
Chris Allegretta committed
258
    size_t sln = curr_pos + strlen(searchword);
259

260
261
    /* Start of line or previous character is not a letter and end of
     * line or next character is not a letter. */
Chris Allegretta's avatar
Chris Allegretta committed
262
263
    return (curr_pos < 1 || !isalpha((int)datastr[curr_pos - 1])) &&
	(sln == strlen(datastr) || !isalpha((int)datastr[sln]));
264
265
}

266
/* Look for needle, starting at current, column current_x.  If
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
267
268
 * no_sameline is TRUE, skip over begin when looking for needle.  begin
 * is the line where we first started searching, at column beginx.  If
269
270
271
272
 * can_display_wrap is TRUE, we put messages on the statusbar, wrap
 * around the file boundaries, and set wrapped to TRUE if it isn't NULL.
 * 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. */
273
274
bool findnextstr(bool can_display_wrap, bool wholeword, bool
	no_sameline, const filestruct *begin, size_t beginx, const char
275
	*needle, bool *wrapped, size_t *needle_len)
Chris Allegretta's avatar
Chris Allegretta committed
276
{
Chris Allegretta's avatar
Chris Allegretta committed
277
    filestruct *fileptr = current;
278
    const char *rev_start = NULL, *found = NULL;
279
280
    size_t found_len;
	/* The length of the match we found. */
281
    size_t current_x_find = 0;
282
	/* The location of the match we found. */
283
    int current_y_find = current_y;
284
285
    bool search_last_line = FALSE;
	/* Have we gone past the last line while searching? */
286

287
288
289
290
    /* wrapped holds the value of search_last_line. */
    if (wrapped != NULL)
	*wrapped = FALSE;

291
292
293
294
295
    /* 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
296
    rev_start =
297
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
298
	ISSET(REVERSE_SEARCH) ? fileptr->data + (current_x - 1) :
299
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
300
	fileptr->data + (current_x + 1);
Chris Allegretta's avatar
Chris Allegretta committed
301

302
    /* Look for needle in searchstr. */
303
    while (TRUE) {
304
	found = strstrwrapper(fileptr->data, needle, rev_start);
Chris Allegretta's avatar
Chris Allegretta committed
305

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
	/* We've found a potential match. */
	if (found != NULL) {
	    bool found_whole = FALSE;
		/* Is this potential match a whole word? */

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

	    /* If we're searching for whole words, see if this potential
	     * match is a whole word. */
	    if (wholeword) {
		char *word = charalloc(found_len + 1);
		strncpy(word, found, found_len);
		word[found_len] = '\0';

		found_whole = is_whole_word(found - fileptr->data,
			fileptr->data, word);
		free(word);
	    }

	    /* If we're searching for whole words and this potential
	     * match isn't a whole word, or if we're not allowed to find
	     * a match on the same line we started on and this potential
	     * match is on that line, continue searching. */
	    if ((!wholeword || found_whole) && (!no_sameline ||
		fileptr != current))
337
		break;
Chris Allegretta's avatar
Chris Allegretta committed
338
	}
339

340
	/* We've finished processing the file, so get out. */
341
342
	if (search_last_line) {
	    if (can_display_wrap)
Chris Allegretta's avatar
Chris Allegretta committed
343
		not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
344
	    return FALSE;
345
	}
346
347
348
349
350
351
352
353
354

#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH)) {
	    fileptr = fileptr->prev;
	    current_y_find--;
	} else {
#endif
	    fileptr = fileptr->next;
	    current_y_find++;
355
#ifndef NANO_SMALL
356
	}
357
#endif
358

359
	/* Start or end of buffer reached, so wrap around. */
360
361
	if (fileptr == NULL) {
	    if (!can_display_wrap)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
362
		return FALSE;
363

364
#ifndef NANO_SMALL
365
366
367
368
	    if (ISSET(REVERSE_SEARCH)) {
		fileptr = filebot;
		current_y_find = editwinrows - 1;
	    } else {
369
#endif
370
371
372
373
374
375
		fileptr = fileage;
		current_y_find = 0;
#ifndef NANO_SMALL
	    }
#endif

376
377
378
	    if (can_display_wrap)
		statusbar(_("Search Wrapped"));
	}
Chris Allegretta's avatar
Chris Allegretta committed
379

380
	/* Original start line reached. */
381
	if (fileptr == begin) {
382
	    search_last_line = TRUE;
383
384
385
	    if (wrapped != NULL)
		*wrapped = TRUE;
	}
386
387
388
389
390
391
	rev_start = fileptr->data;
#ifndef NANO_SMALL
	if (ISSET(REVERSE_SEARCH))
	    rev_start += strlen(fileptr->data);
#endif
    }
392

393
394
    /* We found an instance. */
    current_x_find = found - fileptr->data;
Chris Allegretta's avatar
Chris Allegretta committed
395

396
397
398
399
400
401
402
403
404
    /* Ensure we haven't wrapped around again! */
    if (search_last_line &&
#ifndef NANO_SMALL
	((!ISSET(REVERSE_SEARCH) && current_x_find > beginx) ||
	(ISSET(REVERSE_SEARCH) && current_x_find < beginx))
#else
	current_x_find > beginx
#endif
	) {
Chris Allegretta's avatar
Chris Allegretta committed
405

406
407
	if (can_display_wrap)
	    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
408
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
409
410
    }

411
    /* Set globals now that we are sure we found something. */
412
413
    current = fileptr;
    current_x = current_x_find;
414
    current_y = current_y_find;
415
416

    /* needle_len holds the length of needle. */
417
418
    if (needle_len != NULL)
	*needle_len = found_len;
419

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
420
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
421
422
}

Chris Allegretta's avatar
Chris Allegretta committed
423
/* Search for a string. */
424
void do_search(void)
Chris Allegretta's avatar
Chris Allegretta committed
425
{
426
    size_t old_pww = placewewant, fileptr_x = current_x;
427
428
    int i;
    bool didfind;
429
    filestruct *fileptr = current;
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. */
Chris Allegretta's avatar
Chris Allegretta committed
438
	search_abort();
439
    else if (i == -2)	/* Replace. */
Chris Allegretta's avatar
Chris Allegretta committed
440
	do_replace();
441
442
443
#ifndef NANO_SMALL
    else if (i == 1)	/* Case Sensitive, Backwards, or Regexp search
			 * toggle. */
Chris Allegretta's avatar
Chris Allegretta committed
444
	do_search();
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_SMALL
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
    didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
464
	answer, NULL, NULL);
465

466
467
468
469
470
471
472
473
474
    /* Check to see if there's only one occurrence of the string and
     * we're on it now. */
    if (fileptr == current && fileptr_x == current_x && didfind) {
#ifdef HAVE_REGEX_H
	/* Do the search again, skipping over the current line, if we're
	 * doing a bol and/or eol regex search ("^", "$", or "^$"), so
	 * that we find one only once per line.  We should only end up
	 * back at the same position if the string isn't found again, in
	 * which case it's the only occurrence. */
475
476
	if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
477
	    didfind = findnextstr(TRUE, FALSE, TRUE, current, current_x,
478
		answer, NULL, NULL);
479
480
481
482
483
484
485
486
487
	    if (fileptr == current && fileptr_x == current_x && !didfind)
		statusbar(_("This is the only occurrence"));
	} else {
#endif
	    statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	}
#endif
    }
488

489
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
490
    edit_redraw(fileptr, old_pww);
Chris Allegretta's avatar
Chris Allegretta committed
491
492
493
    search_abort();
}

494
#ifndef NANO_SMALL
495
/* Search for the next string without prompting. */
496
void do_research(void)
497
{
498
    size_t old_pww = placewewant, fileptr_x = current_x;
499
    bool didfind;
500
    filestruct *fileptr = current;
501

502
#ifndef DISABLE_WRAPPING
503
    wrap_reset();
504
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
505

506
507
508
509
510
    search_init_globals();

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

#ifdef HAVE_REGEX_H
511
	/* Since answer is "", use last_search! */
512
	if (ISSET(USE_REGEXP) && regexp_init(last_search) == 0)
513
	    return;
514
515
#endif

516
	didfind = findnextstr(TRUE, FALSE, FALSE, current, current_x,
517
		last_search, NULL, NULL);
518

519
520
521
522
523
524
525
526
527
	/* Check to see if there's only one occurrence of the string and
	 * we're on it now. */
	if (fileptr == current && fileptr_x == current_x && didfind) {
#ifdef HAVE_REGEX_H
	    /* Do the search again, skipping over the current line, if
	     * we're doing a bol and/or eol regex search ("^", "$", or
	     * "^$"), so that we find one only once per line.  We should
	     * only end up back at the same position if the string isn't
	     * found again, in which case it's the only occurrence. */
528
529
	    if (ISSET(USE_REGEXP) && regexp_bol_or_eol(&search_regexp,
		last_search)) {
530
		didfind = findnextstr(TRUE, FALSE, TRUE, current,
531
			current_x, answer, NULL, NULL);
532
533
534
535
536
537
538
539
540
		if (fileptr == current && fileptr_x == current_x && !didfind)
		    statusbar(_("This is the only occurrence"));
	    } else {
#endif
		statusbar(_("This is the only occurrence"));
#ifdef HAVE_REGEX_H
	    }
#endif
	}
541
542
543
    } else
        statusbar(_("No current search pattern"));

544
    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
545
    edit_redraw(fileptr, old_pww);
546
547
    search_abort();
}
548
#endif
549

Chris Allegretta's avatar
Chris Allegretta committed
550
551
void replace_abort(void)
{
552
553
554
    /* Identical to search_abort(), so we'll call it here.  If it does
     * something different later, we can change it back.  For now, it's
     * just a waste to duplicate code. */
555
    search_abort();
556
    placewewant = xplustabs();
557
558
}

559
#ifdef HAVE_REGEX_H
560
int replace_regexp(char *string, bool create_flag)
561
{
562
    /* Split personality here - if create_flag is FALSE, just calculate
563
     * the size of the replacement line (necessary because of
564
     * subexpressions \1 to \9 in the replaced text). */
565

566
    const char *c = last_replace;
567
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
568
    int new_size = strlen(current->data) + 1 - search_match_count;
569

Chris Allegretta's avatar
Chris Allegretta committed
570
571
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
572
    while (*c != '\0') {
573
574
	int num = (int)(*(c + 1) - '0');

575
576
	if (*c != '\\' || num < 1 || num > 9 || num >
		search_regexp.re_nsub) {
577
578
579
580
581
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
582
	    int i = regmatches[num].rm_eo - regmatches[num].rm_so;
583

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

587
588
	    /* But add the length of the subexpression to new_size. */
	    new_size += i;
589

590
	    /* And if create_flag is TRUE, append the result of the
591
592
593
594
595
	     * subexpression match to the new line. */
	    if (create_flag) {
		strncpy(string, current->data + current_x +
			regmatches[num].rm_so, i);
		string += i;
596
597
	    }
	}
598
599
600
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
601
	*string = '\0';
602
603
604

    return new_size;
}
605
#endif
606

607
char *replace_line(const char *needle)
608
{
609
    char *copy;
610
611
612
    int new_line_size;
    int search_match_count;

613
    /* Calculate the size of the new line. */
614
#ifdef HAVE_REGEX_H
615
    if (ISSET(USE_REGEXP)) {
616
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
617
	new_line_size = replace_regexp(NULL, 0);
618
    } else {
619
#endif
620
621
622
623
	search_match_count = strlen(needle);
	new_line_size = strlen(current->data) - search_match_count +
	    strlen(answer) + 1;
#ifdef HAVE_REGEX_H
624
    }
625
#endif
626

627
    /* Create the buffer. */
628
    copy = charalloc(new_line_size);
629

630
    /* The head of the original line. */
631
632
    strncpy(copy, current->data, current_x);

633
    /* The replacement text. */
634
#ifdef HAVE_REGEX_H
635
636
    if (ISSET(USE_REGEXP))
	replace_regexp(copy + current_x, TRUE);
637
    else
638
#endif
639
	strcpy(copy + current_x, answer);
640

641
642
643
    /* The tail of the original line. */
    assert(current_x + search_match_count <= strlen(current->data));
    strcat(copy, current->data + current_x + search_match_count);
644
645

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
646
647
}

648
/* Step through each replace word and prompt user before replacing.
649
650
651
 * 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.
652
653
654
 *
 * needle is the string to seek.  We replace it with answer.  Return -1
 * if needle isn't found, else the number of replacements performed. */
655
656
ssize_t do_replace_loop(const char *needle, filestruct *real_current,
	size_t *real_current_x, bool wholewords)
Chris Allegretta's avatar
Chris Allegretta committed
657
{
658
659
    ssize_t numreplaced = -1;
    size_t match_len;
660
661
    size_t old_pww = placewewant, current_x_save = *real_current_x;
    const filestruct *current_save = real_current;
662
    bool replaceall = FALSE;
663
#ifdef HAVE_REGEX_H
664
    /* The starting-line match and bol/eol regex flags. */
665
    bool begin_line = FALSE, bol_or_eol = FALSE;
666
#endif
667
#ifndef NANO_SMALL
668
    bool old_mark_set = ISSET(MARK_ISSET), wrapped;
669
670
    const filestruct *top, *bot;
    size_t top_x, bot_x;
671
    int wraps = 0;
Chris Allegretta's avatar
Chris Allegretta committed
672

673
    if (old_mark_set) {
674
	/* Save the locations where the mark begins and ends. */
675
676
677
678
679
	filestruct *old_current = current;
	size_t old_current_x = current_x;

	current = real_current;
	current_x = *real_current_x;
680
	mark_order(&top, &top_x, &bot, &bot_x);
681
682
	current = old_current;
	current_x = old_current_x;
683

684
685
686
	UNSET(MARK_ISSET);
	edit_refresh();
    }
687
#endif
688

689
    while (findnextstr(TRUE, wholewords,
690
#ifdef HAVE_REGEX_H
691
692
693
694
695
	/* 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
696
#else
697
	FALSE
698
#endif
699
700
701
702
703
704
705
706
707
708
	, current_save, current_x_save, needle,
#ifndef NANO_SMALL
	/* If we're replacing marked text, we should take note of when
	 * the search wraps.  If the wrapped flag is set, it means that
	 * we've wrapped since the last search. */
	&wrapped
#else
	NULL
#endif
	, &match_len)) {
709
710

	int i = 0;
711
712
713
714
715

#ifndef NANO_SMALL
	/* If we've found a match outside the marked text, skip over it
	 * and search for another one. */
	if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
716
717
718
	    if (current->lineno < top->lineno || current->lineno >
		bot->lineno || (current == top && current_x < top_x) ||
		(current == bot && (current_x > bot_x || current_x +
719
720
721
722
723
724
725
726
727
728
		match_len > bot_x))) {
		/* Keep track of how many times the search has wrapped.
		 * If it's wrapped more than once, it means that the
		 * only matches left are those outside the marked text,
		 * so we're done. */
		if (wrapped) {
		    wraps++;
		    if (wraps > 1)
			break;
		}
729
		continue;
730
	    }
731
732
	}
#endif
733
734

#ifdef HAVE_REGEX_H
735
	/* If the bol_or_eol flag is set, we've found a match on the
736
737
	 * beginning line already, and we're still on the beginning line
	 * after the search, it means that we've wrapped around, so
738
	 * we're done. */
739
740
741
742
743
	if (bol_or_eol && begin_line && current == real_current)
	    break;
	/* Otherwise, set the begin_line flag if we've found a match on
	 * the beginning line, reset the bol_or_eol flag, and
	 * continue. */
744
	else {
745
	    if (current == real_current)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
746
747
		begin_line = TRUE;
	    bol_or_eol = FALSE;
748
749
	}
#endif
Chris Allegretta's avatar
Chris Allegretta committed
750

751
752
	if (!replaceall) {
	    placewewant = xplustabs();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
753
	    edit_redraw(current_save, old_pww);
754
755
	    old_pww = placewewant;
	}
Chris Allegretta's avatar
Chris Allegretta committed
756

757
758
759
	/* Record for the return value that we found the search string. */
	if (numreplaced == -1)
	    numreplaced = 0;
760

761
	if (!replaceall) {
762
763
764
765
766
767
	    char *exp_word;
	    size_t xpt = xplustabs();

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

768
	    curs_set(0);
769
	    do_replace_highlight(TRUE, exp_word);
770

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

773
774
	    do_replace_highlight(FALSE, exp_word);
	    free(exp_word);
775
	    curs_set(1);
776

777
	    if (i == -1)	/* We canceled the replace. */
778
		break;
779
780
	}

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

789
	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
790
	    char *copy;
791
	    ssize_t length_change;
792

793
	    if (i == 2)
794
		replaceall = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
795

796
	    copy = replace_line(needle);
Chris Allegretta's avatar
Chris Allegretta committed
797

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

800
801
802
803
804
805
806
807
#ifndef NANO_SMALL
	    if (current == mark_beginbuf && mark_beginx > current_x) {
		if (mark_beginx < current_x + match_len)
		    mark_beginx = current_x;
		else
		    mark_beginx += length_change;
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
808

809
	    assert(0 <= match_len + length_change);
810
811
812
813
	    if (current == real_current && current_x <= *real_current_x) {
		if (*real_current_x < current_x + match_len)
		    *real_current_x = current_x + match_len;
		*real_current_x += length_change;
814
	    }
815

816
	    /* Set the cursor at the last character of the replacement
817
818
	     * text, so searching will resume after the replacement
	     * text.  Note that current_x might be set to -1 here. */
819
#ifndef NANO_SMALL
820
	    if (!ISSET(REVERSE_SEARCH))
821
822
#endif
		current_x += match_len + length_change - 1;
823

824
	    /* Cleanup. */
825
826
827
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
828

829
830
831
832
833
834
835
836
837
	    if (!replaceall) {
#ifdef ENABLE_COLOR
		if (ISSET(COLOR_SYNTAX))
		    edit_refresh();
		else
#endif
		    update_line(current, current_x);
	    }

Chris Allegretta's avatar
Chris Allegretta committed
838
839
	    set_modified();
	    numreplaced++;
840
	}
841
842
843
844
845
846
847
848
849
850
851
852
853

	/* Save the locations where the mark begins and ends again,
	 * since they may have changed. */
	if (old_mark_set) {
	    filestruct *old_current = current;
	    size_t old_current_x = current_x;

	    current = real_current;
	    current_x = *real_current_x;
	    mark_order(&top, &top_x, &bot, &bot_x);
	    current = old_current;
	    current_x = old_current_x;
	}
Chris Allegretta's avatar
Chris Allegretta committed
854
855
    }

Chris Allegretta's avatar
Chris Allegretta committed
856
857
858
859
    /* If text has been added to the magicline, make a new magicline. */
    if (filebot->data[0] != '\0')
	new_magicline();

860
#ifndef NANO_SMALL
861
    if (old_mark_set)
862
863
864
	SET(MARK_ISSET);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
865
866
867
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
868
/* Replace a string. */
869
void do_replace(void)
Chris Allegretta's avatar
Chris Allegretta committed
870
{
871
    int i;
872
873
    filestruct *edittop_save, *begin;
    size_t beginx;
874
    ssize_t numreplaced;
Chris Allegretta's avatar
Chris Allegretta committed
875

Chris Allegretta's avatar
Chris Allegretta committed
876
877
878
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
879
	return;
Chris Allegretta's avatar
Chris Allegretta committed
880
881
    }

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

    if (i != 0)
895
	return;
Chris Allegretta's avatar
Chris Allegretta committed
896

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

906
#ifndef NANO_SMALL
907
908
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
909
#endif
910

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
948
    renumber_all();
949
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
950
951

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

Chris Allegretta's avatar
Chris Allegretta committed
955
956
957
    replace_abort();
}

958
void do_gotoline(int line, bool save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
959
{
960
    if (line <= 0) {		/* Ask for it. */
961
	char *ans = mallocstrcpy(NULL, answer);
962
	int i = statusq(FALSE, gotoline_list, line < 0 ? ans : "",
Chris Allegretta's avatar
Chris Allegretta committed
963
#ifndef NANO_SMALL
964
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
965
#endif
966
967
968
		_("Enter line number"));

	free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
969
970

	/* Cancel, or Enter with blank string. */
971
	if (i < 0) {
972
	    statusbar(_("Cancelled"));
973
	    display_main_list();
974
975
976
	    return;
	}

977
	if (i == NANO_TOOTHERWHEREIS_KEY) {
978
979
980
	    /* Keep answer up on the statusbar. */
	    search_init(TRUE, TRUE);

981
	    do_search();
982
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
983
	}
984

985
986
	/* Do a bounds check.  Display a warning on an out-of-bounds
	 * line number only if we hit Enter at the statusbar prompt. */
987
	if (!parse_num(answer, &line) || line < 0) {
988
989
	    if (i == 0)
		statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
990
	    display_main_list();
991
	    return;
Chris Allegretta's avatar
Chris Allegretta committed
992
993
994
	}
    }

995
996
997
998
999
1000
1001
1002
1003
    if (current->lineno > line) {
	for (; current->prev != NULL && current->lineno > line;
		current = current->prev)
	    ;
    } else {
	for (; current->next != NULL && current->lineno < line;
		current = current->next)
	    ;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1004

1005
    current_x = 0;
1006

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

1011
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1012
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1013
1014
}

1015
void do_gotoline_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1016
{
1017
    do_gotoline(0, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1018
}
1019

1020
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
1021
void do_gotopos(int line, int pos_x, int pos_y, size_t pos_pww)
1022
{
1023
1024
    /* Since do_gotoline() resets the x-coordinate but not the
     * y-coordinate, set the coordinates up this way. */
1025
    current_y = pos_y;
1026
    do_gotoline(line, TRUE);
1027

1028
1029
1030
1031
    /* Make sure that the x-coordinate is sane here. */
    current_x = strlen(current->data);
    if (pos_x < current_x)
	current_x = pos_x;
1032

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

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

    ch_under_cursor = current->data[current_x];
1051

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

1058
1059
    assert(strlen(brackets) % 2 == 0);
    wanted_ch = brackets[(strlen(brackets) - 1) - (pos - brackets)];
1060
1061

    current_save = current;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1062
1063
    current_x_save = current_x;
    old_pww = placewewant;
1064
    flags_save = flags;
1065
1066
    SET(USE_REGEXP);

1067
1068
    /* Apparent near redundancy with regexp_pat[] here is needed.
     * "[][]" works, "[[]]" doesn't. */
1069

1070
1071
    if (pos < brackets + (strlen(brackets) / 2)) {
	/* On a left bracket. */
1072
1073
1074
	regexp_pat[1] = wanted_ch;
	regexp_pat[2] = ch_under_cursor;
	UNSET(REVERSE_SEARCH);
1075
1076
    } else {
	/* On a right bracket. */
1077
1078
1079
1080
1081
1082
	regexp_pat[1] = ch_under_cursor;
	regexp_pat[2] = wanted_ch;
	SET(REVERSE_SEARCH);
    }

    regexp_init(regexp_pat);
1083
    /* We constructed regexp_pat to be a valid expression. */
1084
    assert(regexp_compiled);
1085

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

1108
    regexp_cleanup();
1109
    flags = flags_save;
1110
1111
}
#endif
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136

#ifndef NANO_SMALL
/*
 * search and replace history list support functions
 */

/* initialize search and replace history lists */
void history_init(void)
{
    search_history.next = (historytype *)&search_history.prev;
    search_history.prev = NULL;
    search_history.tail = (historytype *)&search_history.next;
    search_history.current = search_history.next;
    search_history.count = 0;
    search_history.len = 0;

    replace_history.next = (historytype *)&replace_history.prev;
    replace_history.prev = NULL;
    replace_history.tail = (historytype *)&replace_history.next;
    replace_history.current = replace_history.next;
    replace_history.count = 0;
    replace_history.len = 0;
}

/* find first node containing string *s in history list *h */
1137
historytype *find_node(historytype *h, const char *s)
1138
{
Chris Allegretta's avatar
Chris Allegretta committed
1139
    for (; h->next != NULL; h = h->next)
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
	if (strcmp(s, h->data) == 0)
	    return h;
    return NULL;
}

/* remove node *r */
void remove_node(historytype *r)
{
    r->prev->next = r->next;
    r->next->prev = r->prev;
    free(r->data);
    free(r);
}

/* add a node after node *h */
void insert_node(historytype *h, const char *s)
{
    historytype *a;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1159
    a = (historytype *)nmalloc(sizeof(historytype));
1160
    a->next = h->next;
1161
    a->prev = h;
1162
1163
1164
1165
1166
1167
    h->next->prev = a;
    h->next = a;
    a->data = mallocstrcpy(NULL, s);
}

/* update history list */
1168
void update_history(historyheadtype *h, const char *s)
1169
1170
1171
{
    historytype *p;

Chris Allegretta's avatar
Chris Allegretta committed
1172
1173
1174
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
1175
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
1176
	remove_node(p);			/* delete identical older string */
1177
1178
1179
1180
1181
1182
1183
1184
	h->count--;
    }
    if (h->count == MAX_SEARCH_HISTORY) {	/* list 'full', delete oldest */
	remove_node(h->tail);
	h->count--;
    }
    insert_node((historytype *)h, s);
    h->count++;
1185
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
1186
  up_hs:
1187
1188
1189
1190
1191
1192
    h->current = h->next;
}

/* return a pointer to either the next older history or NULL if no more */
char *get_history_older(historyheadtype *h)
{
Chris Allegretta's avatar
Chris Allegretta committed
1193
    if (h->current->next != NULL) {	/* any older entries? */
1194
1195
1196
1197
1198
1199
1200
1201
	h->current = h->current->next;	/* yes */
	return h->current->data;	/* return it */
    }
    return NULL;			/* end of list */
}

char *get_history_newer(historyheadtype *h)
{
Chris Allegretta's avatar
Chris Allegretta committed
1202
    if (h->current->prev != NULL) {
1203
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1204
	if (h->current->prev != NULL)
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
	    return h->current->data;
    }
    return NULL;
}

/* get a completion */
char *get_history_completion(historyheadtype *h, char *s)
{
    historytype *p;

Chris Allegretta's avatar
Chris Allegretta committed
1215
1216
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1217
1218
1219
1220
1221
1222
1223
1224
1225
	    h->current = p;
	    return p->data;
	}
    }
    h->current = (historytype*)h;
    null_at(&s, h->len);
    return s;
}

1226
#ifdef DEBUG
1227
1228
1229
/* free a history list */
void free_history(historyheadtype *h)
{
1230
    historytype *p;
1231

1232
    for (p = h->next; p->next != NULL; p = p->next)
1233
1234
	remove_node(p);
}
1235
#endif
1236
1237
1238

/* end of history support functions */
#endif /* !NANO_SMALL */