search.c 15.7 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**************************************************************************
 *   search.c                                                             *
 *                                                                        *
 *   Copyright (C) 2000 Chris Allegretta                                  *
 *   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 *
 *   the Free Software Foundation; either version 1, or (at your option)  *
 *   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.            *
 *                                                                        *
 **************************************************************************/

#include <stdlib.h>
#include <string.h>
24
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
25
#include <stdio.h>
Chris Allegretta's avatar
Chris Allegretta committed
26
#include <ctype.h>
Chris Allegretta's avatar
Chris Allegretta committed
27
28
29
#include "config.h"
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
30

Chris Allegretta's avatar
Chris Allegretta committed
31
32
33
34
35
36
37
#ifndef NANO_SMALL
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

38
39
/* Regular expression helper functions */

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

void regexp_cleanup()
{
    UNSET(REGEXP_COMPILED);
    regfree(&search_regexp);
}
52
#endif
53

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

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

   replacing = 1 if we call from do_replace, 0 if called from do_search func.
*/
int search_init(int replacing)
{
74
75
    int i = 0;
    char *buf;
76
    char *prompt, *reprompt = "";
77

Chris Allegretta's avatar
Chris Allegretta committed
78
    search_init_globals();
79

Chris Allegretta's avatar
Chris Allegretta committed
80
81
    buf = nmalloc(strlen(last_search) + 5);
    buf[0] = 0;
82

Chris Allegretta's avatar
Chris Allegretta committed
83
84
    /* If using Pico messages, we do things the old fashioned way... */
    if (ISSET(PICO_MSGS)) {
85
86
87
88
89
90
91
92
93
94
95
	if (last_search[0]) {

	    /* We use COLS / 3 here because we need to see more on the line */
	    if (strlen(last_search) > COLS / 3) {
		snprintf(buf, COLS / 3 + 3, " [%s", last_search);
		sprintf(&buf[COLS / 3 + 2], "...]");
	    } else
		sprintf(buf, " [%s]", last_search);
	} else {
	    buf[0] = '\0';
	}
Chris Allegretta's avatar
Chris Allegretta committed
96
97
    }

98
    if (ISSET(USE_REGEXP) && ISSET(CASE_SENSITIVE))
99
	prompt = _("Case Sensitive Regexp Search%s%s");
100
    else if (ISSET(USE_REGEXP))
101
102
103
	prompt = _("Regexp Search%s%s");
    else if (ISSET(CASE_SENSITIVE))
	prompt = _("Case Sensitive Search%s%s");
104
    else
105
	prompt = _("Search%s%s");
106
107
108

    if (replacing)
	reprompt = _(" (to replace)");
109

110
    if (ISSET(PICO_MSGS))
111
	i = statusq(0, replacing ? replace_list : whereis_list,
112
113
114
		replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, "",
		prompt, reprompt, buf);
    else
115
	i = statusq(0, replacing ? replace_list : whereis_list,
116
117
		replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, last_search,
		prompt, reprompt, "");
Chris Allegretta's avatar
Chris Allegretta committed
118
119
120
121
122
123
124

    /* Cancel any search, or just return with no previous search */
    if ((i == -1) || (i < 0 && !last_search[0])) {
	statusbar(_("Search Cancelled"));
	reset_cursor();
	return -1;
    } else if (i == -2) {	/* Same string */
125
	answer = mallocstrcpy(answer, last_search);
126
#ifdef HAVE_REGEX_H
127
128
	if (ISSET(USE_REGEXP))
	    regexp_init(answer);
129
#endif
Chris Allegretta's avatar
Chris Allegretta committed
130
    } else if (i == 0) {	/* They entered something new */
131
	last_search = mallocstrcpy(last_search, answer);
132
#ifdef HAVE_REGEX_H
133
134
	if (ISSET(USE_REGEXP))
	    regexp_init(answer);
135
#endif
Chris Allegretta's avatar
Chris Allegretta committed
136
137
138
139
140
141
142
143
144
145
146
147
	/* Blow away last_replace because they entered a new search
	   string....uh, right? =) */
	last_replace[0] = '\0';
    } else if (i == NANO_CASE_KEY) {	/* They want it case sensitive */
	if (ISSET(CASE_SENSITIVE))
	    UNSET(CASE_SENSITIVE);
	else
	    SET(CASE_SENSITIVE);

	return 1;
    } else if (i == NANO_OTHERSEARCH_KEY) {
	return -2;		/* Call the opposite search function */
148
149
150
    } else if (i == NANO_FROMSEARCHTOGOTO_KEY) {
	do_gotoline_void();
	return -3;
151
    } else {			/* First line key, etc. */
Chris Allegretta's avatar
Chris Allegretta committed
152
153
154
155
156
157
158
	do_early_abort();
	return -3;
    }

    return 0;
}

159
160
void not_found_msg(char *str)
{
161
    if (strlen(str) <= COLS / 2)
162
163
	statusbar(_("\"%s\" not found"), str);
    else {
164
165
166
	char *foo = NULL;

	foo = mallocstrcpy(foo, str);
167
168
	foo[COLS / 2] = 0;
	statusbar(_("\"%s...\" not found"), foo);
169
170

	free(foo);
171
172
173
    }
}

174
175
filestruct *findnextstr(int quiet, filestruct * begin, int beginx,
			char *needle)
Chris Allegretta's avatar
Chris Allegretta committed
176
177
178
{
    filestruct *fileptr;
    char *searchstr, *found = NULL, *tmp;
179
    int past_editbot = 0;
Chris Allegretta's avatar
Chris Allegretta committed
180

181
    fileptr = current;
Chris Allegretta's avatar
Chris Allegretta committed
182

183
    current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
184

185
    /* Are we searching the last line? (i.e. the line where search started) */
186
    if ((fileptr == begin) && (current_x < beginx))
187
	search_last_line = 1;
Chris Allegretta's avatar
Chris Allegretta committed
188

189
    /* Make sure we haven't passed the end of the string */
190
    if (strlen(fileptr->data) < current_x)
191
	current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
192

193
    searchstr = &fileptr->data[current_x];
Chris Allegretta's avatar
Chris Allegretta committed
194

195
    /* Look for needle in searchstr */
196
    while ((found = strstrwrapper(searchstr, needle)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
197

198
	/* finished processing file, get out */
199
	if (search_last_line) {
Chris Allegretta's avatar
Chris Allegretta committed
200
	    if (!quiet)
201
		not_found_msg(needle);
Chris Allegretta's avatar
Chris Allegretta committed
202
203
204
	    return NULL;
	}

205
206
207
208
209
210
211
212
213
214
	fileptr = fileptr->next;

	if (!past_editbot && (fileptr == editbot))
	    past_editbot = 1;

	/* EOF reached, wrap around once */
	if (fileptr == NULL) {
	    fileptr = fileage;

	    past_editbot = 1;
Chris Allegretta's avatar
Chris Allegretta committed
215
216
217

	    if (!quiet)
		statusbar(_("Search Wrapped"));
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
	}

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

	searchstr = fileptr->data;
    }

    /* We found an instance */
    current = fileptr;
    current_x = 0;
    for (tmp = fileptr->data; tmp != found; tmp++)
	current_x++;

    /* Ensure we haven't wrap around again! */
    if ((search_last_line) && (current_x >= beginx)) {
	if (!quiet)
236
	    not_found_msg(needle);
237
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
238
239
    }

240
241
242
243
244
245
246
247
    if (past_editbot)
	edit_update(fileptr, CENTER);
    else
	update_line(current, current_x);

    placewewant = xplustabs();
    reset_cursor();

Chris Allegretta's avatar
Chris Allegretta committed
248
249
250
251
252
253
254
255
    return fileptr;
}

void search_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    wrefresh(bottomwin);
256
    if (ISSET(MARK_ISSET))
257
	edit_refresh_clearok();
258

259
#ifdef HAVE_REGEX_H
260
    if (ISSET(REGEXP_COMPILED))
261
	regexp_cleanup();
262
#endif
Chris Allegretta's avatar
Chris Allegretta committed
263
264
265
266
267
268
269
270
271
}

/* Search for a string */
int do_search(void)
{
    int i;
    filestruct *fileptr = current;

    wrap_reset();
272
273
274
    i = search_init(0);
    switch (i) {
    case -1:
Chris Allegretta's avatar
Chris Allegretta committed
275
276
277
	current = fileptr;
	search_abort();
	return 0;
278
    case -3:
Chris Allegretta's avatar
Chris Allegretta committed
279
280
	search_abort();
	return 0;
281
    case -2:
Chris Allegretta's avatar
Chris Allegretta committed
282
283
	do_replace();
	return 0;
284
    case 1:
Chris Allegretta's avatar
Chris Allegretta committed
285
286
287
288
	do_search();
	search_abort();
	return 1;
    }
289
290
291

    /* The sneaky user deleted the previous search string */
    if (!strcmp(answer, "")) {
Jordi Mallach's avatar
   
Jordi Mallach committed
292
	statusbar(_("Search Cancelled"));
293
294
295
296
	search_abort();
	return 0;
    }

297
298
    search_last_line = 0;
    findnextstr(0, current, current_x, answer);
Chris Allegretta's avatar
Chris Allegretta committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    search_abort();
    return 1;
}

void print_replaced(int num)
{
    if (num > 1)
	statusbar(_("Replaced %d occurences"), num);
    else if (num == 1)
	statusbar(_("Replaced 1 occurence"));
}

void replace_abort(void)
{
313
314
315
316
    /* Identicle to search_abort, so we'll call it here.  If it
       does something different later, we can change it back.  For now
       it's just a waste to duplicat code */
    search_abort();
317
    placewewant = xplustabs();
318
319
}

320
#ifdef HAVE_REGEX_H
321
322
323
324
325
326
327
328
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;
329
    int search_match_count = regmatches[0].rm_eo - regmatches[0].rm_so;
330
331
332
333
334
335
336
337

    new_size -= search_match_count;

    /* Iterate through the replacement text to handle
     * subexpression replacement using \1, \2, \3, etc */

    c = last_replace;
    while (*c) {
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
	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
		       exist.  */
		    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++;
	    }
	}
373
374
375
    }

    if (create_flag)
376
	*string = 0;
377
378
379

    return new_size;
}
380
#endif
381

Chris Allegretta's avatar
Chris Allegretta committed
382
char *replace_line(void)
383
384
385
386
387
388
{
    char *copy, *tmp;
    int new_line_size;
    int search_match_count;

    /* Calculate size of new line */
389
#ifdef HAVE_REGEX_H
390
    if (ISSET(USE_REGEXP)) {
391
392
393
394
395
396
	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
	 * text, return NULL indicating an error */
	if (new_line_size < 0)
	    return NULL;
397
    } else {
398
399
400
#else
    {
#endif
401
402
403
	search_match_count = strlen(last_search);
	new_line_size = strlen(current->data) - strlen(last_search) +
	    strlen(last_replace) + 1;
404
    }
405

406
407
408
409
410
411
412
413
414
    /* Create buffer */
    copy = nmalloc(new_line_size);

    /* Head of Original Line */
    strncpy(copy, current->data, current_x);
    copy[current_x] = 0;

    /* Replacement Text */
    if (!ISSET(USE_REGEXP))
415
	strcat(copy, last_replace);
416
#ifdef HAVE_REGEX_H
417
    else
418
	(void) replace_regexp(copy + current_x, 1);
419
#endif
420
421
422
423
424
425
426
427
428
429
430
431

    /* The tail of the original line */
    /* This may expose other bugs, because it no longer
       goes through each character on 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 ;-) */
    tmp = current->data + current_x + search_match_count;
    strcat(copy, tmp);

    return copy;
Chris Allegretta's avatar
Chris Allegretta committed
432
433
}

Chris Allegretta's avatar
Chris Allegretta committed
434
435
int do_replace_loop(char *prevanswer, filestruct *begin, int *beginx,
			int wholewords, int *i)
Chris Allegretta's avatar
Chris Allegretta committed
436
{
Chris Allegretta's avatar
Chris Allegretta committed
437
438
439
    int replaceall = 0, numreplaced = 0;
    filestruct *fileptr;
    char *copy;
Chris Allegretta's avatar
Chris Allegretta committed
440

Chris Allegretta's avatar
Chris Allegretta committed
441
    switch (*i) {
442
    case -1:				/* Aborted enter */
443
	if (strcmp(last_replace, ""))
444
	    answer = mallocstrcpy(answer, last_replace);
445
446
447
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
448
    case 0:		/* They actually entered something */
449
	last_replace = mallocstrcpy(last_replace, answer);
450
451
	break;
    default:
Chris Allegretta's avatar
Chris Allegretta committed
452
        if (*i != -2) {	/* First page, last page, for example 
453
				   could get here */
Chris Allegretta's avatar
Chris Allegretta committed
454
455
456
	    do_early_abort();
	    replace_abort();
	    return 0;
457
        }
Chris Allegretta's avatar
Chris Allegretta committed
458
459
460
461
    }

    while (1) {

462
	/* Sweet optimization by Rocco here */
Chris Allegretta's avatar
Chris Allegretta committed
463
	fileptr = findnextstr(replaceall, begin, *beginx, prevanswer);
Chris Allegretta's avatar
Chris Allegretta committed
464
465

	/* No more matches.  Done! */
466
	if (!fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
467
468
	    break;

Chris Allegretta's avatar
Chris Allegretta committed
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
	/* Make sure only wholewords are found */
	if (wholewords)
	{
	    /* start of line or previous character not a letter */
	    if ((current_x == 0) || (!isalpha(fileptr->data[current_x-1])))
	    {
		/* end of line or next character not a letter */
		if (((current_x + strlen(prevanswer)) == strlen(fileptr->data))
			|| (!isalpha(fileptr->data[current_x + strlen(prevanswer)])))
		    ;
		else
		    continue;
	    }
	    else
		continue;
	}

Chris Allegretta's avatar
Chris Allegretta committed
486
487
	/* If we're here, we've found the search string */
	if (!replaceall)
Chris Allegretta's avatar
Chris Allegretta committed
488
	    *i = do_yesno(1, 1, _("Replace this instance?"));
Chris Allegretta's avatar
Chris Allegretta committed
489

Chris Allegretta's avatar
Chris Allegretta committed
490
491
	if (*i > 0 || replaceall) {	/* Yes, replace it!!!! */
	    if (*i == 2)
Chris Allegretta's avatar
Chris Allegretta committed
492
493
		replaceall = 1;

494
495
	    copy = replace_line();
	    if (!copy) {
Jordi Mallach's avatar
   
Jordi Mallach committed
496
		statusbar(_("Replace failed: unknown subexpression!"));
497
		replace_abort();
Chris Allegretta's avatar
Chris Allegretta committed
498
		return 0;
499
	    }
Chris Allegretta's avatar
Chris Allegretta committed
500
501
502
503
504
505

	    /* Cleanup */
	    free(current->data);
	    current->data = copy;

	    /* Stop bug where we replace a substring of the replacement text */
506
	    current_x += strlen(last_replace) - 1;
Chris Allegretta's avatar
Chris Allegretta committed
507

508
509
	    /* Adjust the original cursor position - COULD BE IMPROVED */
	    if (search_last_line) {
Chris Allegretta's avatar
Chris Allegretta committed
510
		*beginx += strlen(last_replace) - strlen(last_search);
511
512
513

		/* For strings that cross the search start/end boundary */
		/* Don't go outside of allocated memory */
Chris Allegretta's avatar
Chris Allegretta committed
514
515
		if (*beginx < 1)
		    *beginx = 1;
516
517
	    }

Chris Allegretta's avatar
Chris Allegretta committed
518
519
520
	    edit_refresh();
	    set_modified();
	    numreplaced++;
Chris Allegretta's avatar
Chris Allegretta committed
521
	} else if (*i == -1)	/* Abort, else do nothing and continue loop */
Chris Allegretta's avatar
Chris Allegretta committed
522
523
524
	    break;
    }

Chris Allegretta's avatar
Chris Allegretta committed
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
    return numreplaced;
}

/* Replace a string */
int do_replace(void)
{
    int i, numreplaced, beginx;
    filestruct *begin;
    char *prevanswer = NULL, *buf = NULL;

    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;
    }

    /* Again, there was a previous string but they deleted it and hit enter */
    if (!strcmp(answer, "")) {
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
    }

    prevanswer = mallocstrcpy(prevanswer, answer);

    if (ISSET(PICO_MSGS)) {
	buf = nmalloc(strlen(last_replace) + 5);
	if (strcmp(last_replace, "")) {
	    if (strlen(last_replace) > (COLS / 3)) {
		strncpy(buf, last_replace, COLS / 3);
		sprintf(&buf[COLS / 3 - 1], "...");
	    } else
		sprintf(buf, "%s", last_replace);

570
	    i = statusq(0, replace_list_2, REPLACE_LIST_2_LEN, "",
Chris Allegretta's avatar
Chris Allegretta committed
571
572
573
			_("Replace with [%s]"), buf);
	}
	else
574
	    i = statusq(0, replace_list_2, REPLACE_LIST_2_LEN, "",
Chris Allegretta's avatar
Chris Allegretta committed
575
576
577
			_("Replace with"));
    }
    else
578
	i = statusq(0, replace_list_2, REPLACE_LIST_2_LEN, last_replace, 
Chris Allegretta's avatar
Chris Allegretta committed
579
580
581
582
583
584
585
586
587
588
589
			_("Replace with"));

    /* save where we are */
    begin = current;
    beginx = current_x + 1;

    search_last_line = 0;

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

    /* restore where we were */
Chris Allegretta's avatar
Chris Allegretta committed
590
    current = begin;
591
    current_x = beginx - 1;
Chris Allegretta's avatar
Chris Allegretta committed
592
    renumber_all();
593
    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
    print_replaced(numreplaced);
    replace_abort();
    return 1;
}

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

int do_gotoline(long defline)
{
    long line, i = 1, j = 0;
    filestruct *fileptr;

    if (defline > 0)		/* We already know what line we want to go to */
	line = defline;
    else {			/* Ask for it */

614
	j = statusq(0, goto_list, GOTO_LIST_LEN, "", _("Enter line number"));
Chris Allegretta's avatar
Chris Allegretta committed
615
616
617
618
619
620
621
622
623
624
625
626
	if (j == -1) {
	    statusbar(_("Aborted"));
	    goto_abort();
	    return 0;
	} else if (j != 0) {
	    do_early_abort();
	    goto_abort();
	    return 0;
	}
	if (!strcmp(answer, "$")) {
	    current = filebot;
	    current_x = 0;
627
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
	    goto_abort();
	    return 1;
	}
	line = atoi(answer);
    }

    /* Bounds check */
    if (line <= 0) {
	statusbar(_("Come on, be reasonable"));
	goto_abort();
	return 0;
    }
    if (line > totlines) {
	statusbar(_("Only %d lines available, skipping to last line"),
		  filebot->lineno);
	current = filebot;
	current_x = 0;
645
	edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
646
647
648
649
650
651
    } else {
	for (fileptr = fileage; fileptr != NULL && i < line; i++)
	    fileptr = fileptr->next;

	current = fileptr;
	current_x = 0;
652
	edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
653
654
655
656
657
658
659
660
661
662
    }

    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
    return do_gotoline(0);
}