search.c 26.7 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
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)
{
    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
81
82
83
void search_init_globals(void)
{
    if (last_search == NULL) {
84
	last_search = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
85
	last_search[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
86
87
    }
    if (last_replace == NULL) {
88
	last_replace = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
89
	last_replace[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
90
91
92
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
93
94
95
96
/* 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
97
98
 * replacing = 1 if we call from do_replace(), 0 if called from
 * do_search(). */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
99
int search_init(int replacing)
Chris Allegretta's avatar
Chris Allegretta committed
100
{
101
    int i = 0;
102
    char *buf;
103
    static char *backupstring = NULL;
104
105
106
#ifdef HAVE_REGEX_H
    const char *regex_error = _("Invalid regex \"%s\"");
#endif /* HAVE_REGEX_H */
107

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

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

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

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

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

	/* 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
147
148
	replacing ? _(" (to replace)") : "",
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
149

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
257
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
258
259
260
261
262
263
	/* 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
264

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

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

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

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

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

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

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

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

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

	    searchstr = fileptr->data;
	}
305

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

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

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

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

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

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

341
	    /* SOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
342
/* ? */	    if (fileptr == NULL) {
343
344
		if (bracket_mode)
		   return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
345
		fileptr = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
346
		search_offscreen = 1;
347
		if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
		    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
366
    }
Chris Allegretta's avatar
Chris Allegretta committed
367
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
368

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

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

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
430
431
432
    return 1;
}

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/* Search for the next string without prompting. */
int do_research(void)
{
    filestruct *fileptr = current, *didfind;
    int fileptr_x = current_x;
#ifdef HAVE_REGEX_H
    const char *regex_error = _("Invalid regex \"%s\"");
#endif /* HAVE_REGEX_H */

    wrap_reset();
    search_init_globals();

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

#ifdef HAVE_REGEX_H
	if (ISSET(USE_REGEXP))
	    if (regexp_init(last_search) == 0) {
		statusbar(regex_error, last_search);
		reset_cursor();
		return -3;
	    }
#endif

	search_last_line = 0;
	didfind = findnextstr(FALSE, FALSE, current, current_x, last_search);

	if (fileptr == current && fileptr_x == current_x && didfind != NULL)
	    statusbar(_("This is the only occurrence"));
	else if (current->lineno <= edittop->lineno
	    || current->lineno >= editbot->lineno)
	    edit_update(current, CENTER);

    } else
        statusbar(_("No current search pattern"));

    search_abort();

    return 1;
}

Chris Allegretta's avatar
Chris Allegretta committed
473
474
void replace_abort(void)
{
475
    /* Identical to search_abort, so we'll call it here.  If it
476
       does something different later, we can change it back.  For now
477
       it's just a waste to duplicate code */
478
    search_abort();
479
    placewewant = xplustabs();
480
481
}

482
#ifdef HAVE_REGEX_H
483
484
int replace_regexp(char *string, int create_flag)
{
Chris Allegretta's avatar
Chris Allegretta committed
485
    /* Split personality here - if create_flag is NULL, just calculate
486
     * the size of the replacement line (necessary because of
Chris Allegretta's avatar
Chris Allegretta committed
487
     * subexpressions like \1 \2 \3 in the replaced text). */
488
489
490

    char *c;
    int new_size = strlen(current->data) + 1;
491
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
492
493
494

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
495
496
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
497
498

    c = last_replace;
499
    while (*c != '\0') {
500
501
502
503
504
505
	if (*c != '\\') {
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
506
	    int num = (int) *(c + 1) - (int) '0';
507
508
	    if (num >= 1 && num <= 9) {

509
		int i = regmatches[num].rm_eo - regmatches[num].rm_so;
510
511
512

		if (num > search_regexp.re_nsub) {
		    /* Ugh, they specified a subexpression that doesn't
Chris Allegretta's avatar
Chris Allegretta committed
513
		     * exist. */
514
515
516
517
518
519
520
		    return -1;
		}

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

		/* But add the length of the subexpression to new_size */
521
		new_size += i;
522
523
524

		/* And if create_flag is set, append the result of the
		 * subexpression match to the new line */
525
526
527
528
529
		if (create_flag) {
		    strncpy(string, current->data + current_x +
			    regmatches[num].rm_so, i);
		    string += i;
		}
530
531
532
533
534
535
536
537

	    } else {
		if (create_flag)
		    *string++ = *c;
		c++;
		new_size++;
	    }
	}
538
539
540
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
541
	*string = '\0';
542
543
544

    return new_size;
}
545
#endif
546

Chris Allegretta's avatar
Chris Allegretta committed
547
char *replace_line(void)
548
549
550
551
552
553
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

    /* Calculate size of new line */
554
#ifdef HAVE_REGEX_H
555
    if (ISSET(USE_REGEXP)) {
556
557
558
	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
559
	 * text, return NULL, indicating an error */
560
561
	if (new_line_size < 0)
	    return NULL;
562
    } else {
563
564
565
#else
    {
#endif
566
567
568
	search_match_count = strlen(last_search);
	new_line_size = strlen(current->data) - strlen(last_search) +
	    strlen(last_replace) + 1;
569
    }
570

571
    /* Create buffer */
572
    copy = charalloc(new_line_size);
573
574
575

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
576
    copy[current_x] = '\0';
577
578
579

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
580
	strcat(copy, last_replace);
581
#ifdef HAVE_REGEX_H
582
    else
583
	replace_regexp(copy + current_x, 1);
584
#endif
585
586

    /* The tail of the original line */
Chris Allegretta's avatar
Chris Allegretta committed
587
588
589
590
591
592

    /* 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 ;-) */
593
594
595
596
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
597
598
}

Chris Allegretta's avatar
Chris Allegretta committed
599
600
601
/* 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
602
603
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
604
{
Chris Allegretta's avatar
Chris Allegretta committed
605
    int replaceall = 0, numreplaced = -1;
606
607

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
608
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
609

Chris Allegretta's avatar
Chris Allegretta committed
610
    switch (*i) {
Chris Allegretta's avatar
Chris Allegretta committed
611
    case -1:		/* Aborted enter */
Chris Allegretta's avatar
Chris Allegretta committed
612
	if (last_replace[0] != '\0')
613
	    answer = mallocstrcpy(answer, last_replace);
614
615
616
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
617
618
619
    case 0:		/* They actually entered something */
	break;
    default:
Chris Allegretta's avatar
Chris Allegretta committed
620
621
        if (*i != -2) {	/* First page, last page, for example, could
			   get here */
Chris Allegretta's avatar
Chris Allegretta committed
622
623
624
	    do_early_abort();
	    replace_abort();
	    return 0;
625
        }
Chris Allegretta's avatar
Chris Allegretta committed
626
627
    }

628
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
629
    while (1) {
630
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
631
632
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
633

634
635
	if (current->lineno <= edittop->lineno
	    || current->lineno >= editbot->lineno)
636
	    edit_update(current, CENTER);
637

Chris Allegretta's avatar
Chris Allegretta committed
638
	/* No more matches.  Done! */
639
	if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
640
641
	    break;

642
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
643
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
644
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
645

Chris Allegretta's avatar
Chris Allegretta committed
646
	/* If we're here, we've found the search string */
Chris Allegretta's avatar
Chris Allegretta committed
647
648
649
	if (numreplaced == -1)
	    numreplaced = 0;

650
651
652
653
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

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

656
657
658
659
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
660
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
661
662
663
	    long length_change;
	    size_t match_len;

Chris Allegretta's avatar
Chris Allegretta committed
664
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
665
666
		replaceall = 1;

667
	    copy = replace_line();
668
	    if (copy == NULL) {
Jordi Mallach's avatar
   
Jordi Mallach committed
669
		statusbar(_("Replace failed: unknown subexpression!"));
670
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
671
		return 0;
672
	    }
Chris Allegretta's avatar
Chris Allegretta committed
673

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

676
677
678
679
680
681
#ifdef HAVE_REGEX_H
	    if (ISSET(USE_REGEXP))
		match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
	    else
#endif
		match_len = strlen(prevanswer);
682

683
684
685
686
687
688
689
690
#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
691

692
693
694
695
696
697
	    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;
	    }
698

699
700
701
702
703
704
705
	    /* 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;
706

707
708
709
710
	    /* Cleanup */
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
711

Chris Allegretta's avatar
Chris Allegretta committed
712
713
714
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
715
716
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
717
718
719
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
720
721
722
723
    /* 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
724
725
726
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
727
/* Replace a string. */
Chris Allegretta's avatar
Chris Allegretta committed
728
729
730
731
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
Chris Allegretta's avatar
Chris Allegretta committed
732
    char *prevanswer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
733

Chris Allegretta's avatar
Chris Allegretta committed
734
735
736
737
738
739
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
    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;
    }

757
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
758
    if (answer[0] != '\0')
759
	update_history(&search_history, answer);
760
761
#endif	/* !NANO_SMALL */

762
763
    /* If answer is now == "", copy last_search into answer 
	(and prevanswer)...  */
Chris Allegretta's avatar
Chris Allegretta committed
764
    if (answer[0] == '\0')
765
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
766
    else
Chris Allegretta's avatar
Chris Allegretta committed
767
	last_search = mallocstrcpy(last_search, answer);
768

Chris Allegretta's avatar
Chris Allegretta committed
769
770
    prevanswer = mallocstrcpy(prevanswer, last_search);

771
#ifndef NANO_SMALL
772
773
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
774
#endif
775
776

    i = statusq(0, replace_list_2, last_replace,
777
778
779
780
#ifndef NANO_SMALL
		&replace_history,
#endif
		_("Replace with"));
781

782
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
783
    if (i == 0 && answer[0] != '\0')
784
785
	update_history(&replace_history, answer);
#endif	/* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
786
787

    begin = current;
788
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
789
790
791
792
793
    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
794
    current = begin;
795
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
796
    renumber_all();
797
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
798
799

    if (numreplaced >= 0)
800
	statusbar(P_("Replaced %d occurrence", "Replaced %d occurrences",
Chris Allegretta's avatar
Chris Allegretta committed
801
802
803
804
		numreplaced), numreplaced);
    else
	not_found_msg(prevanswer);

805
    free(prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
806
807
808
809
    replace_abort();
    return 1;
}

810
int do_gotoline(int line, int save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
811
{
812
    if (line <= 0) {		/* Ask for it */
Chris Allegretta's avatar
Chris Allegretta committed
813
	int st = statusq(FALSE, goto_list, line != 0 ? answer : "",
Chris Allegretta's avatar
Chris Allegretta committed
814
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
815
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
816
#endif
Chris Allegretta's avatar
Chris Allegretta committed
817
818
819
820
			_("Enter line number"));

	/* Cancel, or Enter with blank string. */
	if (st == -1 || st == -2)
Chris Allegretta's avatar
Chris Allegretta committed
821
	    statusbar(_("Aborted"));
Chris Allegretta's avatar
Chris Allegretta committed
822
	if (st != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
823
	    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
824
825
	    return 0;
	}
826
827
828
829
830
831

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
832
	    display_main_list();
833
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
834
835
836
	}
    }

837
    for (current = fileage; current->next != NULL && line > 1; line--)
838
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
839

840
    current_x = 0;
841

842
    /* if save_pos is nonzero, don't change the cursor position when
843
844
845
846
847
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
848
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
849
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
850
851
852
853
854
    return 1;
}

int do_gotoline_void(void)
{
855
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
856
}
857

858
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
859
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
860
861
862
863
864
{
    /* 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);
865

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
866
867
868
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
869
870

    /* set the rest of the coordinates up */
871
872
873
874
875
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
876
877
878
879
880

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
881
    const char *pos, *brackets = "([{<>}])";
882
    char regexp_pat[] = "[  ]";
Chris Allegretta's avatar
Chris Allegretta committed
883
    int offset, have_search_offscreen = 0, flagsave, current_x_save, count = 1;
884
885
886
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
887
888
889

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

890
    if (((pos = strchr(brackets, ch_under_cursor)) == NULL) || (((offset = pos - brackets) < 8) == 0)) {
891
892
893
894
895
896
897
898
899
900
901
902
903
	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
904
/* apparent near redundancy with regexp_pat[] here is needed, [][] works, [[]] doesn't */
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919

    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;
920
	if (findnextstr(1, 1, current, current_x, regexp_pat) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
921
922
923
924
	    have_search_offscreen |= search_offscreen;

	    /* found identical bracket */
	    if (current->data[current_x] == ch_under_cursor)
925
		count++;
Chris Allegretta's avatar
Chris Allegretta committed
926
927
928
	    else {

		/* found complementary bracket */
929
		if (!(--count)) {
Chris Allegretta's avatar
Chris Allegretta committed
930
		    if (have_search_offscreen)
931
932
933
934
935
			edit_update(current, CENTER);
		    else
			update_line(current, current_x);
		    placewewant = xplustabs();
		    reset_cursor();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
936
		    break;
937
938
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
939
940
941
	} else {

	    /* didn't find either left or right bracket */
942
943
944
	    statusbar(_("No matching bracket"));
	    current_x = current_x_save;
	    current = current_save;
Chris Allegretta's avatar
Chris Allegretta committed
945
	    update_line(current, current_x);
946
947
948
949
950
951
952
953
954
955
	    break;
	}
    }

    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
    flags = flagsave;
    return 0;
}
#endif
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
982

#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
983
    for (; h->next != NULL; h = h->next)
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
	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
1003
    a = (historytype *)nmalloc(sizeof(historytype));
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
    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
1016
1017
1018
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
1019
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
1020
	remove_node(p);			/* delete identical older string */
1021
1022
1023
1024
1025
1026
1027
1028
	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++;
1029
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
1030
  up_hs:
1031
1032
1033
1034
1035
1036
    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
1037
    if (h->current->next != NULL) {	/* any older entries? */
1038
1039
1040
1041
1042
1043
1044
1045
	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
1046
    if (h->current->prev != NULL) {
1047
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1048
	if (h->current->prev != NULL)
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
	    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
1059
1060
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
	    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
1075
    for (p = h->next; (n = p->next); p = n)
1076
1077
1078
1079
1080
	remove_node(p);
}

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