search.c 22.2 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   search.c                                                             *
 *                                                                        *
5
 *   Copyright (C) 2000-2002 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>
Chris Allegretta's avatar
Chris Allegretta committed
29
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
32

33
34
/* Regular expression helper functions */

35
#ifdef HAVE_REGEX_H
36
37
void regexp_init(const char *regexp)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
38
    regcomp(&search_regexp, regexp, (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE) | REG_EXTENDED);
39
40
41
    SET(REGEXP_COMPILED);
}

42
void regexp_cleanup(void)
43
44
45
46
{
    UNSET(REGEXP_COMPILED);
    regfree(&search_regexp);
}
47
#endif
48

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
76
77
78
void search_init_globals(void)
{
    if (last_search == NULL) {
79
	last_search = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
80
	last_search[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
81
82
    }
    if (last_replace == NULL) {
83
	last_replace = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
84
	last_replace[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
85
86
87
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
88
89
/* Set up the system variables for a search or replace.  Returns -1 on
   abort, 0 on success, and 1 on rerun calling program 
90
91
   Return -2 to run opposite program (search -> replace, replace ->
   search).
Chris Allegretta's avatar
Chris Allegretta committed
92

93
94
   replacing = 1 if we call from do_replace, 0 if called from do_search
   func. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
95
int search_init(int replacing)
Chris Allegretta's avatar
Chris Allegretta committed
96
{
97
    int i = 0;
98
    char *buf;
99
    static char *backupstring = NULL;
100

Chris Allegretta's avatar
Chris Allegretta committed
101
    search_init_globals();
102

Chris Allegretta's avatar
Chris Allegretta committed
103
104
105
106
107
    /* Clear the backupstring if we've changed from Pico mode to regular
	mode */
    if (ISSET(CLEAR_BACKUPSTRING)) {
	free(backupstring);
	backupstring = NULL;
108
	UNSET(CLEAR_BACKUPSTRING);
Chris Allegretta's avatar
Chris Allegretta committed
109
110
    }
	
111
112
     /* Okay, fun time.  backupstring is our holder for what is being 
	returned from the statusq call.  Using answer for this would be tricky.
113
	Here, if we're using PICO_MODE, we only want nano to put the
114
115
116
	old string back up as editable if it's not the same as last_search.

	Otherwise, if we don't already have a backupstring, set it to
Chris Allegretta's avatar
Chris Allegretta committed
117
	last_search. */
118

119
    if (ISSET(PICO_MODE)) {
120
121
122
123
124
125
	if (backupstring == NULL || !strcmp(backupstring, last_search))
	    backupstring = mallocstrcpy(backupstring, "");
    }
    else if (backupstring == NULL)
	backupstring = mallocstrcpy(backupstring, last_search);

Chris Allegretta's avatar
Chris Allegretta committed
126
    /* If using Pico messages, we do things the old fashioned way... */
Chris Allegretta's avatar
Chris Allegretta committed
127
    if (ISSET(PICO_MODE) && last_search[0]) {
Chris Allegretta's avatar
Chris Allegretta committed
128
	buf = charalloc(COLS / 3 + 7);
Chris Allegretta's avatar
Chris Allegretta committed
129
	/* We use COLS / 3 here because we need to see more on the line */
Chris Allegretta's avatar
Chris Allegretta committed
130
131
	sprintf(buf, " [%.*s%s]", COLS / 3, last_search,
		strlen(last_search) > COLS / 3 ? "..." : "");
Chris Allegretta's avatar
Chris Allegretta committed
132
133
    } else {
	buf = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
134
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
135
    }
Chris Allegretta's avatar
Chris Allegretta committed
136

137
    /* This is now one simple call.  It just does a lot */
138
    i = statusq(0, replacing ? replace_list : whereis_list, backupstring,
139
	"%s%s%s%s%s%s", 
Chris Allegretta's avatar
Chris Allegretta committed
140
	_("Search"),
141
142
143
144
145
146
147
148
149
150
151
152
153

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

Chris Allegretta's avatar
Chris Allegretta committed
157
158
159
    /* Release buf now that we don't need it anymore */
    free(buf);

Chris Allegretta's avatar
Chris Allegretta committed
160
    /* Cancel any search, or just return with no previous search */
Chris Allegretta's avatar
Chris Allegretta committed
161
    if (i == -1 || (i < 0 && last_search[0] == '\0')) {
Chris Allegretta's avatar
Chris Allegretta committed
162
163
	statusbar(_("Search Cancelled"));
	reset_cursor();
164
165
	free(backupstring);
	backupstring = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
166
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
167
168
169
    } else {
	switch (i) {
	case -2:	/* Same string */
170
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
171
172
173
174
	    if (ISSET(USE_REGEXP))
		/* If we're in Pico mode, and answer is "", use
		   last_search! */
		regexp_init(ISSET(PICO_MODE) ? last_search : answer);
175
#endif
Chris Allegretta's avatar
Chris Allegretta committed
176
177
	    break;
	case 0:		/* They entered something new */
178
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
179
180
	    if (ISSET(USE_REGEXP))
		regexp_init(answer);
181
#endif
Chris Allegretta's avatar
Chris Allegretta committed
182
183
184
185
186
187
188
189
190
191
192
193
194
	    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;
195
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
196
197
198
199
	case TOGGLE_REGEXP_KEY:
	    TOGGLE(USE_REGEXP);
	    backupstring = mallocstrcpy(backupstring, answer);
	    return 1;
200
#endif
Chris Allegretta's avatar
Chris Allegretta committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#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;
	    do_gotoline_void();
	    return -3;
	default:
	    do_early_abort();
	    free(backupstring);
	    backupstring = NULL;
	    return -3;
	}
Chris Allegretta's avatar
Chris Allegretta committed
216
217
218
219
    }
    return 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
220
int is_whole_word(int curr_pos, const char *datastr, const char *searchword)
221
{
Chris Allegretta's avatar
Chris Allegretta committed
222
    size_t sln = curr_pos + strlen(searchword);
223

Chris Allegretta's avatar
Chris Allegretta committed
224
225
226
227
    /* start of line or previous character not a letter and end of line
     * or next character not a letter */
    return (curr_pos < 1 || !isalpha((int) datastr[curr_pos - 1])) &&
	(sln == strlen(datastr) || !isalpha((int) datastr[sln]));
228
229
}

Chris Allegretta's avatar
Chris Allegretta committed
230
231
static int past_editbuff;
	/* findnextstr() is now searching lines not displayed */
232

Chris Allegretta's avatar
Chris Allegretta committed
233
filestruct *findnextstr(int quiet, int bracket_mode,
234
235
			const filestruct *begin, int beginx,
			const char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
236
{
Chris Allegretta's avatar
Chris Allegretta committed
237
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
238
    const char *searchstr, *rev_start = NULL, *found = NULL;
239
    int current_x_find = 0;
Chris Allegretta's avatar
Chris Allegretta committed
240

241
242
    past_editbuff = 0;

Chris Allegretta's avatar
Chris Allegretta committed
243
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
244
245
246
247
248
249
	/* 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
250

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

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

Chris Allegretta's avatar
Chris Allegretta committed
256
257
258
259
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
260
		update_line(fileptr, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
261
262
	        return NULL;
	    }
263

Chris Allegretta's avatar
Chris Allegretta committed
264
	    update_line(fileptr, 0);
265
266
267
268

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

Chris Allegretta's avatar
Chris Allegretta committed
269
	    fileptr = fileptr->next;
270

271
	    if (fileptr == editbot)
272
		past_editbuff = 1;
Chris Allegretta's avatar
Chris Allegretta committed
273
274
275

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

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

	    searchstr = fileptr->data;
	}
293

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

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

Chris Allegretta's avatar
Chris Allegretta committed
319
	    update_line(fileptr, 0);
320
321
322
323

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

Chris Allegretta's avatar
Chris Allegretta committed
324
325
	    fileptr = fileptr->prev;

326
	    if (fileptr == edittop->prev)
327
		past_editbuff = 1;
328

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

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

363
364
365
366
367
    if (!bracket_mode) {
	if (past_editbuff)
	   edit_update(fileptr, CENTER);
	else
	   update_line(current, current_x);
368

369
370
371
	placewewant = xplustabs();
	reset_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
372
373
374
    return fileptr;
}

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

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

    /* The sneaky user deleted the previous search string */
Chris Allegretta's avatar
Chris Allegretta committed
402
    if (!ISSET(PICO_MODE) && answer[0] == '\0') {
Jordi Mallach's avatar
   
Jordi Mallach committed
403
	statusbar(_("Search Cancelled"));
404
405
406
407
	search_abort();
	return 0;
    }

408
     /* If answer is now == "", then PICO_MODE is set.  So, copy
409
410
	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
    search_last_line = 0;
417
418
419
420
421
422
    didfind = findnextstr(FALSE, FALSE, current, current_x, answer);

    if ((fileptr == current) && (fileptr_x == current_x) &&
	didfind != NULL)
	statusbar(_("This is the only occurrence"));

Chris Allegretta's avatar
Chris Allegretta committed
423
    search_abort();
424

Chris Allegretta's avatar
Chris Allegretta committed
425
426
427
428
429
    return 1;
}

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

437
#ifdef HAVE_REGEX_H
438
439
440
441
442
443
444
445
int replace_regexp(char *string, int create_flag)
{
    /* split personality here - if create_flag is null, just calculate
     * the size of the replacement line (necessary because of
     * subexpressions like \1 \2 \3 in the replaced text) */

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

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
450
451
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
452
453
454

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

		int i = regmatches[num].rm_so;

		if (num > search_regexp.re_nsub) {
		    /* Ugh, they specified a subexpression that doesn't
Chris Allegretta's avatar
Chris Allegretta committed
468
		       exist. */
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
		    return -1;
		}

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

		/* But add the length of the subexpression to new_size */
		new_size += regmatches[num].rm_eo - regmatches[num].rm_so;

		/* And if create_flag is set, append the result of the
		 * subexpression match to the new line */
		while (create_flag && i < regmatches[num].rm_eo)
		    *string++ = *(current->data + i++);

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

    if (create_flag)
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
	(void) 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
}

551
552
553
554
555
556
557
558
void print_replaced(int num)
{
    if (num > 1)
	statusbar(_("Replaced %d occurrences"), num);
    else if (num == 1)
	statusbar(_("Replaced 1 occurrence"));
}

559
/* step through each replace word and prompt user before replacing word */
Chris Allegretta's avatar
Chris Allegretta committed
560
561
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
562
{
Chris Allegretta's avatar
Chris Allegretta committed
563
    int replaceall = 0, numreplaced = 0;
564
565

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
566
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
567

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

Chris Allegretta's avatar
Chris Allegretta committed
586
    if (ISSET(PICO_MODE) && answer[0] == '\0')
587
588
	answer = mallocstrcpy(answer, last_replace);

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

	/* No more matches.  Done! */
596
	if (!fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
597
598
	    break;

599
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
600
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
601
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
602

Chris Allegretta's avatar
Chris Allegretta committed
603
	/* If we're here, we've found the search string */
604
605
606
607
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

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

610
611
612
613
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
614
615
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
616
617
		replaceall = 1;

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

	    /* Cleanup */
626
	    totsize -= strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
627
628
	    free(current->data);
	    current->data = copy;
629
	    totsize += strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
630

631
	    if (!ISSET(REVERSE_SEARCH)) {
Chris Allegretta's avatar
Chris Allegretta committed
632
633
		/* Stop bug where we replace a substring of the
		   replacement text */
634
		current_x += strlen(last_replace) - 1;
Chris Allegretta's avatar
Chris Allegretta committed
635

636
637
638
		/* Adjust the original cursor position - COULD BE IMPROVED */
		if (search_last_line) {
		    *beginx += strlen(last_replace) - strlen(last_search);
639

640
		    /* For strings that cross the search start/end boundary */
Chris Allegretta's avatar
Chris Allegretta committed
641

642
643
644
645
646
647
648
649
650
651
652
653
654
655
		    /* Don't go outside of allocated memory */
		    if (*beginx < 1)
			*beginx = 1;
		}
	    } else {
		if (current_x > 1)
		    current_x--;

		if (search_last_line) {
		    *beginx += strlen(last_replace) - strlen(last_search);

		    if (*beginx > strlen(current->data))
			*beginx = strlen(current->data);
		}
656
657
	    }

Chris Allegretta's avatar
Chris Allegretta committed
658
659
660
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
661
662
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
663
664
665
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
666
667
668
669
670
671
672
673
    return numreplaced;
}

/* Replace a string */
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
Chris Allegretta's avatar
Chris Allegretta committed
674
    char *prevanswer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
675

Chris Allegretta's avatar
Chris Allegretta committed
676
677
678
679
680
681
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
    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;
    }

699
    /* Again, there was a previous string, but they deleted it and hit enter */
Chris Allegretta's avatar
Chris Allegretta committed
700
    if (!ISSET(PICO_MODE) && answer[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
701
702
703
704
705
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
    }

706
     /* If answer is now == "", then PICO_MODE is set.  So, copy
707
	last_search into answer (and prevanswer)... */
Chris Allegretta's avatar
Chris Allegretta committed
708
    if (answer[0] == '\0')
709
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
710
    else
Chris Allegretta's avatar
Chris Allegretta committed
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
	last_search = mallocstrcpy(last_search, answer);
    prevanswer = mallocstrcpy(prevanswer, last_search);

    if (ISSET(PICO_MODE) && last_replace[0] != '\0') {
	if (strlen(last_replace) > COLS / 3) {
	    char *buf = charalloc(COLS / 3 + 3);

	    strncpy(buf, last_replace, COLS / 3 - 1);
	    strcpy(buf + COLS / 3 - 1, "...");
	    i = statusq(0, replace_list_2, "", _("Replace with [%s]"),
			buf);
	    free(buf);
	} else
	    i = statusq(0, replace_list_2, "", _("Replace with [%s]"),
			last_replace);
    } else
	i = statusq(0, replace_list_2, last_replace, _("Replace with"));
Chris Allegretta's avatar
Chris Allegretta committed
728
729
730

    /* save where we are */
    begin = current;
731
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
732
733
734
735
736
    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
737
    current = begin;
738
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
739
    renumber_all();
740
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
741
742
743
744
745
746
747
748
749
750
751
    print_replaced(numreplaced);
    replace_abort();
    return 1;
}

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

752
int do_gotoline(int line, int save_pos)
Chris Allegretta's avatar
Chris Allegretta committed
753
{
754
    if (line <= 0) {		/* Ask for it */
755
	if (statusq(0, goto_list, "", _("Enter line number"))) {
Chris Allegretta's avatar
Chris Allegretta committed
756
757
758
759
	    statusbar(_("Aborted"));
	    goto_abort();
	    return 0;
	}
760
761
762
763
764
765

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
766
	    goto_abort();
767
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
768
769
770
	}
    }

771
    for (current = fileage; current->next != NULL && line > 1; line--)
772
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
773

774
    current_x = 0;
775
776
777
778
779
780
781

    /* if save_pos is non-zero, don't change the cursor position when
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
782

783
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
784
785
786
787
788
789
    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
790
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
791
}
792

793
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
794
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
795
796
797
798
799
{
    /* 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);
800

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
801
802
803
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
804
805

    /* set the rest of the coordinates up */
806
807
808
809
810
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
811
812
813
814
815

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
816
    const char *pos, *brackets = "([{<>}])";
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
    char regexp_pat[] = "[  ]";
    int offset, have_past_editbuff = 0, flagsave, current_x_save, count = 1;
    filestruct *current_save;

    ch_under_cursor = current->data[current_x];
 
    if ((!(pos = strchr(brackets, ch_under_cursor))) || (!((offset = pos - brackets) < 8))) {
	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);

/* apparent near redundancy with regexp_pat[] here is needed, [][] works, [[]] doesn't */ 

    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;
	if (findnextstr(1, 1, current, current_x, regexp_pat)) {
	    have_past_editbuff |= past_editbuff;
	    if (current->data[current_x] == ch_under_cursor)	/* found identical bracket  */
		count++;
	    else {						/* found complementary bracket */
		if (!(--count)) {
		    if (have_past_editbuff)
			edit_update(current, CENTER);
		    else
			update_line(current, current_x);
		    placewewant = xplustabs();
		    reset_cursor();
		    break ;
		}
	    }
	} else {						/* didn't find either left or right bracket */
	    statusbar(_("No matching bracket"));
	    current_x = current_x_save;
	    current = current_save;
	    break;
	}
    }

    if (ISSET(REGEXP_COMPILED))
	regexp_cleanup();
    flags = flagsave;
    return 0;
}
#endif