search.c 25.4 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

Chris Allegretta's avatar
Chris Allegretta committed
106
    search_init_globals();
107

108
    if (backupstring == NULL)
109
	backupstring = mallocstrcpy(backupstring, "");
110

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
229
int is_whole_word(int curr_pos, const char *datastr, const char *searchword)
230
{
Chris Allegretta's avatar
Chris Allegretta committed
231
    size_t sln = curr_pos + strlen(searchword);
232

Chris Allegretta's avatar
Chris Allegretta committed
233
    /* start of line or previous character not a letter and end of line
Chris Allegretta's avatar
Chris Allegretta committed
234
235
236
       or next character not a letter */
    return (curr_pos < 1 || !isalpha((int)datastr[curr_pos - 1])) &&
	(sln == strlen(datastr) || !isalpha((int)datastr[sln]));
237
238
}

Chris Allegretta's avatar
Chris Allegretta committed
239
filestruct *findnextstr(int quiet, int bracket_mode,
240
241
			const filestruct *begin, int beginx,
			const char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
242
{
Chris Allegretta's avatar
Chris Allegretta committed
243
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
244
    const char *searchstr, *rev_start = NULL, *found = NULL;
245
    int current_x_find = 0;
Chris Allegretta's avatar
Chris Allegretta committed
246

Chris Allegretta's avatar
Chris Allegretta committed
247
    search_offscreen = 0;
248

Chris Allegretta's avatar
Chris Allegretta committed
249
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
250
251
252
253
254
255
	/* 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
256

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

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

Chris Allegretta's avatar
Chris Allegretta committed
262
263
264
265
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
266
		update_line(fileptr, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
267
268
	        return NULL;
	    }
269

Chris Allegretta's avatar
Chris Allegretta committed
270
	    update_line(fileptr, 0);
271
272
273
274

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

Chris Allegretta's avatar
Chris Allegretta committed
275
	    fileptr = fileptr->next;
276

277
	    if (fileptr == editbot)
Chris Allegretta's avatar
Chris Allegretta committed
278
		search_offscreen = 1;
Chris Allegretta's avatar
Chris Allegretta committed
279

Chris Allegretta's avatar
Chris Allegretta committed
280
	    /* EOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
281
	    if (fileptr == NULL) {
282
283
		/* don't wrap if looking for bracket match */
		if (bracket_mode)
Chris Allegretta's avatar
Chris Allegretta committed
284
		    return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
285
		fileptr = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
286
		search_offscreen = 1;
287
		if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
288
		    statusbar(_("Search Wrapped"));
Chris Allegretta's avatar
Chris Allegretta committed
289
290
291
292
293
294
295
296
	    }

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

	    searchstr = fileptr->data;
	}
297

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

Chris Allegretta's avatar
Chris Allegretta committed
314
	/* Look for needle in searchstr */
315
	while ((found = strstrwrapper(searchstr, needle, rev_start, current_x_find)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
316
317
318
319
320
321
322
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
		return NULL;
	    }

Chris Allegretta's avatar
Chris Allegretta committed
323
	    update_line(fileptr, 0);
324
325
326
327

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

Chris Allegretta's avatar
Chris Allegretta committed
328
329
	    fileptr = fileptr->prev;

330
	    if (fileptr == edittop->prev)
Chris Allegretta's avatar
Chris Allegretta committed
331
		search_offscreen = 1;
332

333
	    /* SOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
334
/* ? */	    if (fileptr == NULL) {
335
336
		if (bracket_mode)
		   return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
337
		fileptr = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
338
		search_offscreen = 1;
339
		if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
		    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
358
    }
Chris Allegretta's avatar
Chris Allegretta committed
359
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
360

Chris Allegretta's avatar
Chris Allegretta committed
361
    /* Set globals now that we are sure we found something */
362
363
364
    current = fileptr;
    current_x = current_x_find;

365
    if (!bracket_mode) {
366
	update_line(current, current_x);
367
368
369
	placewewant = xplustabs();
	reset_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
370
371
372
    return fileptr;
}

Chris Allegretta's avatar
Chris Allegretta committed
373
/* Search for a string. */
Chris Allegretta's avatar
Chris Allegretta committed
374
375
376
int do_search(void)
{
    int i;
377
378
    filestruct *fileptr = current, *didfind;
    int fileptr_x = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
379
380

    wrap_reset();
381
382
383
    i = search_init(0);
    switch (i) {
    case -1:
Chris Allegretta's avatar
Chris Allegretta committed
384
385
386
	current = fileptr;
	search_abort();
	return 0;
387
    case -3:
Chris Allegretta's avatar
Chris Allegretta committed
388
389
	search_abort();
	return 0;
390
    case -2:
Chris Allegretta's avatar
Chris Allegretta committed
391
392
	do_replace();
	return 0;
393
    case 1:
Chris Allegretta's avatar
Chris Allegretta committed
394
395
396
397
	do_search();
	search_abort();
	return 1;
    }
398

Chris Allegretta's avatar
Chris Allegretta committed
399
     /* If answer is now "", copy last_search into answer... */
Chris Allegretta's avatar
Chris Allegretta committed
400
    if (answer[0] == '\0')
401
402
403
404
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

405
406
#ifndef NANO_SMALL
    /* add this search string to the search history list */
Chris Allegretta's avatar
Chris Allegretta committed
407
    if (answer[0] != '\0')
408
	update_history(&search_history, answer);
409
410
#endif	/* !NANO_SMALL */

411
    search_last_line = 0;
412
413
    didfind = findnextstr(FALSE, FALSE, current, current_x, answer);

Chris Allegretta's avatar
Chris Allegretta committed
414
    if (fileptr == current && fileptr_x == current_x && didfind != NULL)
415
	statusbar(_("This is the only occurrence"));
416
    else if (current->lineno <= edittop->lineno
Chris Allegretta's avatar
Chris Allegretta committed
417
	|| current->lineno >= editbot->lineno)
418
        edit_update(current, CENTER);
419

Chris Allegretta's avatar
Chris Allegretta committed
420
    search_abort();
421

Chris Allegretta's avatar
Chris Allegretta committed
422
423
424
425
426
    return 1;
}

void replace_abort(void)
{
427
    /* Identical to search_abort, so we'll call it here.  If it
428
       does something different later, we can change it back.  For now
429
       it's just a waste to duplicate code */
430
    search_abort();
431
    placewewant = xplustabs();
432
433
}

434
#ifdef HAVE_REGEX_H
435
436
int replace_regexp(char *string, int create_flag)
{
Chris Allegretta's avatar
Chris Allegretta committed
437
    /* Split personality here - if create_flag is NULL, just calculate
438
     * the size of the replacement line (necessary because of
Chris Allegretta's avatar
Chris Allegretta committed
439
     * subexpressions like \1 \2 \3 in the replaced text). */
440
441
442

    char *c;
    int new_size = strlen(current->data) + 1;
443
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
444
445
446

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
447
448
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
449
450

    c = last_replace;
451
    while (*c != '\0') {
452
453
454
455
456
457
	if (*c != '\\') {
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
458
	    int num = (int) *(c + 1) - (int) '0';
459
460
	    if (num >= 1 && num <= 9) {

461
		int i = regmatches[num].rm_eo - regmatches[num].rm_so;
462
463
464

		if (num > search_regexp.re_nsub) {
		    /* Ugh, they specified a subexpression that doesn't
Chris Allegretta's avatar
Chris Allegretta committed
465
		     * exist. */
466
467
468
469
470
471
472
		    return -1;
		}

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

		/* But add the length of the subexpression to new_size */
473
		new_size += i;
474
475
476

		/* And if create_flag is set, append the result of the
		 * subexpression match to the new line */
477
478
479
480
481
		if (create_flag) {
		    strncpy(string, current->data + current_x +
			    regmatches[num].rm_so, i);
		    string += i;
		}
482
483
484
485
486
487
488
489

	    } else {
		if (create_flag)
		    *string++ = *c;
		c++;
		new_size++;
	    }
	}
490
491
492
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
493
	*string = '\0';
494
495
496

    return new_size;
}
497
#endif
498

Chris Allegretta's avatar
Chris Allegretta committed
499
char *replace_line(void)
500
501
502
503
504
505
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

    /* Calculate size of new line */
506
#ifdef HAVE_REGEX_H
507
    if (ISSET(USE_REGEXP)) {
508
509
510
	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
511
	 * text, return NULL, indicating an error */
512
513
	if (new_line_size < 0)
	    return NULL;
514
    } else {
515
516
517
#else
    {
#endif
518
519
520
	search_match_count = strlen(last_search);
	new_line_size = strlen(current->data) - strlen(last_search) +
	    strlen(last_replace) + 1;
521
    }
522

523
    /* Create buffer */
524
    copy = charalloc(new_line_size);
525
526
527

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
528
    copy[current_x] = '\0';
529
530
531

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
532
	strcat(copy, last_replace);
533
#ifdef HAVE_REGEX_H
534
    else
535
	replace_regexp(copy + current_x, 1);
536
#endif
537
538

    /* The tail of the original line */
Chris Allegretta's avatar
Chris Allegretta committed
539
540
541
542
543
544

    /* 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 ;-) */
545
546
547
548
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
549
550
}

Chris Allegretta's avatar
Chris Allegretta committed
551
552
553
/* 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
554
555
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
556
{
Chris Allegretta's avatar
Chris Allegretta committed
557
    int replaceall = 0, numreplaced = -1;
558
559

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
560
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
561

Chris Allegretta's avatar
Chris Allegretta committed
562
    switch (*i) {
Chris Allegretta's avatar
Chris Allegretta committed
563
    case -1:		/* Aborted enter */
Chris Allegretta's avatar
Chris Allegretta committed
564
	if (last_replace[0] != '\0')
565
	    answer = mallocstrcpy(answer, last_replace);
566
567
568
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
569
570
571
    case 0:		/* They actually entered something */
	break;
    default:
Chris Allegretta's avatar
Chris Allegretta committed
572
573
        if (*i != -2) {	/* First page, last page, for example, could
			   get here */
Chris Allegretta's avatar
Chris Allegretta committed
574
575
576
	    do_early_abort();
	    replace_abort();
	    return 0;
577
        }
Chris Allegretta's avatar
Chris Allegretta committed
578
579
    }

580
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
581
    while (1) {
582
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
583
584
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
585

586
587
	if (current->lineno <= edittop->lineno
	    || current->lineno >= editbot->lineno)
588
	    edit_update(current, CENTER);
589

Chris Allegretta's avatar
Chris Allegretta committed
590
	/* No more matches.  Done! */
591
	if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
592
593
	    break;

594
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
595
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
596
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
597

Chris Allegretta's avatar
Chris Allegretta committed
598
	/* If we're here, we've found the search string */
Chris Allegretta's avatar
Chris Allegretta committed
599
600
601
	if (numreplaced == -1)
	    numreplaced = 0;

602
603
604
605
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

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

608
609
610
611
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
612
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
613
614
615
	    long length_change;
	    size_t match_len;

Chris Allegretta's avatar
Chris Allegretta committed
616
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
617
618
		replaceall = 1;

619
	    copy = replace_line();
620
	    if (copy == NULL) {
Jordi Mallach's avatar
   
Jordi Mallach committed
621
		statusbar(_("Replace failed: unknown subexpression!"));
622
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
623
		return 0;
624
	    }
Chris Allegretta's avatar
Chris Allegretta committed
625

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

628
629
630
631
632
633
#ifdef HAVE_REGEX_H
	    if (ISSET(USE_REGEXP))
		match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
	    else
#endif
		match_len = strlen(prevanswer);
634

635
636
637
638
639
640
641
642
#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
643

644
645
646
647
648
649
	    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;
	    }
650

651
652
653
654
655
656
657
	    /* 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;
658

659
660
661
662
	    /* Cleanup */
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
663

Chris Allegretta's avatar
Chris Allegretta committed
664
665
666
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
667
668
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
669
670
671
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
672
673
674
675
    /* 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
676
677
678
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
679
/* Replace a string. */
Chris Allegretta's avatar
Chris Allegretta committed
680
681
682
683
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
Chris Allegretta's avatar
Chris Allegretta committed
684
    char *prevanswer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
685

Chris Allegretta's avatar
Chris Allegretta committed
686
687
688
689
690
691
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
    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;
    }

709
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
710
    if (answer[0] != '\0')
711
	update_history(&search_history, answer);
712
713
#endif	/* !NANO_SMALL */

714
715
    /* If answer is now == "", copy last_search into answer 
	(and prevanswer)...  */
Chris Allegretta's avatar
Chris Allegretta committed
716
    if (answer[0] == '\0')
717
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
718
    else
Chris Allegretta's avatar
Chris Allegretta committed
719
	last_search = mallocstrcpy(last_search, answer);
720

Chris Allegretta's avatar
Chris Allegretta committed
721
722
    prevanswer = mallocstrcpy(prevanswer, last_search);

723
#ifndef NANO_SMALL
724
725
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
726
#endif
727
728

    i = statusq(0, replace_list_2, last_replace,
729
730
731
732
#ifndef NANO_SMALL
		&replace_history,
#endif
		_("Replace with"));
733

734
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
735
    if (i == 0 && answer[0] != '\0')
736
737
	update_history(&replace_history, answer);
#endif	/* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
738
739

    begin = current;
740
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
741
742
743
744
745
    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
746
    current = begin;
747
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
748
    renumber_all();
749
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
750
751
752
753
754
755
756

    if (numreplaced >= 0)
	statusbar(__("Replaced %d occurrence", "Replaced %d occurrences",
		numreplaced), numreplaced);
    else
	not_found_msg(prevanswer);

Chris Allegretta's avatar
Chris Allegretta committed
757
758
759
760
761
762
763
764
765
766
    replace_abort();
    return 1;
}

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

767
int do_gotoline(int line, int save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
768
{
769
    if (line <= 0) {		/* Ask for it */
Chris Allegretta's avatar
Chris Allegretta committed
770
771
772
773
774
	if (statusq(0, goto_list, (line ? answer : ""),
#ifndef NANO_SMALL
	    0,
#endif
	    _("Enter line number"))) {
Chris Allegretta's avatar
Chris Allegretta committed
775
776
777
778
	    statusbar(_("Aborted"));
	    goto_abort();
	    return 0;
	}
779
780
781
782
783
784

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
785
	    goto_abort();
786
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
787
788
789
	}
    }

790
    for (current = fileage; current->next != NULL && line > 1; line--)
791
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
792

793
    current_x = 0;
794

795
    /* if save_pos is nonzero, don't change the cursor position when
796
797
798
799
800
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
801
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
802
803
804
805
806
807
    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
808
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
809
}
810

811
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
812
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
813
814
815
816
817
{
    /* 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);
818

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
819
820
821
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
822
823

    /* set the rest of the coordinates up */
824
825
826
827
828
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
829
830
831
832
833

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
834
    const char *pos, *brackets = "([{<>}])";
835
    char regexp_pat[] = "[  ]";
Chris Allegretta's avatar
Chris Allegretta committed
836
    int offset, have_search_offscreen = 0, flagsave, current_x_save, count = 1;
837
838
839
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
840
841
842

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

843
    if (((pos = strchr(brackets, ch_under_cursor)) == NULL) || (((offset = pos - brackets) < 8) == 0)) {
844
845
846
847
848
849
850
851
852
853
854
855
856
	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
857
/* apparent near redundancy with regexp_pat[] here is needed, [][] works, [[]] doesn't */
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872

    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;
873
	if (findnextstr(1, 1, current, current_x, regexp_pat) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
874
875
876
877
	    have_search_offscreen |= search_offscreen;

	    /* found identical bracket */
	    if (current->data[current_x] == ch_under_cursor)
878
		count++;
Chris Allegretta's avatar
Chris Allegretta committed
879
880
881
	    else {

		/* found complementary bracket */
882
		if (!(--count)) {
Chris Allegretta's avatar
Chris Allegretta committed
883
		    if (have_search_offscreen)
884
885
886
887
888
			edit_update(current, CENTER);
		    else
			update_line(current, current_x);
		    placewewant = xplustabs();
		    reset_cursor();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
889
		    break;
890
891
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
892
893
894
	} else {

	    /* didn't find either left or right bracket */
895
896
897
898
899
900
901
902
903
904
905
906
907
	    statusbar(_("No matching bracket"));
	    current_x = current_x_save;
	    current = current_save;
	    break;
	}
    }

    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
    flags = flagsave;
    return 0;
}
#endif
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934

#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
935
    for (; h->next != NULL; h = h->next)
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
	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
968
969
970
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
971
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
972
	remove_node(p);			/* delete identical older string */
973
974
975
976
977
978
979
980
	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++;
981
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
982
  up_hs:
983
984
985
986
987
988
    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
989
    if (h->current->next != NULL) {	/* any older entries? */
990
991
992
993
994
995
996
997
	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
998
    if (h->current->prev != NULL) {
999
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1000
	if (h->current->prev != NULL)
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
	    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
1011
1012
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
	    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
1027
    for (p = h->next; (n = p->next); p = n)
1028
1029
1030
1031
1032
	remove_node(p);
}

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