search.c 27 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 we don't already have a backupstring, set it. */
111
    if (backupstring == NULL)
112
	backupstring = mallocstrcpy(NULL, "");
113

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

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

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

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

139
#ifndef NANO_SMALL
140
141
	/* This string is just a modifier for the search prompt,
	   no grammar is implied */
142
143
144
	ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
#endif
		"",
145
146
147
148
149

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

150
#ifndef NANO_SMALL
151
152
	/* This string is just a modifier for the search prompt,
	   no grammar is implied */
153
154
155
	ISSET(REVERSE_SEARCH) ? _(" [Backwards]") :
#endif
		"",
156

Chris Allegretta's avatar
Chris Allegretta committed
157
158
	replacing ? _(" (to replace)") : "",
	buf);
Chris Allegretta's avatar
Chris Allegretta committed
159

Chris Allegretta's avatar
Chris Allegretta committed
160
161
162
    /* Release buf now that we don't need it anymore */
    free(buf);

163
164
165
    free(backupstring);
    backupstring = NULL;

Chris Allegretta's avatar
Chris Allegretta committed
166
    /* Cancel any search, or just return with no previous search */
Chris Allegretta's avatar
Chris Allegretta committed
167
    if (i == -1 || (i < 0 && last_search[0] == '\0')) {
Chris Allegretta's avatar
Chris Allegretta committed
168
169
	statusbar(_("Search Cancelled"));
	reset_cursor();
170
171
172
#ifndef NANO_SMALL
	search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
173
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
174
175
176
    } else {
	switch (i) {
	case -2:	/* Same string */
177
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
178
	    if (ISSET(USE_REGEXP))
Chris Allegretta's avatar
Chris Allegretta committed
179
		/* If answer is "", use last_search! */
180
181
182
183
184
		if (regexp_init(last_search) == 0) {
		    statusbar(regex_error, last_search);
		    reset_cursor();
		    return -3;
		}
185
#endif
Chris Allegretta's avatar
Chris Allegretta committed
186
187
	    break;
	case 0:		/* They entered something new */
188
	    last_replace[0] = '\0';
189
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
190
	    if (ISSET(USE_REGEXP))
191
		if (regexp_init(answer) == 0) {
192
		    statusbar(regex_error, answer);
193
194
195
196
197
198
		    reset_cursor();
#ifndef NANO_SMALL
		    search_history.current = search_history.next;
#endif
		    return -3;
		}
199
#endif
Chris Allegretta's avatar
Chris Allegretta committed
200
201
202
203
204
205
206
207
208
209
	    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;
210
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
211
212
213
214
	case TOGGLE_REGEXP_KEY:
	    TOGGLE(USE_REGEXP);
	    backupstring = mallocstrcpy(backupstring, answer);
	    return 1;
215
#endif
Chris Allegretta's avatar
Chris Allegretta committed
216
217
218
219
220
#endif /* !NANO_SMALL */
	case NANO_OTHERSEARCH_KEY:
	    backupstring = mallocstrcpy(backupstring, answer);
	    return -2;		/* Call the opposite search function */
	case NANO_FROMSEARCHTOGOTO_KEY:
221
222
223
#ifndef NANO_SMALL
	    search_history.current = search_history.next;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
224
	    i = (int)strtol(answer, &buf, 10);	/* Just testing answer here */
225
226
227
228
	    if (!(errno == ERANGE || *answer == '\0' || *buf != '\0'))
		do_gotoline(-1, 0);
	    else
		do_gotoline_void();
Chris Allegretta's avatar
Chris Allegretta committed
229
230
231
232
233
	    return -3;
	default:
	    do_early_abort();
	    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
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
390
    wrap_reset();
391
#endif
392
393
394
    i = search_init(0);
    switch (i) {
    case -1:
Chris Allegretta's avatar
Chris Allegretta committed
395
396
397
	current = fileptr;
	search_abort();
	return 0;
398
    case -3:
Chris Allegretta's avatar
Chris Allegretta committed
399
400
	search_abort();
	return 0;
401
    case -2:
Chris Allegretta's avatar
Chris Allegretta committed
402
403
	do_replace();
	return 0;
404
    case 1:
Chris Allegretta's avatar
Chris Allegretta committed
405
406
407
408
	do_search();
	search_abort();
	return 1;
    }
409

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

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

422
    search_last_line = 0;
423
424
    didfind = findnextstr(FALSE, FALSE, current, current_x, answer);

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

Chris Allegretta's avatar
Chris Allegretta committed
431
    search_abort();
432

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

436
437
438
439
440
441
442
443
444
/* 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 */

445
#ifndef DISABLE_WRAPPING
446
    wrap_reset();
447
#endif
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
473
474
475
476
477
    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
478
479
void replace_abort(void)
{
480
    /* Identical to search_abort, so we'll call it here.  If it
481
       does something different later, we can change it back.  For now
482
       it's just a waste to duplicate code */
483
    search_abort();
484
    placewewant = xplustabs();
485
486
}

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

    char *c;
    int new_size = strlen(current->data) + 1;
496
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
497
498
499

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
500
501
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
502
503

    c = last_replace;
504
    while (*c != '\0') {
505
506
507
508
509
510
	if (*c != '\\') {
	    if (create_flag)
		*string++ = *c;
	    c++;
	    new_size++;
	} else {
511
	    int num = (int) *(c + 1) - (int) '0';
512
513
	    if (num >= 1 && num <= 9) {

514
		int i = regmatches[num].rm_eo - regmatches[num].rm_so;
515
516
517

		if (num > search_regexp.re_nsub) {
		    /* Ugh, they specified a subexpression that doesn't
Chris Allegretta's avatar
Chris Allegretta committed
518
		     * exist. */
519
520
521
522
523
524
525
		    return -1;
		}

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

		/* But add the length of the subexpression to new_size */
526
		new_size += i;
527
528
529

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

	    } else {
		if (create_flag)
		    *string++ = *c;
		c++;
		new_size++;
	    }
	}
543
544
545
    }

    if (create_flag)
Chris Allegretta's avatar
Chris Allegretta committed
546
	*string = '\0';
547
548
549

    return new_size;
}
550
#endif
551

Chris Allegretta's avatar
Chris Allegretta committed
552
char *replace_line(void)
553
554
555
556
557
558
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

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

576
    /* Create buffer */
577
    copy = charalloc(new_line_size);
578
579
580

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
581
    copy[current_x] = '\0';
582
583
584

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
585
	strcat(copy, last_replace);
586
#ifdef HAVE_REGEX_H
587
    else
588
	replace_regexp(copy + current_x, 1);
589
#endif
590
591

    /* The tail of the original line */
Chris Allegretta's avatar
Chris Allegretta committed
592
593
594
595
596
597

    /* 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 ;-) */
598
599
600
601
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
602
603
}

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

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
613
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
614

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

633
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
634
    while (1) {
635
636
	size_t match_len;

637
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
638
639
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
640

641
642
	if (current->lineno <= edittop->lineno
	    || current->lineno >= editbot->lineno)
643
	    edit_update(current, CENTER);
644

Chris Allegretta's avatar
Chris Allegretta committed
645
	/* No more matches.  Done! */
646
	if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
647
648
	    break;

649
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
650
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
651
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
652

Chris Allegretta's avatar
Chris Allegretta committed
653
	/* If we're here, we've found the search string */
Chris Allegretta's avatar
Chris Allegretta committed
654
655
656
	if (numreplaced == -1)
	    numreplaced = 0;

657
658
659
660
661
662
663
#ifdef HAVE_REGEX_H
	if (ISSET(USE_REGEXP))
	    match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
	else
#endif
	    match_len = strlen(prevanswer);

664
	if (!replaceall) {
665
666
667
668
669
670
	    char *exp_word;
	    size_t xpt = xplustabs();

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

671
	    curs_set(0);
672
	    do_replace_highlight(TRUE, exp_word);
673

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

676
677
	    do_replace_highlight(FALSE, exp_word);
	    free(exp_word);
678
679
680
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
681
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
682
683
684
	    long length_change;
	    size_t match_len;

Chris Allegretta's avatar
Chris Allegretta committed
685
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
686
687
		replaceall = 1;

688
	    copy = replace_line();
689
	    if (copy == NULL) {
Jordi Mallach's avatar
   
Jordi Mallach committed
690
		statusbar(_("Replace failed: unknown subexpression!"));
691
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
692
		return 0;
693
	    }
Chris Allegretta's avatar
Chris Allegretta committed
694

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

697
698
699
700
701
702
#ifdef HAVE_REGEX_H
	    if (ISSET(USE_REGEXP))
		match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
	    else
#endif
		match_len = strlen(prevanswer);
703

704
705
706
707
708
709
710
711
#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
712

713
714
715
716
717
718
	    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;
	    }
719

720
721
722
723
724
725
726
	    /* 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;
727

728
729
730
731
	    /* Cleanup */
	    totsize += length_change;
	    free(current->data);
	    current->data = copy;
732

Chris Allegretta's avatar
Chris Allegretta committed
733
734
735
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
736
737
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
738
739
740
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
741
742
743
744
    /* 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
745
746
747
    return numreplaced;
}

Chris Allegretta's avatar
Chris Allegretta committed
748
/* Replace a string. */
Chris Allegretta's avatar
Chris Allegretta committed
749
750
751
752
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
Chris Allegretta's avatar
Chris Allegretta committed
753
    char *prevanswer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
754

Chris Allegretta's avatar
Chris Allegretta committed
755
756
757
758
759
760
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    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;
    }

778
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
779
    if (answer[0] != '\0')
780
	update_history(&search_history, answer);
781
782
#endif	/* !NANO_SMALL */

783
784
    /* If answer is now == "", copy last_search into answer 
	(and prevanswer)...  */
Chris Allegretta's avatar
Chris Allegretta committed
785
    if (answer[0] == '\0')
786
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
787
    else
Chris Allegretta's avatar
Chris Allegretta committed
788
	last_search = mallocstrcpy(last_search, answer);
789

Chris Allegretta's avatar
Chris Allegretta committed
790
791
    prevanswer = mallocstrcpy(prevanswer, last_search);

792
#ifndef NANO_SMALL
793
794
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
795
#endif
796
797

    i = statusq(0, replace_list_2, last_replace,
798
799
800
801
#ifndef NANO_SMALL
		&replace_history,
#endif
		_("Replace with"));
802

803
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
804
    if (i == 0 && answer[0] != '\0')
805
806
	update_history(&replace_history, answer);
#endif	/* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
807
808

    begin = current;
809
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
810
811
812
813
814
    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
815
    current = begin;
816
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
817
    renumber_all();
818
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
819
820

    if (numreplaced >= 0)
821
	statusbar(P_("Replaced %d occurrence", "Replaced %d occurrences",
Chris Allegretta's avatar
Chris Allegretta committed
822
823
824
825
		numreplaced), numreplaced);
    else
	not_found_msg(prevanswer);

826
    free(prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
827
828
829
830
    replace_abort();
    return 1;
}

831
int do_gotoline(int line, int save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
832
{
833
    if (line <= 0) {		/* Ask for it */
Chris Allegretta's avatar
Chris Allegretta committed
834
	int st = statusq(FALSE, goto_list, line != 0 ? answer : "",
Chris Allegretta's avatar
Chris Allegretta committed
835
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
836
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
837
#endif
Chris Allegretta's avatar
Chris Allegretta committed
838
839
840
841
			_("Enter line number"));

	/* Cancel, or Enter with blank string. */
	if (st == -1 || st == -2)
Chris Allegretta's avatar
Chris Allegretta committed
842
	    statusbar(_("Aborted"));
Chris Allegretta's avatar
Chris Allegretta committed
843
	if (st != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
844
	    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
845
846
	    return 0;
	}
847
848
849
850
851
852

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
853
	    display_main_list();
854
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
855
856
857
	}
    }

858
    for (current = fileage; current->next != NULL && line > 1; line--)
859
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
860

861
    current_x = 0;
862

863
    /* if save_pos is nonzero, don't change the cursor position when
864
865
866
867
868
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
869
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
870
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
871
872
873
874
875
    return 1;
}

int do_gotoline_void(void)
{
876
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
877
}
878

879
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
880
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
881
882
883
884
885
{
    /* 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);
886

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
887
888
889
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
890
891

    /* set the rest of the coordinates up */
892
893
894
895
896
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
897
898
899
900
901

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
902
    const char *pos, *brackets = "([{<>}])";
903
    char regexp_pat[] = "[  ]";
Chris Allegretta's avatar
Chris Allegretta committed
904
    int offset, have_search_offscreen = 0, flagsave, current_x_save, count = 1;
905
906
907
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
908
909
910

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

911
    if (((pos = strchr(brackets, ch_under_cursor)) == NULL) || (((offset = pos - brackets) < 8) == 0)) {
912
913
914
915
916
917
918
919
920
921
922
923
924
	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
925
/* apparent near redundancy with regexp_pat[] here is needed, [][] works, [[]] doesn't */
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940

    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;
941
	if (findnextstr(1, 1, current, current_x, regexp_pat) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
942
943
944
945
	    have_search_offscreen |= search_offscreen;

	    /* found identical bracket */
	    if (current->data[current_x] == ch_under_cursor)
946
		count++;
Chris Allegretta's avatar
Chris Allegretta committed
947
948
949
	    else {

		/* found complementary bracket */
950
		if (!(--count)) {
Chris Allegretta's avatar
Chris Allegretta committed
951
		    if (have_search_offscreen)
952
953
954
955
956
			edit_update(current, CENTER);
		    else
			update_line(current, current_x);
		    placewewant = xplustabs();
		    reset_cursor();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
957
		    break;
958
959
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
960
961
962
	} else {

	    /* didn't find either left or right bracket */
963
964
965
	    statusbar(_("No matching bracket"));
	    current_x = current_x_save;
	    current = current_save;
Chris Allegretta's avatar
Chris Allegretta committed
966
	    update_line(current, current_x);
967
968
969
970
971
972
973
974
975
976
	    break;
	}
    }

    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
    flags = flagsave;
    return 0;
}
#endif
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003

#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
1004
    for (; h->next != NULL; h = h->next)
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
	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
1024
    a = (historytype *)nmalloc(sizeof(historytype));
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
    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
1037
1038
1039
    if ((p = find_node(h->next, s)) != NULL) {
	if (p == h->next)		/* catch delete and re-insert of
					   same string in 1st node */
1040
	    goto up_hs;
Chris Allegretta's avatar
Chris Allegretta committed
1041
	remove_node(p);			/* delete identical older string */
1042
1043
1044
1045
1046
1047
1048
1049
	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++;
1050
    SET(HISTORY_CHANGED);
Chris Allegretta's avatar
Chris Allegretta committed
1051
  up_hs:
1052
1053
1054
1055
1056
1057
    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
1058
    if (h->current->next != NULL) {	/* any older entries? */
1059
1060
1061
1062
1063
1064
1065
1066
	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
1067
    if (h->current->prev != NULL) {
1068
	h->current = h->current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
1069
	if (h->current->prev != NULL)
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
	    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
1080
1081
    for (p = h->current->next; p->next != NULL; p = p->next) {
	if (strncmp(s, p->data, h->len) == 0 && strlen(p->data) != h->len) {
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
	    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
1096
    for (p = h->next; (n = p->next); p = n)
1097
1098
1099
1100
1101
	remove_node(p);
}

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