search.c 25.1 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>
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
36
37
38
39
#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
#include <pwd.h>
#endif
#endif

40
41
42
static int past_editbuff;
	/* findnextstr() is now searching lines not displayed */

43
44
/* Regular expression helper functions */

45
#ifdef HAVE_REGEX_H
46
47
void regexp_init(const char *regexp)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
48
    regcomp(&search_regexp, regexp, (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE) | REG_EXTENDED);
49
50
51
    SET(REGEXP_COMPILED);
}

52
void regexp_cleanup(void)
53
54
55
56
{
    UNSET(REGEXP_COMPILED);
    regfree(&search_regexp);
}
57
#endif
58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
86
87
88
void search_init_globals(void)
{
    if (last_search == NULL) {
89
	last_search = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
90
	last_search[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
91
92
    }
    if (last_replace == NULL) {
93
	last_replace = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
94
	last_replace[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
95
96
97
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
98
/* Set up the system variables for a search or replace.  Returns -1 on
99
   abort, 0 on success, and 1 on rerun calling program
100
101
   Return -2 to run opposite program (search -> replace, replace ->
   search).
Chris Allegretta's avatar
Chris Allegretta committed
102

103
104
   replacing = 1 if we call from do_replace, 0 if called from do_search
   func. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
105
int search_init(int replacing)
Chris Allegretta's avatar
Chris Allegretta committed
106
{
107
    int i = 0;
108
    char *buf;
109
    static char *backupstring = NULL;
110

Chris Allegretta's avatar
Chris Allegretta committed
111
    search_init_globals();
112

Chris Allegretta's avatar
Chris Allegretta committed
113
    /* Clear the backupstring if we've changed from Pico mode to regular
114
       mode */
Chris Allegretta's avatar
Chris Allegretta committed
115
116
117
    if (ISSET(CLEAR_BACKUPSTRING)) {
	free(backupstring);
	backupstring = NULL;
118
	UNSET(CLEAR_BACKUPSTRING);
Chris Allegretta's avatar
Chris Allegretta committed
119
    }
120
121
122
123
124

    if (backupstring == NULL)
#ifndef NANO_SMALL
	backupstring = mallocstrcpy(backupstring, search_history.current->data);
#else
125
	backupstring = mallocstrcpy(backupstring, last_search);
126
#endif
127

128
/* NEW TEST */
129
    backupstring = mallocstrcpy(backupstring, "");
130
#ifndef NANO_SMALL
131
    search_history.current = (historytype *)&search_history.next;
132
#endif
133

Chris Allegretta's avatar
Chris Allegretta committed
134
    /* If using Pico messages, we do things the old fashioned way... */
135
    if (last_search[0] != '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
136
	buf = charalloc(COLS / 3 + 7);
Chris Allegretta's avatar
Chris Allegretta committed
137
	/* We use COLS / 3 here because we need to see more on the line */
Chris Allegretta's avatar
Chris Allegretta committed
138
139
	sprintf(buf, " [%.*s%s]", COLS / 3, last_search,
		strlen(last_search) > COLS / 3 ? "..." : "");
Chris Allegretta's avatar
Chris Allegretta committed
140
141
    } else {
	buf = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
142
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
143
    }
Chris Allegretta's avatar
Chris Allegretta committed
144

145
    /* This is now one simple call.  It just does a lot */
146
    i = statusq(0, replacing ? replace_list : whereis_list, backupstring,
147
148
149
150
#ifndef NANO_SMALL
	&search_history,
#endif
	"%s%s%s%s%s%s",
Chris Allegretta's avatar
Chris Allegretta committed
151
	_("Search"),
152
153
154
155
156
157
158
159
160
161
162
163
164

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

Chris Allegretta's avatar
Chris Allegretta committed
168
169
170
    /* Release buf now that we don't need it anymore */
    free(buf);

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

Chris Allegretta's avatar
Chris Allegretta committed
241
int is_whole_word(int curr_pos, const char *datastr, const char *searchword)
242
{
Chris Allegretta's avatar
Chris Allegretta committed
243
    size_t sln = curr_pos + strlen(searchword);
244

Chris Allegretta's avatar
Chris Allegretta committed
245
246
247
248
    /* 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]));
249
250
}

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

259
260
    past_editbuff = 0;

Chris Allegretta's avatar
Chris Allegretta committed
261
    if (!ISSET(REVERSE_SEARCH)) {		/* forward search */
Chris Allegretta's avatar
Chris Allegretta committed
262
263
264
265
266
267
	/* 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
268

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
282
	    update_line(fileptr, 0);
283
284
285
286

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

Chris Allegretta's avatar
Chris Allegretta committed
287
	    fileptr = fileptr->next;
288

289
	    if (fileptr == editbot)
290
		past_editbuff = 1;
Chris Allegretta's avatar
Chris Allegretta committed
291
292
293

	    /* EOF reached ?, wrap around once */
	    if (fileptr == NULL) {
294
295
		/* don't wrap if looking for bracket match */
		if (bracket_mode)
Chris Allegretta's avatar
Chris Allegretta committed
296
		    return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
297
		fileptr = fileage;
298
		past_editbuff = 1;
299
		if (!quiet) {
Chris Allegretta's avatar
Chris Allegretta committed
300
301
		    statusbar(_("Search Wrapped"));
		    SET(DISABLE_CURPOS);
302
		}
Chris Allegretta's avatar
Chris Allegretta committed
303
304
305
306
307
308
309
310
	    }

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

	    searchstr = fileptr->data;
	}
311

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

Chris Allegretta's avatar
Chris Allegretta committed
328
	/* Look for needle in searchstr */
329
	while ((found = strstrwrapper(searchstr, needle, rev_start, current_x_find)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
330
331
332
333
334
335
336
	    /* finished processing file, get out */
	    if (search_last_line) {
		if (!quiet)
		    not_found_msg(needle);
		return NULL;
	    }

Chris Allegretta's avatar
Chris Allegretta committed
337
	    update_line(fileptr, 0);
338
339
340
341

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

Chris Allegretta's avatar
Chris Allegretta committed
342
343
	    fileptr = fileptr->prev;

344
	    if (fileptr == edittop->prev)
345
		past_editbuff = 1;
346

347
	    /* SOF reached?, wrap around once */
Chris Allegretta's avatar
Chris Allegretta committed
348
/* ? */	    if (fileptr == NULL) {
349
350
		if (bracket_mode)
		   return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
351
		fileptr = filebot;
352
		past_editbuff = 1;
353
		if (!quiet) {
Chris Allegretta's avatar
Chris Allegretta committed
354
		    statusbar(_("Search Wrapped"));
355
356
		    SET(DISABLE_CURPOS);
		}
Chris Allegretta's avatar
Chris Allegretta committed
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
	    }
	    /* 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
374
    }
Chris Allegretta's avatar
Chris Allegretta committed
375
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
376

Chris Allegretta's avatar
Chris Allegretta committed
377
    /* Set globals now that we are sure we found something */
378
379
380
    current = fileptr;
    current_x = current_x_find;

381
382
383
384
385
    if (!bracket_mode) {
	if (past_editbuff)
	   edit_update(fileptr, CENTER);
	else
	   update_line(current, current_x);
386

387
388
389
	placewewant = xplustabs();
	reset_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
390
391
392
    return fileptr;
}

Chris Allegretta's avatar
Chris Allegretta committed
393
/* Search for a string. */
Chris Allegretta's avatar
Chris Allegretta committed
394
395
396
int do_search(void)
{
    int i;
397
398
    filestruct *fileptr = current, *didfind;
    int fileptr_x = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
399
400

    wrap_reset();
401
402
403
    i = search_init(0);
    switch (i) {
    case -1:
Chris Allegretta's avatar
Chris Allegretta committed
404
405
406
	current = fileptr;
	search_abort();
	return 0;
407
    case -3:
Chris Allegretta's avatar
Chris Allegretta committed
408
409
	search_abort();
	return 0;
410
    case -2:
Chris Allegretta's avatar
Chris Allegretta committed
411
412
	do_replace();
	return 0;
413
    case 1:
Chris Allegretta's avatar
Chris Allegretta committed
414
415
416
417
	do_search();
	search_abort();
	return 1;
    }
418

419
     /* If answer is now == "", copy last_search into answer... */
Chris Allegretta's avatar
Chris Allegretta committed
420
    if (answer[0] == '\0')
421
422
423
424
	answer = mallocstrcpy(answer, last_search);
    else
	last_search = mallocstrcpy(last_search, answer);

425
426
#ifndef NANO_SMALL
    /* add this search string to the search history list */
427
428
    if (strcmp(answer, ""))
	update_history(&search_history, answer);
429
430
#endif	/* !NANO_SMALL */

431
    search_last_line = 0;
432
433
    didfind = findnextstr(FALSE, FALSE, current, current_x, answer);

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

Chris Allegretta's avatar
Chris Allegretta committed
437
    search_abort();
438

Chris Allegretta's avatar
Chris Allegretta committed
439
440
441
442
443
    return 1;
}

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

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

    new_size -= search_match_count;

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

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

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

    return new_size;
}
511
#endif
512

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

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

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

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

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

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

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

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

565
566
567
568
569
570
571
572
void print_replaced(int num)
{
    if (num > 1)
	statusbar(_("Replaced %d occurrences"), num);
    else if (num == 1)
	statusbar(_("Replaced 1 occurrence"));
}

573
/* step through each replace word and prompt user before replacing word */
Chris Allegretta's avatar
Chris Allegretta committed
574
575
int do_replace_loop(const char *prevanswer, const filestruct *begin,
			int *beginx, int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
576
{
Chris Allegretta's avatar
Chris Allegretta committed
577
    int replaceall = 0, numreplaced = 0;
578
579

    filestruct *fileptr = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
580
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
581

Chris Allegretta's avatar
Chris Allegretta committed
582
    switch (*i) {
Chris Allegretta's avatar
Chris Allegretta committed
583
    case -1:		/* Aborted enter */
Chris Allegretta's avatar
Chris Allegretta committed
584
	if (last_replace[0] != '\0')
585
	    answer = mallocstrcpy(answer, last_replace);
586
587
588
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
589
590
591
    case 0:		/* They actually entered something */
	break;
    default:
Chris Allegretta's avatar
Chris Allegretta committed
592
593
        if (*i != -2) {	/* First page, last page, for example, could
			   get here */
Chris Allegretta's avatar
Chris Allegretta committed
594
595
596
	    do_early_abort();
	    replace_abort();
	    return 0;
597
        }
Chris Allegretta's avatar
Chris Allegretta committed
598
599
    }

600
    if (answer[0] == '\0')
601
602
	answer = mallocstrcpy(answer, last_replace);

603
    last_replace = mallocstrcpy(last_replace, answer);
Chris Allegretta's avatar
Chris Allegretta committed
604
    while (1) {
605
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
606
607
	fileptr = findnextstr(fileptr || replaceall || search_last_line,
				FALSE, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
608
609

	/* No more matches.  Done! */
610
	if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
611
612
	    break;

613
	/* Make sure only whole words are found */
Chris Allegretta's avatar
Chris Allegretta committed
614
	if (wholewords && !is_whole_word(current_x, fileptr->data, prevanswer))
615
	    continue;
Chris Allegretta's avatar
Chris Allegretta committed
616

Chris Allegretta's avatar
Chris Allegretta committed
617
	/* If we're here, we've found the search string */
618
619
620
621
	if (!replaceall) {
	    curs_set(0);
	    do_replace_highlight(TRUE, prevanswer);

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

624
625
626
627
	    do_replace_highlight(FALSE, prevanswer);
	    curs_set(1);
	}

Chris Allegretta's avatar
Chris Allegretta committed
628
629
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
630
631
		replaceall = 1;

632
	    copy = replace_line();
633
	    if (copy == NULL) {
Jordi Mallach's avatar
   
Jordi Mallach committed
634
		statusbar(_("Replace failed: unknown subexpression!"));
635
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
636
		return 0;
637
	    }
Chris Allegretta's avatar
Chris Allegretta committed
638
639

	    /* Cleanup */
640
	    totsize -= strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
641
642
	    free(current->data);
	    current->data = copy;
643
	    totsize += strlen(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
644

645
	    if (!ISSET(REVERSE_SEARCH)) {
Chris Allegretta's avatar
Chris Allegretta committed
646
647
		/* Stop bug where we replace a substring of the
		   replacement text */
648
		current_x += strlen(last_replace) - 1;
Chris Allegretta's avatar
Chris Allegretta committed
649

650
651
652
		/* Adjust the original cursor position - COULD BE IMPROVED */
		if (search_last_line) {
		    *beginx += strlen(last_replace) - strlen(last_search);
653

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

656
657
658
659
660
661
662
663
664
665
666
667
668
669
		    /* 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);
		}
670
671
	    }

Chris Allegretta's avatar
Chris Allegretta committed
672
673
674
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
675
676
	} else if (*i == -1)	/* Abort, else do nothing and continue
				   loop */
Chris Allegretta's avatar
Chris Allegretta committed
677
678
679
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
680
681
682
683
684
685
686
687
    return numreplaced;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
690
691
692
693
694
695
    if (ISSET(VIEW_MODE)) {
	print_view_warning();
	replace_abort();
	return 0;
    }

Chris Allegretta's avatar
Chris Allegretta committed
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
    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;
    }

713
#ifndef NANO_SMALL
714
715
    if (strcmp(answer, ""))
	update_history(&search_history, answer);
716
717
#endif	/* !NANO_SMALL */

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

Chris Allegretta's avatar
Chris Allegretta committed
725
726
    prevanswer = mallocstrcpy(prevanswer, last_search);

727
#ifndef NANO_SMALL
728
729
    replace_history.current = (historytype *)&replace_history.next;
    last_replace = mallocstrcpy(last_replace, "");
730
#endif
731
732

    i = statusq(0, replace_list_2, last_replace,
733
734
735
736
#ifndef NANO_SMALL
		&replace_history,
#endif
		_("Replace with"));
737

738
#ifndef NANO_SMALL
739
    if (i == 0 && strcmp(answer, ""))
740
741
	update_history(&replace_history, answer);
#endif	/* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
742
743

    begin = current;
744
    beginx = current_x;
Chris Allegretta's avatar
Chris Allegretta committed
745
746
747
748
749
    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
750
    current = begin;
751
    current_x = beginx;
Chris Allegretta's avatar
Chris Allegretta committed
752
    renumber_all();
753
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
754
755
756
757
758
759
760
761
762
763
764
    print_replaced(numreplaced);
    replace_abort();
    return 1;
}

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

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

	line = atoi(answer);

	/* Bounds check */
	if (line <= 0) {
	    statusbar(_("Come on, be reasonable"));
Chris Allegretta's avatar
Chris Allegretta committed
779
	    goto_abort();
780
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
781
782
783
	}
    }

784
    for (current = fileage; current->next != NULL && line > 1; line--)
785
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
786

787
    current_x = 0;
788

789
    /* if save_pos is nonzero, don't change the cursor position when
790
791
792
793
794
       updating the edit window */
    if (save_pos)
    	edit_update(current, NONE);
    else
	edit_update(current, CENTER);
795
    placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
796
    goto_abort();
797
    blank_statusbar_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
798
799
800
801
802
    return 1;
}

int do_gotoline_void(void)
{
803
    return do_gotoline(0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
804
}
805

806
#if defined(ENABLE_MULTIBUFFER) || !defined(DISABLE_SPELLER)
807
void do_gotopos(int line, int pos_x, int pos_y, int pos_placewewant)
808
809
810
811
812
{
    /* 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);
813

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
814
815
816
    /* make sure that the x-coordinate is sane here */
    if (pos_x > strlen(current->data))
	pos_x = strlen(current->data);
817
818

    /* set the rest of the coordinates up */
819
820
821
822
823
    current_x = pos_x;
    placewewant = pos_placewewant;
    update_line(current, pos_x);
}
#endif
824
825
826
827
828

#if !defined(NANO_SMALL) && defined(HAVE_REGEX_H)
int do_find_bracket(void)
{
    char ch_under_cursor, wanted_ch;
829
    const char *pos, *brackets = "([{<>}])";
830
831
832
833
834
    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];
835
836
837

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

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

    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;
868
	if (findnextstr(1, 1, current, current_x, regexp_pat) != NULL) {
869
	    have_past_editbuff |= past_editbuff;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
870
	    if (current->data[current_x] == ch_under_cursor)	/* found identical bracket */
871
872
873
874
875
876
877
878
879
		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();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
880
		    break;
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
		}
	    }
	} 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
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
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
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019

#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)
{
    for ( ; h->next ; h = h->next)
	if (strcmp(s, h->data) == 0)
	    return h;
    return NULL;
}

/* remove node *r */
void remove_node(historytype *r)
{
    r->prev->next = r->next;
    r->next->prev = r->prev;
    free(r->data);
    free(r);
}

/* add a node after node *h */
void insert_node(historytype *h, const char *s)
{
    historytype *a;

    a = nmalloc(sizeof(historytype));
    a->next = h->next;
    a->prev = h->next->prev;
    h->next->prev = a;
    h->next = a;
    a->data = mallocstrcpy(NULL, s);
}

/* update history list */
void update_history(historyheadtype *h, char *s)
{
    historytype *p;

    if ((p = find_node(h->next, s))) {
	if (p == h->next)		/* catch delete and re-insert of same string in 1st node */
	    goto up_hs;
	remove_node(p);				/* delete identical older string */
	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++;
up_hs:
    h->current = h->next;
}

/* return a pointer to either the next older history or NULL if no more */
char *get_history_older(historyheadtype *h)
{
    if (h->current->next) {		/* any older entries ? */
	h->current = h->current->next;	/* yes */
	return h->current->data;	/* return it */
    }
    return NULL;			/* end of list */
}

char *get_history_newer(historyheadtype *h)
{
    if (h->current->prev) {
	h->current = h->current->prev;
	if (h->current->prev)
	    return h->current->data;
    }
    return NULL;
}

/* get a completion */
char *get_history_completion(historyheadtype *h, char *s)
{
    historytype *p;

    for (p = h->current->next ; p->next ; p = p->next) {
	if ((strncmp(s, p->data, h->len) == 0) && (strlen(p->data) != h->len)) {
	    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;

    for (p = h->next ; (n = p->next) ; p = n)
	remove_node(p);
}

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