search.c 25.8 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-2003 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
#include <stdlib.h>
#include <string.h>
26
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
27
#include <stdio.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
35
/* Regular expression helper functions */

36
#ifdef HAVE_REGEX_H
37
int regexp_init(const char *regexp)
38
{
39
40
41
42
43
    /* Hmm, perhaps we should check for whether regcomp returns successfully */
    if (regcomp(&search_regexp, regexp, (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE) 
		| REG_EXTENDED) != 0)
	return 0;

44
    SET(REGEXP_COMPILED);
45
    return 1;
46
47
}

48
void regexp_cleanup(void)
49
50
51
52
{
    UNSET(REGEXP_COMPILED);
    regfree(&search_regexp);
}
53
#endif
54

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
void not_found_msg(const char *str)
{
    if (strlen(str) <= COLS / 2)
	statusbar(_("\"%s\" not found"), str);
    else {
	char *foo = mallocstrcpy(NULL, str);

	foo[COLS / 2] = '\0';
	statusbar(_("\"%s...\" not found"), foo);
	free(foo);
    }
}

void search_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    wrefresh(bottomwin);
    if (ISSET(MARK_ISSET))
	edit_refresh_clearok();

#ifdef HAVE_REGEX_H
    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
#endif
}

Chris Allegretta's avatar
Chris Allegretta committed
82
83
84
void search_init_globals(void)
{
    if (last_search == NULL) {
85
	last_search = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
86
	last_search[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
87
88
    }
    if (last_replace == NULL) {
89
	last_replace = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
90
	last_replace[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
91
92
93
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
94
95
96
97
/* Set up the system variables for a search or replace.  Return -1 on
 * abort, 0 on success, and 1 on rerun calling program.  Return -2 to
 * run opposite program (search -> replace, replace -> search).
 *
Chris Allegretta's avatar
Chris Allegretta committed
98
99
 * replacing = 1 if we call from do_replace(), 0 if called from
 * do_search(). */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
100
int search_init(int replacing)
Chris Allegretta's avatar
Chris Allegretta committed
101
{
102
    int i = 0;
103
    char *buf;
104
    static char *backupstring = NULL;
105
106
107
#ifdef HAVE_REGEX_H
    const char *regex_error = _("Invalid regex \"%s\"");
#endif /* HAVE_REGEX_H */
108

Chris Allegretta's avatar
Chris Allegretta committed
109
    search_init_globals();
110

111
    if (backupstring == NULL)
112
	backupstring = mallocstrcpy(backupstring, "");
113

114
#ifndef NANO_SMALL
115
    search_history.current = (historytype *)&search_history.next;
116
#endif
117
118

    if (last_search[0] != '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
119
	buf = charalloc(COLS / 3 + 7);
Chris Allegretta's avatar
Chris Allegretta committed
120
	/* We use COLS / 3 here because we need to see more on the line */
Chris Allegretta's avatar
Chris Allegretta committed
121
122
	sprintf(buf, " [%.*s%s]", COLS / 3, last_search,
		strlen(last_search) > COLS / 3 ? "..." : "");
Chris Allegretta's avatar
Chris Allegretta committed
123
124
    } else {
	buf = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
125
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
126
    }
Chris Allegretta's avatar
Chris Allegretta committed
127

128
    /* This is now one simple call.  It just does a lot */
129
    i = statusq(0, replacing ? replace_list : whereis_list, backupstring,
130
131
132
133
#ifndef NANO_SMALL
	&search_history,
#endif
	"%s%s%s%s%s%s",
Chris Allegretta's avatar
Chris Allegretta committed
134
	_("Search"),
135
136
137
138
139
140
141
142
143
144
145
146
147

	/* This string is just a modifier for the search prompt,
	   no grammar is implied */
	ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : "",

	/* This string is just a modifier for the search prompt,
	   no grammar is implied */
	ISSET(USE_REGEXP) ? _(" [Regexp]") : "",

	/* This string is just a modifier for the search prompt,
	   no grammar is implied */
	ISSET(REVERSE_SEARCH) ? _(" [Backwards]") : "",

Chris Allegretta's avatar
Chris Allegretta committed
148
149
	replacing ? _(" (to replace)") : "",
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
150

Chris Allegretta's avatar
Chris Allegretta committed
151
152
153
    /* Release buf now that we don't need it anymore */
    free(buf);

Chris Allegretta's avatar
Chris Allegretta committed
154
    /* Cancel any search, or just return with no previous search */
Chris Allegretta's avatar
Chris Allegretta committed
155
    if (i == -1 || (i < 0 && last_search[0] == '\0')) {
Chris Allegretta's avatar
Chris Allegretta committed
156
157
	statusbar(_("Search Cancelled"));
	reset_cursor();
158
159
	free(backupstring);
	backupstring = NULL;
160
161
162
#ifndef NANO_SMALL
	search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
163
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
164
165
166
    } else {
	switch (i) {
	case -2:	/* Same string */
167
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
168
	    if (ISSET(USE_REGEXP))
Chris Allegretta's avatar
Chris Allegretta committed
169
		/* If answer is "", use last_search! */
170
171
172
173
174
175
176
		if (regexp_init(last_search) == 0) {
		    statusbar(regex_error, last_search);
		    reset_cursor();
		    free(backupstring);
		    backupstring = NULL;
		    return -3;
		}
177
#endif
Chris Allegretta's avatar
Chris Allegretta committed
178
179
	    break;
	case 0:		/* They entered something new */
180
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
181
	    if (ISSET(USE_REGEXP))
182
		if (regexp_init(answer) == 0) {
183
		    statusbar(regex_error, answer);
184
185
186
187
188
189
190
191
		    reset_cursor();
		    free(backupstring);
		    backupstring = NULL;
#ifndef NANO_SMALL
		    search_history.current = search_history.next;
#endif
		    return -3;
		}
192
#endif
Chris Allegretta's avatar
Chris Allegretta committed
193
194
195
196
197
198
199
200
201
202
203
204
205
	    free(backupstring);
	    backupstring = NULL;
	    last_replace[0] = '\0';
	    break;
#ifndef NANO_SMALL
	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;
206
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
207
208
209
210
	case TOGGLE_REGEXP_KEY:
	    TOGGLE(USE_REGEXP);
	    backupstring = mallocstrcpy(backupstring, answer);
	    return 1;
211
#endif
Chris Allegretta's avatar
Chris Allegretta committed
212
213
214
215
216
217
218
#endif /* !NANO_SMALL */
	case NANO_OTHERSEARCH_KEY:
	    backupstring = mallocstrcpy(backupstring, answer);
	    return -2;		/* Call the opposite search function */
	case NANO_FROMSEARCHTOGOTO_KEY:
	    free(backupstring);
	    backupstring = NULL;
219
220
221
#ifndef NANO_SMALL
	    search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
222
	    i = (int)strtol(answer, &buf, 10);	/* Just testing answer here */
223
224
225
226
	    if (!(errno == ERANGE || *answer == '\0' || *buf != '\0'))
		do_gotoline(-1, 0);
	    else
		do_gotoline_void();
Chris Allegretta's avatar
Chris Allegretta committed
227
228
229
230
231
232
233
	    return -3;
	default:
	    do_early_abort();
	    free(backupstring);
	    backupstring = NULL;
	    return -3;
	}
Chris Allegretta's avatar
Chris Allegretta committed
234
235
236
237
    }
    return 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
238
int is_whole_word(int curr_pos, const char *datastr, const char *searchword)
239
{
Chris Allegretta's avatar
Chris Allegretta committed
240
    size_t sln = curr_pos + strlen(searchword);
241

Chris Allegretta's avatar
Chris Allegretta committed
242
    /* start of line or previous character not a letter and end of line
Chris Allegretta's avatar
Chris Allegretta committed
243
244
245
       or next character not a letter */
    return (curr_pos < 1 || !isalpha((int)datastr[curr_pos - 1])) &&
	(sln == strlen(datastr) || !isalpha((int)datastr[sln]));
246
247
}

Chris Allegretta's avatar
Chris Allegretta committed
248
filestruct *findnextstr(int quiet, int bracket_mode,
249
250
			const filestruct *begin, int beginx,
			const char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
251
{
Chris Allegretta's avatar
Chris Allegretta committed
252
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
253
    const char *searchstr, *rev_start = NULL, *found = NULL;
254
    int current_x_find = 0;
Chris Allegretta's avatar
Chris Allegretta committed
255

Chris Allegretta's avatar
Chris Allegretta committed
256
    search_offscreen = 0;
257

Chris Allegretta's avatar
Chris Allegretta committed
258
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
259
260
261
262
263
264
	/* Argh, current_x is set to -1 by nano.c:do_int_spell_fix(), and
	 * strlen returns size_t, which is unsigned. */
	assert(current_x < 0 || current_x <= strlen(fileptr->data));
	current_x_find = current_x;
	if (current_x_find < 0 || fileptr->data[current_x_find] != '\0')
	    current_x_find++;
Chris Allegretta's avatar
Chris Allegretta committed
265

Chris Allegretta's avatar
Chris Allegretta committed
266
	searchstr = &fileptr->data[current_x_find];
Chris Allegretta's avatar
Chris Allegretta committed
267

Chris Allegretta's avatar
Chris Allegretta committed
268
	/* Look for needle in searchstr */
269
	while ((found = strstrwrapper(searchstr, needle, rev_start, current_x_find)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
270

Chris Allegretta's avatar
Chris Allegretta committed
271
272
273
274
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
275
		update_line(fileptr, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
276
277
	        return NULL;
	    }
278

Chris Allegretta's avatar
Chris Allegretta committed
279
	    update_line(fileptr, 0);
280
281
282
283

	    /* reset current_x_find between lines */
	    current_x_find = 0;

Chris Allegretta's avatar
Chris Allegretta committed
284
	    fileptr = fileptr->next;
285

286
	    if (fileptr == editbot)
Chris Allegretta's avatar
Chris Allegretta committed
287
		search_offscreen = 1;
Chris Allegretta's avatar
Chris Allegretta committed
288

Chris Allegretta's avatar
Chris Allegretta committed
289
	    /* EOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
290
	    if (fileptr == NULL) {
291
292
		/* don't wrap if looking for bracket match */
		if (bracket_mode)
Chris Allegretta's avatar
Chris Allegretta committed
293
		    return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
294
		fileptr = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
295
		search_offscreen = 1;
296
		if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
297
		    statusbar(_("Search Wrapped"));
Chris Allegretta's avatar
Chris Allegretta committed
298
299
300
301
302
303
304
305
	    }

	    /* Original start line reached */
	    if (fileptr == begin)
		search_last_line = 1;

	    searchstr = fileptr->data;
	}
306

Chris Allegretta's avatar
Chris Allegretta committed
307
308
309
	/* We found an instance */
	current_x_find = found - fileptr->data;
	/* Ensure we haven't wrapped around again! */
Chris Allegretta's avatar
Chris Allegretta committed
310
	if (search_last_line && current_x_find > beginx) {
Chris Allegretta's avatar
Chris Allegretta committed
311
	    if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
312
313
		not_found_msg(needle);
	    return NULL;
314
	}
315
    }
316
317
#ifndef NANO_SMALL
    else {	/* reverse search */
Chris Allegretta's avatar
Chris Allegretta committed
318
	current_x_find = current_x - 1;
Chris Allegretta's avatar
Chris Allegretta committed
319
	/* Make sure we haven't passed the beginning of the string */
Chris Allegretta's avatar
Chris Allegretta committed
320
	rev_start = &fileptr->data[current_x_find];
321
322
	searchstr = fileptr->data;

Chris Allegretta's avatar
Chris Allegretta committed
323
	/* Look for needle in searchstr */
324
	while ((found = strstrwrapper(searchstr, needle, rev_start, current_x_find)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
325
326
327
328
329
330
331
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
		return NULL;
	    }

Chris Allegretta's avatar
Chris Allegretta committed
332
	    update_line(fileptr, 0);
333
334
335
336

	    /* reset current_x_find between lines */
	    current_x_find = 0;

Chris Allegretta's avatar
Chris Allegretta committed
337
338
	    fileptr = fileptr->prev;

339
	    if (fileptr == edittop->prev)
Chris Allegretta's avatar
Chris Allegretta committed
340
		search_offscreen = 1;
341

342
	    /* SOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
343
/* ? */	    if (fileptr == NULL) {
344
345
		if (bracket_mode)
		   return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
346
		fileptr = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
347
		search_offscreen = 1;
348
		if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
		    statusbar(_("Search Wrapped"));
	    }
	    /* Original start line reached */
	    if (fileptr == begin)
		search_last_line = 1;

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

	/* We found an instance */
	current_x_find = found - fileptr->data;
	/* Ensure we haven't wrapped around again! */
	if ((search_last_line) && (current_x_find < beginx)) {
	    if (!quiet)
		not_found_msg(needle);
	    return NULL;
	}
Chris Allegretta's avatar
Chris Allegretta committed
367
    }
Chris Allegretta's avatar
Chris Allegretta committed
368
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
369

Chris Allegretta's avatar
Chris Allegretta committed
370
    /* Set globals now that we are sure we found something */
371
372
373
    current = fileptr;
    current_x = current_x_find;

374
    if (!bracket_mode) {
375
	update_line(current, current_x);
376
377
378
	placewewant = xplustabs();
	reset_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
379
380
381
    return fileptr;
}

Chris Allegretta's avatar
Chris Allegretta committed
382
/* Search for a string. */
Chris Allegretta's avatar
Chris Allegretta committed
383
384
385
int do_search(void)
{
    int i;
386
387
    filestruct *fileptr = current, *didfind;
    int fileptr_x = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
388
389

    wrap_reset();
390
391
392
    i = search_init(0);
    switch (i) {
    case -1:
Chris Allegretta's avatar
Chris Allegretta committed
393
394
395
	current = fileptr;
	search_abort();
	return 0;
396
    case -3:
Chris Allegretta's avatar
Chris Allegretta committed
397
398
	search_abort();
	return 0;
399
    case -2:
Chris Allegretta's avatar
Chris Allegretta committed
400
401
	do_replace();
	return 0;
402
    case 1:
Chris Allegretta's avatar
Chris Allegretta committed
403
404
405
406
	do_search();
	search_abort();
	return 1;
    }
407

Chris Allegretta's avatar
Chris Allegretta committed
408
     /* If answer is now "", copy last_search into answer... */
Chris Allegretta's avatar
Chris Allegretta committed
409
    if (answer[0] == '\0')
410
411
412
413
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

414
415
#ifndef NANO_SMALL
    /* add this search string to the search history list */
Chris Allegretta's avatar
Chris Allegretta committed
416
    if (answer[0] != '\0')
417
	update_history(&search_history, answer);
418
419
#endif	/* !NANO_SMALL */

420
    search_last_line = 0;
421
422
    didfind = findnextstr(FALSE, FALSE, current, current_x, answer);

Chris Allegretta's avatar
Chris Allegretta committed
423
    if (fileptr == current && fileptr_x == current_x && didfind != NULL)
424
	statusbar(_("This is the only occurrence"));
425
    else if (current->lineno <= edittop->lineno
Chris Allegretta's avatar
Chris Allegretta committed
426
	|| current->lineno >= editbot->lineno)
427
        edit_update(current, CENTER);
428

Chris Allegretta's avatar
Chris Allegretta committed
429
    search_abort();
430

Chris Allegretta's avatar
Chris Allegretta committed
431
432
433
434
435
    return 1;
}

void replace_abort(void)
{
436
    /* Identical to search_abort, so we'll call it here.  If it
437
       does something different later, we can change it back.  For now
438
       it's just a waste to duplicate code */
439
    search_abort();
440
    placewewant = xplustabs();
441
442
}

443
#ifdef HAVE_REGEX_H
444
445
int replace_regexp(char *string, int create_flag)
{
Chris Allegretta's avatar
Chris Allegretta committed
446
    /* Split personality here - if create_flag is NULL, just calculate
447
     * the size of the replacement line (necessary because of
Chris Allegretta's avatar
Chris Allegretta committed
448
     * subexpressions like \1 \2 \3 in the replaced text). */
449
450
451

    char *c;
    int new_size = strlen(current->data) + 1;
452
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
453
454
455

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
456
457
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
458
459

    c = last_replace;
460
    while (*c != '\0') {
461
462
463
464
465
466
	if (*c != '\\') {
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
467
	    int num = (int) *(c + 1) - (int) '0';
468
469
	    if (num >= 1 && num <= 9) {

470
		int i = regmatches[num].rm_eo - regmatches[num].rm_so;
471
472
473

		if (num > search_regexp.re_nsub) {
		    /* Ugh, they specified a subexpression that doesn't
Chris Allegretta's avatar
Chris Allegretta committed
474
		     * exist. */
475
476
477
478
479
480
481
		    return -1;
		}

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

		/* But add the length of the subexpression to new_size */
482
		new_size += i;
483
484
485

		/* And if create_flag is set, append the result of the
		 * subexpression match to the new line */
486
487
488
489
490
		if (create_flag) {
		    strncpy(string, current->data + current_x +
			    regmatches[num].rm_so, i);
		    string += i;
		}
491
492
493
494
495
496
497
498

	    } else {
		if (create_flag)
		    *string++ = *c;
		c++;
		new_size++;
	    }
	}
499
500
501
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
502
	*string = '\0';
503
504
505

    return new_size;
}
506
#endif
507

Chris Allegretta's avatar
Chris Allegretta committed
508
char *replace_line(void)
509
510
511
512
513
514
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

    /* Calculate size of new line */
515
#ifdef HAVE_REGEX_H
516
    if (ISSET(USE_REGEXP)) {
517
518
519
	search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
	new_line_size = replace_regexp(NULL, 0);
	/* If they specified an invalid subexpression in the replace
520
	 * text, return NULL, indicating an error */
521
522
	if (new_line_size < 0)
	    return NULL;
523
    } else {
524
525
526
#else
    {
#endif
527
528
529
	search_match_count = strlen(last_search);
	new_line_size = strlen(current->data) - strlen(last_search) +
	    strlen(last_replace) + 1;
530
    }
531

532
    /* Create buffer */
533
    copy = charalloc(new_line_size);
534
535
536

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
537
    copy[current_x] = '\0';
538
539
540

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
541
	strcat(copy, last_replace);
542
#ifdef HAVE_REGEX_H
543
    else
544
	replace_regexp(copy + current_x, 1);
545
#endif
546
547

    /* The tail of the original line */
Chris Allegretta's avatar
Chris Allegretta committed
548
549
550
551
552
553

    /* This may expose other bugs, because it no longer goes through
     * each character in the string and tests for string goodness.  But
     * because we can assume the invariant that current->data is less
     * than current_x + strlen(last_search) long, this should be safe. 
     * Or it will expose bugs ;-) */
554
555
556
557
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
558
559
}

Chris Allegretta's avatar
Chris Allegretta committed
560
561
562
/* Step through each replace word and prompt user before replacing
 * word.  Return -1 if the string to replace isn't found at all.
 * Otherwise, return the number of replacements made. */
Chris Allegretta's avatar
Chris Allegretta committed
563
564
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
565
{
Chris Allegretta's avatar
Chris Allegretta committed
566
    int replaceall = 0, numreplaced = -1;
567
568

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
569
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
570

Chris Allegretta's avatar
Chris Allegretta committed
571
    switch (*i) {
Chris Allegretta's avatar
Chris Allegretta committed
572
    case -1:		/* Aborted enter */
Chris Allegretta's avatar
Chris Allegretta committed
573
	if (last_replace[0] != '\0')
574
	    answer = mallocstrcpy(answer, last_replace);
575
576
577
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
578
579
580
    case 0:		/* They actually entered something */
	break;
    default:
Chris Allegretta's avatar
Chris Allegretta committed
581
582
        if (*i != -2) {	/* First page, last page, for example, could
			   get here */
Chris Allegretta's avatar
Chris Allegretta committed
583
584
585
	    do_early_abort();
	    replace_abort();
	    return 0;
586
        }
Chris Allegretta's avatar
Chris Allegretta committed
587
588
    }

589
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
590
    while (1) {
591
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
592
593
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
594

595
596
	if (current->lineno <= edittop->lineno
	    || current->lineno >= editbot->lineno)
597
	    edit_update(current, CENTER);
598

Chris Allegretta's avatar
Chris Allegretta committed
599
	/* No more matches.  Done! */
600
	if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
601
602
	    break;

603
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
604
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
605
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
606

Chris Allegretta's avatar
Chris Allegretta committed
607
	/* If we're here, we've found the search string */
Chris Allegretta's avatar
Chris Allegretta committed
608
609
610
	if (numreplaced == -1)
	    numreplaced = 0;

611
612
613
614
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

Chris Allegretta's avatar
Chris Allegretta committed
615
	    *i = do_yesno(1, 1, _("Replace this instance?"));
Chris Allegretta's avatar
Chris Allegretta committed
616

617
618
619
620
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
621
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
622
623
624
	    long length_change;
	    size_t match_len;

Chris Allegretta's avatar
Chris Allegretta committed
625
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
626
627
		replaceall = 1;

628
	    copy = replace_line();
629
	    if (copy == NULL) {
Jordi Mallach's avatar
   
Jordi Mallach committed
630
		statusbar(_("Replace failed: unknown subexpression!"));
631
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
632
		return 0;
633
	    }
Chris Allegretta's avatar
Chris Allegretta committed
634

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

637
638
639
640
641
642
#ifdef HAVE_REGEX_H
	    if (ISSET(USE_REGEXP))
		match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
	    else
#endif
		match_len = strlen(prevanswer);
643

644
645
646
647
648
649
650
651
#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
652

653
654
655
656
657
658
	    assert(0 <= match_len + length_change);
	    if (current == begin && current_x <= *beginx) {
		if (*beginx < current_x + match_len)
		    *beginx = current_x + match_len;
		*beginx += length_change;
	    }
659

660
661
662
663
664
665
666
	    /* Set the cursor at the last character of the replacement
	     * text, so searching will resume after the replacement text.
	     * Note that current_x might be set to -1 here. */
#ifndef NANO_SMALL
	    if (!ISSET(REVERSE_SEARCH))
#endif
		current_x += match_len + length_change - 1;
667

668
669
670
671
	    /* Cleanup */
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
672

Chris Allegretta's avatar
Chris Allegretta committed
673
674
675
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
676
677
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
678
679
680
	    break;
    }

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

Chris Allegretta's avatar
Chris Allegretta committed
685
686
687
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
688
/* Replace a string. */
Chris Allegretta's avatar
Chris Allegretta committed
689
690
691
692
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
Chris Allegretta's avatar
Chris Allegretta committed
693
    char *prevanswer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
694

Chris Allegretta's avatar
Chris Allegretta committed
695
696
697
698
699
700
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
    i = search_init(1);
    switch (i) {
    case -1:
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
    case 1:
	do_replace();
	return 1;
    case -2:
	do_search();
	return 0;
    case -3:
	replace_abort();
	return 0;
    }

718
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
719
    if (answer[0] != '\0')
720
	update_history(&search_history, answer);
721
722
#endif	/* !NANO_SMALL */

723
724
    /* If answer is now == "", copy last_search into answer 
	(and prevanswer)...  */
Chris Allegretta's avatar
Chris Allegretta committed
725
    if (answer[0] == '\0')
726
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
727
    else
Chris Allegretta's avatar
Chris Allegretta committed
728
	last_search = mallocstrcpy(last_search, answer);
729

Chris Allegretta's avatar
Chris Allegretta committed
730
731
    prevanswer = mallocstrcpy(prevanswer, last_search);

732
#ifndef NANO_SMALL
733
734
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
735
#endif
736
737

    i = statusq(0, replace_list_2, last_replace,
738
739
740
741
#ifndef NANO_SMALL
		&replace_history,
#endif
		_("Replace with"));
742

743
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
744
    if (i == 0 && answer[0] != '\0')
745
746
	update_history(&replace_history, answer);
#endif	/* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
747
748

    begin = current;
749
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
750
751
752
753
754
    search_last_line = 0;

    numreplaced = do_replace_loop(prevanswer, begin, &beginx, FALSE, &i);

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
755
    current = begin;
756
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
757
    renumber_all();
758
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
759
760

    if (numreplaced >= 0)
761
	statusbar(P_("Replaced %d occurrence", "Replaced %d occurrences",
Chris Allegretta's avatar
Chris Allegretta committed
762
763
764
765
		numreplaced), numreplaced);
    else
	not_found_msg(prevanswer);

766
    free(prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
767
768
769
770
771
772
773
774
775
776
    replace_abort();
    return 1;
}

void goto_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
}

777
int do_gotoline(int line, int save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
778
{
779
    if (line <= 0) {		/* Ask for it */
Chris Allegretta's avatar
Chris Allegretta committed
780
	int st = statusq(FALSE, goto_list, line != 0 ? answer : "",
Chris Allegretta's avatar
Chris Allegretta committed
781
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
782
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
783
#endif
Chris Allegretta's avatar
Chris Allegretta committed
784
785
786
787
			_("Enter line number"));

	/* Cancel, or Enter with blank string. */
	if (st == -1 || st == -2)
Chris Allegretta's avatar
Chris Allegretta committed
788
	    statusbar(_("Aborted"));
Chris Allegretta's avatar
Chris Allegretta committed
789
	if (st != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
790
791
792
	    goto_abort();
	    return 0;
	}
793
794
795
796
797
798

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
799
	    goto_abort();
800
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
801
802
803
	}
    }

804
    for (current = fileage; current->next != NULL && line > 1; line--)
805
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
806

807
    current_x = 0;
808

809
    /* if save_pos is nonzero, don't change the cursor position when
810
811
812
813
814
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
815
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
816
817
818
819
820
821
    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
822
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
823
}
824

825
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
826
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
827
828
829
830
831
{
    /* since do_gotoline() resets the x-coordinate but not the
       y-coordinate, set the coordinates up this way */
    current_y = pos_y;
    do_gotoline(line, 1);
832

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
833
834
835
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
836
837

    /* set the rest of the coordinates up */
838
839
840
841
842
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
843
844
845
846
847

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
848
    const char *pos, *brackets = "([{<>}])";
849
    char regexp_pat[] = "[  ]";
Chris Allegretta's avatar
Chris Allegretta committed
850
    int offset, have_search_offscreen = 0, flagsave, current_x_save, count = 1;
851
852
853
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
854
855
856

/*    if ((!(pos = strchr(brackets, ch_under_cursor))) || (!((offset = pos - brackets) < 8))) { */

857
    if (((pos = strchr(brackets, ch_under_cursor)) == NULL) || (((offset = pos - brackets) < 8) == 0)) {
858
859
860
861
862
863
864
865
866
867
868
869
870
	statusbar(_("Not a bracket"));
	return 1;
    }

    blank_statusbar_refresh();

    wanted_ch = *(brackets + ((strlen(brackets) - (offset + 1))));

    current_x_save = current_x;
    current_save = current;
    flagsave = flags;
    SET(USE_REGEXP);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
871
/* apparent near redundancy with regexp_pat[] here is needed, [][] works, [[]] doesn't */
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886

    if (offset < (strlen(brackets) / 2)) {			/* on a left bracket */
	regexp_pat[1] = wanted_ch;
	regexp_pat[2] = ch_under_cursor;
	UNSET(REVERSE_SEARCH);
    } else {							/* on a right bracket */
	regexp_pat[1] = ch_under_cursor;
	regexp_pat[2] = wanted_ch;
	SET(REVERSE_SEARCH);
    }

    regexp_init(regexp_pat);

    while (1) {
	search_last_line = 0;
887
	if (findnextstr(1, 1, current, current_x, regexp_pat) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
888
889
890
891
	    have_search_offscreen |= search_offscreen;

	    /* found identical bracket */
	    if (current->data[current_x] == ch_under_cursor)
892
		count++;
Chris Allegretta's avatar
Chris Allegretta committed
893
894
895
	    else {

		/* found complementary bracket */
896
		if (!(--count)) {
Chris Allegretta's avatar
Chris Allegretta committed
897
		    if (have_search_offscreen)
898
899
900
901
902
			edit_update(current, CENTER);
		    else
			update_line(current, current_x);
		    placewewant = xplustabs();
		    reset_cursor();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
903
		    break;
904
905
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
906
907
908
	} else {

	    /* didn't find either left or right bracket */
909
910
911
912
913
914
915
916
917
918
919
920
921
	    statusbar(_("No matching bracket"));
	    current_x = current_x_save;
	    current = current_save;
	    break;
	}
    }

    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
    flags = flagsave;
    return 0;
}
#endif
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948

#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 */
historytype *find_node(historytype *h, char *s)
{
Chris Allegretta's avatar
Chris Allegretta committed
949
    for (; h->next != NULL; h = h->next)
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
	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;

    a = nmalloc(sizeof(historytype));
    a->next = h->next;
    a->prev = h->next->prev;
    h->next->prev = a;
    h->next = a;
    a->data = mallocstrcpy(NULL, s);
}

/* update history list */
void update_history(historyheadtype *h, char *s)
{
    historytype *p;

Chris Allegretta's avatar
Chris Allegretta committed
982
983
984
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
985
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
986
	remove_node(p);			/* delete identical older string */
987
988
989
990
991
992
993
994
	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++;
995
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
996
  up_hs:
997
998
999
1000
1001
1002
    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
1003
    if (h->current->next != NULL) {	/* any older entries? */
1004
1005
1006
1007
1008
1009
1010
1011
	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
1012
    if (h->current->prev != NULL) {
1013
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1014
	if (h->current->prev != NULL)
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
	    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
1025
1026
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
	    h->current = p;
	    return p->data;
	}
    }
    h->current = (historytype*)h;
    null_at(&s, h->len);
    return s;
}

/* free a history list */
void free_history(historyheadtype *h)
{
    historytype *p, *n;

Chris Allegretta's avatar
Chris Allegretta committed
1041
    for (p = h->next; (n = p->next); p = n)
1042
1043
1044
1045
1046
	remove_node(p);
}

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