search.c 22.3 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
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
34
35
36
37
38
39
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

40
41
/* Regular expression helper functions */

42
#ifdef HAVE_REGEX_H
43
44
45
46
47
48
void regexp_init(const char *regexp)
{
    regcomp(&search_regexp, regexp, ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE);
    SET(REGEXP_COMPILED);
}

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

Chris Allegretta's avatar
Chris Allegretta committed
56
57
58
void search_init_globals(void)
{
    if (last_search == NULL) {
59
	last_search = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
60
	last_search[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
61
62
    }
    if (last_replace == NULL) {
63
	last_replace = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
64
	last_replace[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
65
66
67
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
68
69
/* Set up the system variables for a search or replace.  Returns -1 on
   abort, 0 on success, and 1 on rerun calling program 
70
   Return -2 to run opposite program (search -> replace, replace -> search)
Chris Allegretta's avatar
Chris Allegretta committed
71
72
73

   replacing = 1 if we call from do_replace, 0 if called from do_search func.
*/
Chris Allegretta's avatar
Chris Allegretta committed
74
static int search_init(int replacing)
Chris Allegretta's avatar
Chris Allegretta committed
75
{
76
    int i = 0;
77
    char *buf;
78
    static char *backupstring = NULL;
79

Chris Allegretta's avatar
Chris Allegretta committed
80
    search_init_globals();
81

Chris Allegretta's avatar
Chris Allegretta committed
82
83
84
85
86
    /* Clear the backupstring if we've changed from Pico mode to regular
	mode */
    if (ISSET(CLEAR_BACKUPSTRING)) {
	free(backupstring);
	backupstring = NULL;
87
	UNSET(CLEAR_BACKUPSTRING);
Chris Allegretta's avatar
Chris Allegretta committed
88
89
    }
	
90
91
     /* Okay, fun time.  backupstring is our holder for what is being 
	returned from the statusq call.  Using answer for this would be tricky.
92
	Here, if we're using PICO_MODE, we only want nano to put the
93
94
95
	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
96
	last_search. */
97

98
    if (ISSET(PICO_MODE)) {
99
100
101
102
103
104
	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
105
    /* If using Pico messages, we do things the old fashioned way... */
Chris Allegretta's avatar
Chris Allegretta committed
106
    if (ISSET(PICO_MODE) && last_search[0]) {
Chris Allegretta's avatar
Chris Allegretta committed
107
	buf = charalloc(COLS / 3 + 7);
Chris Allegretta's avatar
Chris Allegretta committed
108
	/* We use COLS / 3 here because we need to see more on the line */
Chris Allegretta's avatar
Chris Allegretta committed
109
110
	sprintf(buf, " [%.*s%s]", COLS / 3, last_search,
		strlen(last_search) > COLS / 3 ? "..." : "");
Chris Allegretta's avatar
Chris Allegretta committed
111
112
    } else {
	buf = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
113
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
114
    }
Chris Allegretta's avatar
Chris Allegretta committed
115

116
    /* This is now one simple call.  It just does a lot */
117
    i = statusq(0, replacing ? replace_list : whereis_list, backupstring,
118
	"%s%s%s%s%s%s", 
Chris Allegretta's avatar
Chris Allegretta committed
119
	_("Search"),
120
121
122
123
124
125
126
127
128
129
130
131
132

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

Chris Allegretta's avatar
Chris Allegretta committed
136
137
138
    /* Release buf now that we don't need it anymore */
    free(buf);

Chris Allegretta's avatar
Chris Allegretta committed
139
    /* Cancel any search, or just return with no previous search */
Chris Allegretta's avatar
Chris Allegretta committed
140
    if (i == -1 || (i < 0 && last_search[0] == '\0')) {
Chris Allegretta's avatar
Chris Allegretta committed
141
142
	statusbar(_("Search Cancelled"));
	reset_cursor();
143
144
	free(backupstring);
	backupstring = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
145
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
146
147
148
    } else {
	switch (i) {
	case -2:	/* Same string */
149
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
150
151
152
153
	    if (ISSET(USE_REGEXP))
		/* If we're in Pico mode, and answer is "", use
		   last_search! */
		regexp_init(ISSET(PICO_MODE) ? last_search : answer);
154
#endif
Chris Allegretta's avatar
Chris Allegretta committed
155
156
	    break;
	case 0:		/* They entered something new */
157
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
158
159
	    if (ISSET(USE_REGEXP))
		regexp_init(answer);
160
#endif
Chris Allegretta's avatar
Chris Allegretta committed
161
162
163
164
165
166
167
168
169
170
171
172
173
	    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;
174
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
175
176
177
178
	case TOGGLE_REGEXP_KEY:
	    TOGGLE(USE_REGEXP);
	    backupstring = mallocstrcpy(backupstring, answer);
	    return 1;
179
#endif
Chris Allegretta's avatar
Chris Allegretta committed
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#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
195
196
197
198
    }
    return 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
199
void not_found_msg(const char *str)
200
{
201
    if (strlen(str) <= COLS / 2)
202
203
	statusbar(_("\"%s\" not found"), str);
    else {
Chris Allegretta's avatar
Chris Allegretta committed
204
	char *foo = mallocstrcpy(NULL, str);
205

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
206
	foo[COLS / 2] = '\0';
207
	statusbar(_("\"%s...\" not found"), foo);
208
	free(foo);
209
210
211
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
212
int is_whole_word(int curr_pos, const char *datastr, const char *searchword)
213
{
Chris Allegretta's avatar
Chris Allegretta committed
214
    size_t sln = curr_pos + strlen(searchword);
215

Chris Allegretta's avatar
Chris Allegretta committed
216
217
218
219
    /* 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]));
220
221
}

Chris Allegretta's avatar
Chris Allegretta committed
222
223
static int past_editbuff;
	/* findnextstr() is now searching lines not displayed */
224

Chris Allegretta's avatar
Chris Allegretta committed
225
226
filestruct *findnextstr(int quiet, int bracket_mode,
		const filestruct *begin, int beginx, const char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
227
{
Chris Allegretta's avatar
Chris Allegretta committed
228
    filestruct *fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
229
    const char *searchstr, *rev_start = NULL, *found = NULL;
230
    int current_x_find = 0;
Chris Allegretta's avatar
Chris Allegretta committed
231

232
233
    past_editbuff = 0;

Chris Allegretta's avatar
Chris Allegretta committed
234
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
235
236
237
238
239
240
	/* 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
241

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

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

Chris Allegretta's avatar
Chris Allegretta committed
247
248
249
250
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
251
		update_line(fileptr, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
252
253
	        return NULL;
	    }
254

Chris Allegretta's avatar
Chris Allegretta committed
255
	    update_line(fileptr, 0);
256
257
258
259

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

Chris Allegretta's avatar
Chris Allegretta committed
260
	    fileptr = fileptr->next;
261

262
	    if (fileptr == editbot)
263
		past_editbuff = 1;
Chris Allegretta's avatar
Chris Allegretta committed
264
265
266

	    /* EOF reached ?, wrap around once */
	    if (fileptr == NULL) {
267
268
		/* don't wrap if looking for bracket match */
		if (bracket_mode)
Chris Allegretta's avatar
Chris Allegretta committed
269
		    return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
270
		fileptr = fileage;
271
		past_editbuff = 1;
272
		if (!quiet) {
Chris Allegretta's avatar
Chris Allegretta committed
273
274
		    statusbar(_("Search Wrapped"));
		    SET(DISABLE_CURPOS);
275
		}
Chris Allegretta's avatar
Chris Allegretta committed
276
277
278
279
280
281
282
283
	    }

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

	    searchstr = fileptr->data;
	}
284

Chris Allegretta's avatar
Chris Allegretta committed
285
286
287
	/* We found an instance */
	current_x_find = found - fileptr->data;
	/* Ensure we haven't wrapped around again! */
288
	if ((search_last_line) && (current_x_find > beginx)) {
Chris Allegretta's avatar
Chris Allegretta committed
289
	    if (!quiet)
Chris Allegretta's avatar
Chris Allegretta committed
290
291
		not_found_msg(needle);
	    return NULL;
292
	}
293
294
295
    } 
#ifndef NANO_SMALL
    else {	/* reverse search */
Chris Allegretta's avatar
Chris Allegretta committed
296
297
298
	current_x_find = current_x - 1;
	/* Make sure we haven't passed the begining of the string */
	rev_start = &fileptr->data[current_x_find];
299
300
	searchstr = fileptr->data;

Chris Allegretta's avatar
Chris Allegretta committed
301
	/* Look for needle in searchstr */
302
	while ((found = strstrwrapper(searchstr, needle, rev_start, current_x_find)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
303
304
305
306
307
308
309
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
		return NULL;
	    }

Chris Allegretta's avatar
Chris Allegretta committed
310
	    update_line(fileptr, 0);
311
312
313
314

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

Chris Allegretta's avatar
Chris Allegretta committed
315
316
	    fileptr = fileptr->prev;

317
	    if (fileptr == edittop->prev)
318
		past_editbuff = 1;
319

Chris Allegretta's avatar
Chris Allegretta committed
320
321
	    /* SOF reached ?, wrap around once */
/* ? */	    if (fileptr == NULL) {
322
323
		if (bracket_mode)
		   return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
324
		fileptr = filebot;
325
		past_editbuff = 1;
326
		if (!quiet) {
Chris Allegretta's avatar
Chris Allegretta committed
327
		    statusbar(_("Search Wrapped"));
328
329
		    SET(DISABLE_CURPOS);
		}
Chris Allegretta's avatar
Chris Allegretta committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
	    }
	    /* 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
347
    }
Chris Allegretta's avatar
Chris Allegretta committed
348
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
349

Chris Allegretta's avatar
Chris Allegretta committed
350
    /* Set globals now that we are sure we found something */
351
352
353
    current = fileptr;
    current_x = current_x_find;

354
355
356
357
358
    if (!bracket_mode) {
	if (past_editbuff)
	   edit_update(fileptr, CENTER);
	else
	   update_line(current, current_x);
359

360
361
362
	placewewant = xplustabs();
	reset_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
363
364
365
366
367
368
369
370
    return fileptr;
}

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

374
#ifdef HAVE_REGEX_H
375
    if (ISSET(REGEXP_COMPILED))
376
	regexp_cleanup();
377
#endif
Chris Allegretta's avatar
Chris Allegretta committed
378
379
}

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

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

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

413
     /* If answer is now == "", then PICO_MODE is set.  So, copy
414
415
	last_search into answer... */

Chris Allegretta's avatar
Chris Allegretta committed
416
    if (answer[0] == '\0')
417
418
419
420
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

421
    search_last_line = 0;
422
423
424
425
426
427
    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
428
    search_abort();
429

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

void print_replaced(int num)
{
    if (num > 1)
Jordi Mallach's avatar
   
Jordi Mallach committed
436
	statusbar(_("Replaced %d occurrences"), num);
Chris Allegretta's avatar
Chris Allegretta committed
437
    else if (num == 1)
Jordi Mallach's avatar
   
Jordi Mallach committed
438
	statusbar(_("Replaced 1 occurrence"));
Chris Allegretta's avatar
Chris Allegretta committed
439
440
441
442
}

void replace_abort(void)
{
443
    /* Identical to search_abort, so we'll call it here.  If it
444
       does something different later, we can change it back.  For now
445
       it's just a waste to duplicate code */
446
    search_abort();
447
    placewewant = xplustabs();
448
449
}

450
#ifdef HAVE_REGEX_H
451
452
453
454
455
456
457
458
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;
459
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
460
461
462

    new_size -= search_match_count;

Chris Allegretta's avatar
Chris Allegretta committed
463
464
    /* Iterate through the replacement text to handle subexpression
     * replacement using \1, \2, \3, etc. */
465
466
467

    c = last_replace;
    while (*c) {
468
469
470
471
472
473
474
475
476
477
478
479
480
	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
481
		       exist. */
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
		    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++;
	    }
	}
503
504
505
    }

    if (create_flag)
506
	*string = 0;
507
508
509

    return new_size;
}
510
#endif
511

Chris Allegretta's avatar
Chris Allegretta committed
512
char *replace_line(void)
513
514
515
516
517
518
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

    /* Calculate size of new line */
519
#ifdef HAVE_REGEX_H
520
    if (ISSET(USE_REGEXP)) {
521
522
523
	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
524
	 * text, return NULL, indicating an error */
525
526
	if (new_line_size < 0)
	    return NULL;
527
    } else {
528
529
530
#else
    {
#endif
531
532
533
	search_match_count = strlen(last_search);
	new_line_size = strlen(current->data) - strlen(last_search) +
	    strlen(last_replace) + 1;
534
    }
535

536
    /* Create buffer */
537
    copy = charalloc(new_line_size);
538
539
540

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
541
    copy[current_x] = '\0';
542
543
544

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
545
	strcat(copy, last_replace);
546
#ifdef HAVE_REGEX_H
547
    else
548
	(void) replace_regexp(copy + current_x, 1);
549
#endif
550
551

    /* The tail of the original line */
Chris Allegretta's avatar
Chris Allegretta committed
552
553
554
555
556
557

    /* 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 ;-) */
558
559
560
561
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
562
563
}

564
/* step through each replace word and prompt user before replacing word */
Chris Allegretta's avatar
Chris Allegretta committed
565
566
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
567
{
Chris Allegretta's avatar
Chris Allegretta committed
568
    int replaceall = 0, numreplaced = 0;
569
570

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
571
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
572

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

Chris Allegretta's avatar
Chris Allegretta committed
591
    if (ISSET(PICO_MODE) && answer[0] == '\0')
592
593
	answer = mallocstrcpy(answer, last_replace);

594
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
595
    while (1) {
596
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
597
598
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
599
600

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

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

Chris Allegretta's avatar
Chris Allegretta committed
608
	/* If we're here, we've found the search string */
609
610
611
612
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

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

615
616
617
618
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
619
620
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
621
622
		replaceall = 1;

623
624
	    copy = replace_line();
	    if (!copy) {
Jordi Mallach's avatar
   
Jordi Mallach committed
625
		statusbar(_("Replace failed: unknown subexpression!"));
626
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
627
		return 0;
628
	    }
Chris Allegretta's avatar
Chris Allegretta committed
629
630

	    /* Cleanup */
631
	    totsize -= strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
632
633
	    free(current->data);
	    current->data = copy;
634
	    totsize += strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
635

636
	    if (!ISSET(REVERSE_SEARCH)) {
Chris Allegretta's avatar
Chris Allegretta committed
637
638
		/* Stop bug where we replace a substring of the
		   replacement text */
639
		current_x += strlen(last_replace) - 1;
Chris Allegretta's avatar
Chris Allegretta committed
640

641
642
643
		/* Adjust the original cursor position - COULD BE IMPROVED */
		if (search_last_line) {
		    *beginx += strlen(last_replace) - strlen(last_search);
644

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

647
648
649
650
651
652
653
654
655
656
657
658
659
660
		    /* 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);
		}
661
662
	    }

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

Chris Allegretta's avatar
Chris Allegretta committed
671
672
673
674
675
676
677
678
    return numreplaced;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
681
682
683
684
685
686
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
    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;
    }

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

711
     /* If answer is now == "", then PICO_MODE is set.  So, copy
712
	last_search into answer (and prevanswer)... */
Chris Allegretta's avatar
Chris Allegretta committed
713
    if (answer[0] == '\0')
714
	answer = mallocstrcpy(answer, last_search);
Chris Allegretta's avatar
Chris Allegretta committed
715
    else
Chris Allegretta's avatar
Chris Allegretta committed
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
	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
733
734
735

    /* save where we are */
    begin = current;
736
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
737
738
739
740
741
    search_last_line = 0;

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

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

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

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

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
771
	    goto_abort();
772
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
773
774
775
	}
    }

776
    for (current = fileage; current->next != NULL && line > 1; line--)
777
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
778

779
    current_x = 0;
780
781
782
783
784
785
786

    /* 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
787

788
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
789
790
791
792
793
794
    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
795
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
796
}
797

798
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
799
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
800
801
802
803
804
{
    /* 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);
805

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
806
807
808
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
809
810

    /* set the rest of the coordinates up */
811
812
813
814
815
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
816
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
882
883
884
885
886

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
    char *pos, *brackets = "([{<>}])";
    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