nano.c 126 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   nano.c                                                               *
 *                                                                        *
5
 *   Copyright (C) 1999-2005 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
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   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.                             *
Chris Allegretta's avatar
Chris Allegretta committed
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

23
24
25
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
26

Chris Allegretta's avatar
Chris Allegretta committed
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/param.h>
Chris Allegretta's avatar
Chris Allegretta committed
36
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
38
39
#include <errno.h>
#include <ctype.h>
#include <locale.h>
40
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
41
42
43
44
45
46
47
48
49
50
#include "proto.h"

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

51
52
53
54
#ifndef NANO_SMALL
#include <setjmp.h>
#endif

55
#ifndef DISABLE_WRAPJUSTIFY
56
static ssize_t fill = 0;	/* Fill - where to wrap lines */
57
#endif
58
#ifndef DISABLE_WRAPPING
59
static bool same_line_wrap = FALSE;	/* Whether wrapped text should
60
61
					   be prepended to the next
					   line */
62
#endif
63

Chris Allegretta's avatar
Chris Allegretta committed
64
static struct termios oldterm;	/* The user's original term settings */
65
static struct sigaction act;	/* For all our fun signal handlers */
Chris Allegretta's avatar
Chris Allegretta committed
66

67
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
68
69
static sigjmp_buf jmpbuf;	/* Used to return to main() after a
				   SIGWINCH. */
70
71
72
73
static int pid;			/* The PID of the newly forked process
				 * in open_pipe().  It must be global
				 * because the signal handler needs
				 * it. */
74
#endif
75

76
77
78
79
80
#ifndef DISABLE_JUSTIFY
static filestruct *jusbottom = NULL;
	/* Pointer to end of justify buffer. */
#endif

81
82
83
84
85
void print_view_warning(void)
{
    statusbar(_("Key illegal in VIEW mode"));
}

86
87
/* What we do when we're all set to exit. */
void finish(void)
Chris Allegretta's avatar
Chris Allegretta committed
88
{
89
90
91
92
    if (!ISSET(NO_HELP))
	blank_bottombars();
    else
	blank_statusbar();
93

Chris Allegretta's avatar
Chris Allegretta committed
94
95
96
    wrefresh(bottomwin);
    endwin();

97
    /* Restore the old terminal settings. */
Chris Allegretta's avatar
Chris Allegretta committed
98
    tcsetattr(0, TCSANOW, &oldterm);
Chris Allegretta's avatar
Chris Allegretta committed
99

100
101
102
103
104
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	save_history();
#endif

105
#ifdef DEBUG
106
    thanks_for_all_the_fish();
107
#endif
108

109
    exit(0);
Chris Allegretta's avatar
Chris Allegretta committed
110
111
}

112
/* Die (gracefully?). */
Chris Allegretta's avatar
Chris Allegretta committed
113
void die(const char *msg, ...)
Chris Allegretta's avatar
Chris Allegretta committed
114
115
116
{
    va_list ap;

117
118
119
    endwin();
    curses_ended = TRUE;

120
    /* Restore the old terminal settings. */
121
122
    tcsetattr(0, TCSANOW, &oldterm);

Chris Allegretta's avatar
Chris Allegretta committed
123
124
125
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
126

127
    /* Save the current file buffer if it's been modified. */
128
129
130
131
132
    if (ISSET(MODIFIED)) {
	/* If we've partitioned the filestruct, unpartition it now. */
	if (filepart != NULL)
	    unpartition_filestruct(&filepart);

133
	die_save_file(filename);
134
    }
135

136
#ifdef ENABLE_MULTIBUFFER
137
    /* Save all of the other modified file buffers, if any. */
138
    if (open_files != NULL) {
139
	openfilestruct *tmp = open_files;
140

141
142
	while (tmp != open_files->next) {
	    open_files = open_files->next;
143

144
145
146
147
	    /* Save the current file buffer if it's been modified. */
	    if (open_files->flags & MODIFIED) {
		/* Set fileage and filebot to match the current file
		 * buffer, and then write it to disk. */
148
		fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
149
		filebot = open_files->filebot;
150
		die_save_file(open_files->filename);
151
152
153
154
155
	    }
	}
    }
#endif

156
157
    /* Get out. */
    exit(1);
158
159
}

Chris Allegretta's avatar
Chris Allegretta committed
160
void die_save_file(const char *die_filename)
161
{
162
    char *retval;
163
    bool failed = TRUE;
164

165
166
167
    /* If we're using restricted mode, don't write any emergency backup
     * files, since that would allow reading from or writing to files
     * not specified on the command line. */
168
169
170
    if (ISSET(RESTRICTED))
	return;

Chris Allegretta's avatar
Chris Allegretta committed
171
    /* If we can't save, we have REAL bad problems, but we might as well
172
     * TRY. */
Chris Allegretta's avatar
Chris Allegretta committed
173
    if (die_filename[0] == '\0')
174
175
	die_filename = "nano";

176
    retval = get_next_filename(die_filename, ".save");
177
    if (retval[0] != '\0')
178
	failed = (write_file(retval, NULL, TRUE, FALSE, TRUE) == -1);
Chris Allegretta's avatar
Chris Allegretta committed
179

180
    if (!failed)
181
	fprintf(stderr, _("\nBuffer written to %s\n"), retval);
182
    else if (retval[0] != '\0')
183
	fprintf(stderr, _("\nBuffer not written to %s: %s\n"), retval,
184
185
186
		strerror(errno));
    else
	fprintf(stderr, _("\nBuffer not written: %s\n"),
187
		_("Too many backup files?"));
188

189
    free(retval);
Chris Allegretta's avatar
Chris Allegretta committed
190
191
}

192
/* Die with an error message that the screen was too small if, well, the
Chris Allegretta's avatar
Chris Allegretta committed
193
 * screen is too small. */
194
void check_die_too_small(void)
195
{
196
197
198
    editwinrows = LINES - 5 + no_more_space() + no_help();
    if (editwinrows < MIN_EDITOR_ROWS)
	die(_("Window size is too small for nano...\n"));
199
200
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
201
202
/* Reinitialize the variables that depend on the window size.  That is,
 * fill and hblank. */
203
void resize_variables(void)
Chris Allegretta's avatar
Chris Allegretta committed
204
{
205
206
207
208
209
210
211
212
213
#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
    if (fill < 0)
	fill = 0;
#endif

    hblank = charealloc(hblank, COLS + 1);
214
    charset(hblank, ' ', COLS);
215
    hblank[COLS] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
216
217
}

218
/* Initialize global variables -- no better way for now.  If
219
220
 * save_cutbuffer is TRUE, don't set cutbuffer to NULL. */
void global_init(bool save_cutbuffer)
Chris Allegretta's avatar
Chris Allegretta committed
221
{
222
223
    check_die_too_small();
    resize_variables();
224

Chris Allegretta's avatar
Chris Allegretta committed
225
    fileage = NULL;
226
227
    edittop = NULL;
    current = NULL;
228
229
    if (!save_cutbuffer)
	cutbuffer = NULL;
230
231
232
    current_x = 0;
    placewewant = 0;
    current_y = 0;
Chris Allegretta's avatar
Chris Allegretta committed
233
    totlines = 0;
234
    totsize = 0;
Chris Allegretta's avatar
Chris Allegretta committed
235
236
}

237
238
void window_init(void)
{
239
    check_die_too_small();
240

241
242
    if (topwin != NULL)
	delwin(topwin);
243
244
    if (edit != NULL)
	delwin(edit);
245
246
247
    if (bottomwin != NULL)
	delwin(bottomwin);

248
    /* Set up the windows. */
249
250
    topwin = newwin(2 - no_more_space(), COLS, 0, 0);
    edit = newwin(editwinrows, COLS, 2 - no_more_space(), 0);
251
252
    bottomwin = newwin(3 - no_help(), COLS, editwinrows +
	(2 - no_more_space()), 0);
253

254
    /* Turn the keypad back on. */
255
256
257
258
    keypad(edit, TRUE);
    keypad(bottomwin, TRUE);
}

259
#ifndef DISABLE_MOUSE
260
261
262
263
264
265
266
267
void mouse_init(void)
{
    if (ISSET(USE_MOUSE)) {
	mousemask(BUTTON1_RELEASED, NULL);
	mouseinterval(50);
    } else
	mousemask(0, NULL);
}
268
#endif
269
270
271
272
273
274

#ifndef DISABLE_HELP
/* This function allocates help_text, and stores the help string in it. 
 * help_text should be NULL initially. */
void help_init(void)
{
275
    size_t allocsize = 0;	/* Space needed for help_text. */
276
277
278
279
    const char *htx[3];		/* Untranslated help message.  We break
				 * it up into three chunks in case the
				 * full string is too long for the
				 * compiler to handle. */
280
    char *ptr;
281
    const shortcut *s;
282
283
#ifndef NANO_SMALL
    const toggle *t;
284
285
286
287
288
#ifdef ENABLE_NANORC
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
#endif
289
290
#endif

291
    /* First, set up the initial help text for the current function. */
292
    if (currshortcut == whereis_list || currshortcut == replace_list
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
	     || currshortcut == replace_list_2) {
	htx[0] = N_("Search Command Help Text\n\n "
		"Enter the words or characters you would like to "
		"search for, and then press Enter.  If there is a "
		"match for the text you entered, the screen will be "
		"updated to the location of the nearest match for the "
		"search string.\n\n The previous search string will be "
		"shown in brackets after the search prompt.  Hitting "
		"Enter without entering any text will perform the "
		"previous search.  ");
	htx[1] = N_("If you have selected text with the mark and then "
		"search to replace, only matches in the selected text "
		"will be replaced.\n\n The following function keys are "
		"available in Search mode:\n\n");
	htx[2] = NULL;
    } else if (currshortcut == gotoline_list) {
	htx[0] = N_("Go To Line Help Text\n\n "
310
311
		"Enter the line number that you wish to go to and hit "
		"Enter.  If there are fewer lines of text than the "
312
313
		"number you entered, you will be brought to the last "
		"line of the file.\n\n The following function keys are "
314
		"available in Go To Line mode:\n\n");
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
	htx[1] = NULL;
	htx[2] = NULL;
    } else if (currshortcut == insertfile_list) {
	htx[0] = N_("Insert File Help Text\n\n "
		"Type in the name of a file to be inserted into the "
		"current file buffer at the current cursor "
		"location.\n\n If you have compiled nano with multiple "
		"file buffer support, and enable multiple file buffers "
		"with the -F or --multibuffer command line flags, the "
		"Meta-F toggle, or a nanorc file, inserting a file "
		"will cause it to be loaded into a separate buffer "
		"(use Meta-< and > to switch between file buffers).  ");
	htx[1] = N_("If you need another blank buffer, do not enter "
		"any filename, or type in a nonexistent filename at "
		"the prompt and press Enter.\n\n The following "
330
		"function keys are available in Insert File mode:\n\n");
331
332
333
	htx[2] = NULL;
    } else if (currshortcut == writefile_list) {
	htx[0] = N_("Write File Help Text\n\n "
334
		"Type the name that you wish to save the current file "
335
		"as and press Enter to save the file.\n\n If you have "
336
		"selected text with the mark, you will be prompted to "
337
338
339
340
341
		"save only the selected portion to a separate file.  To "
		"reduce the chance of overwriting the current file with "
		"just a portion of it, the current filename is not the "
		"default in this mode.\n\n The following function keys "
		"are available in Write File mode:\n\n");
342
343
344
	htx[1] = NULL;
	htx[2] = NULL;
    }
345
#ifndef DISABLE_BROWSER
346
347
    else if (currshortcut == browser_list) {
	htx[0] = N_("File Browser Help Text\n\n "
348
349
350
351
352
		"The file browser is used to visually browse the "
		"directory structure to select a file for reading "
		"or writing.  You may use the arrow keys or Page Up/"
		"Down to browse through the files, and S or Enter to "
		"choose the selected file or enter the selected "
353
354
355
356
357
358
359
360
		"directory.  To move up one level, select the "
		"directory called \"..\" at the top of the file "
		"list.\n\n The following function keys are available "
		"in the file browser:\n\n");
	htx[1] = NULL;
	htx[2] = NULL;
    } else if (currshortcut == gotodir_list) {
	htx[0] = N_("Browser Go To Directory Help Text\n\n "
361
		"Enter the name of the directory you would like to "
362
363
364
365
366
367
368
369
		"browse to.\n\n If tab completion has not been "
		"disabled, you can use the Tab key to (attempt to) "
		"automatically complete the directory name.\n\n The "
		"following function keys are available in Browser Go "
		"To Directory mode:\n\n");
	htx[1] = NULL;
	htx[2] = NULL;
    }
370
#endif
371
#ifndef DISABLE_SPELLER
372
373
374
375
    else if (currshortcut == spell_list) {
	htx[0] = N_("Spell Check Help Text\n\n "
		"The spell checker checks the spelling of all text in "
		"the current file.  When an unknown word is "
376
377
		"encountered, it is highlighted and a replacement can "
		"be edited.  It will then prompt to replace every "
378
379
380
381
382
383
384
		"instance of the given misspelled word in the current "
		"file, or, if you have selected text with the mark, in "
		"the selected text.\n\n The following other functions "
		"are available in Spell Check mode:\n\n");
	htx[1] = NULL;
	htx[2] = NULL;
    }
385
#endif
386
#ifndef NANO_SMALL
387
388
389
390
391
392
393
394
395
396
397
    else if (currshortcut == extcmd_list) {
	htx[0] = N_("Execute Command Help Text\n\n "
		"This menu allows you to insert the output of a "
		"command run by the shell into the current buffer (or "
		"a new buffer in multiple file buffer mode). If you "
		"need another blank buffer, do not enter any "
		"command.\n\n The following keys are available in "
		"Execute Command mode:\n\n");
	htx[1] = NULL;
	htx[2] = NULL;
    }
398
#endif
399
    else {
400
	/* Default to the main help list. */
401
402
403
404
405
406
407
408
409
410
	htx[0] = N_(" nano help text\n\n "
		"The nano editor is designed to emulate the "
		"functionality and ease-of-use of the UW Pico text "
		"editor.  There are four main sections of the editor.  "
		"The top line shows the program version, the current "
		"filename being edited, and whether or not the file "
		"has been modified.  Next is the main editor window "
		"showing the file being edited.  The status line is "
		"the third line from the bottom and shows important "
		"messages.  The bottom two lines show the most "
411
		"commonly used shortcuts in the editor.\n\n ");
412
413
414
415
416
417
418
419
420
	htx[1] = N_("The notation for shortcuts is as follows: "
		"Control-key sequences are notated with a caret (^) "
		"symbol and can be entered either by using the Control "
		"(Ctrl) key or pressing the Escape (Esc) key twice.  "
		"Escape-key sequences are notated with the Meta (M) "
		"symbol and can be entered using either the Esc, Alt, "
		"or Meta key depending on your keyboard setup.  ");
	htx[2] = N_("Also, pressing Esc twice and then typing a "
		"three-digit decimal number from 000 to 255 will enter "
421
422
423
424
		"the character with the corresponding value.  The "
		"following keystrokes are available in the main editor "
		"window.  Alternative keys are shown in "
		"parentheses:\n\n");
425
426
427
428
429
430
431
432
433
434
435
436
437
    }

    htx[0] = _(htx[0]);
    if (htx[1] != NULL)
	htx[1] = _(htx[1]);
    if (htx[2] != NULL)
	htx[2] = _(htx[2]);

    allocsize += strlen(htx[0]);
    if (htx[1] != NULL)
	allocsize += strlen(htx[1]);
    if (htx[2] != NULL)
	allocsize += strlen(htx[2]);
438
439
440

    /* The space needed for the shortcut lists, at most COLS characters,
     * plus '\n'. */
441
442
    allocsize += (COLS < 24 ? (24 * mb_cur_max()) :
	((COLS + 1) * mb_cur_max())) * length_of_list(currshortcut);
443
444

#ifndef NANO_SMALL
445
    /* If we're on the main list, we also count the toggle help text.
446
447
     * Each line has "M-%c\t\t\t", which fills 24 columns, plus a space,
     * plus translated text, plus '\n'. */
448
    if (currshortcut == main_list) {
449
	size_t endis_len = strlen(_("enable/disable"));
450

451
	for (t = toggles; t != NULL; t = t->next)
452
	    allocsize += 8 + strlen(t->desc) + endis_len;
453
    }
454
#endif
455
456
457
458
459

    /* help_text has been freed and set to NULL unless the user resized
     * while in the help screen. */
    free(help_text);

460
    /* Allocate space for the help text. */
461
    help_text = charalloc(allocsize + 1);
462

463
    /* Now add the text we want. */
464
465
466
467
468
469
    strcpy(help_text, htx[0]);
    if (htx[1] != NULL)
	strcat(help_text, htx[1]);
    if (htx[2] != NULL)
	strcat(help_text, htx[2]);

470
471
    ptr = help_text + strlen(help_text);

472
473
474
475
476
    /* Now add our shortcut info.  Assume that each shortcut has, at the
     * very least, an equivalent control key, an equivalent primary meta
     * key sequence, or both.  Also assume that the meta key values are
     * not control characters.  We can display a maximum of 3 shortcut
     * entries. */
477
    for (s = currshortcut; s != NULL; s = s->next) {
478
	int entries = 0;
479

480
	/* Control key. */
481
	if (s->ctrlval != NANO_NO_KEY) {
482
	    entries++;
483
484
	    /* Yucky sentinel values that we can't handle a better
	     * way. */
485
486
487
488
489
490
491
492
	    if (s->ctrlval == NANO_CONTROL_SPACE) {
		char *space_ptr = display_string(_("Space"), 0, 6,
			FALSE);

		ptr += sprintf(ptr, "^%s", space_ptr);

		free(space_ptr);
	    } else if (s->ctrlval == NANO_CONTROL_8)
493
		ptr += sprintf(ptr, "^?");
494
	    /* Normal values. */
495
	    else
496
		ptr += sprintf(ptr, "^%c", s->ctrlval + 64);
497
	    *(ptr++) = '\t';
498
	}
499

500
501
502
	/* Function key. */
	if (s->funcval != NANO_NO_KEY) {
	    entries++;
503
504
505
506
507
	    /* If this is the first entry, put it in the middle. */
	    if (entries == 1) {
		entries++;
		*(ptr++) = '\t';
	    }
508
	    ptr += sprintf(ptr, "(F%d)", s->funcval - KEY_F0);
509
510
	    *(ptr++) = '\t';
	}
511

512
513
	/* Primary meta key sequence.  If it's the first entry, don't
	 * put parentheses around it. */
514
515
516
517
518
519
520
	if (s->metaval != NANO_NO_KEY) {
	    entries++;
	    /* If this is the last entry, put it at the end. */
	    if (entries == 2 && s->miscval == NANO_NO_KEY) {
		entries++;
		*(ptr++) = '\t';
	    }
521
522
523
524
	    /* Yucky sentinel values that we can't handle a better
	     * way. */
	    if (s->metaval == NANO_ALT_SPACE && entries == 1) {
		char *space_ptr = display_string(_("Space"), 0, 5,
525
526
			FALSE);

527
		ptr += sprintf(ptr, "M-%s", space_ptr);
528

529
		free(space_ptr);
530
	    } else
531
532
		/* Normal values. */
		ptr += sprintf(ptr, (entries == 1) ? "M-%c" : "(M-%c)",
533
534
535
			toupper(s->metaval));
	    *(ptr++) = '\t';
	}
536

537
538
539
540
541
542
543
544
	/* Miscellaneous meta key sequence. */
	if (entries < 3 && s->miscval != NANO_NO_KEY) {
	    entries++;
	    /* If this is the last entry, put it at the end. */
	    if (entries == 2) {
		entries++;
		*(ptr++) = '\t';
	    }
545
	    ptr += sprintf(ptr, "(M-%c)", toupper(s->miscval));
546
547
	    *(ptr++) = '\t';
	}
548

549
550
551
552
553
	/* Make sure all the help text starts at the same place. */
	while (entries < 3) {
	    entries++;
	    *(ptr++) = '\t';
	}
554
555

	assert(s->help != NULL);
556
557
558
559
560
561
562
563
564

	if (COLS > 24) {
	    char *help_ptr = display_string(s->help, 0, COLS - 24,
		FALSE);

	    ptr += sprintf(ptr, help_ptr);

	    free(help_ptr);
	}
565

566
	ptr += sprintf(ptr, "\n");
567
568
569
570
    }

#ifndef NANO_SMALL
    /* And the toggles... */
571
    if (currshortcut == main_list) {
572
	for (t = toggles; t != NULL; t = t->next) {
573

574
	    assert(t->desc != NULL);
575

576
577
	    ptr += sprintf(ptr, "M-%c\t\t\t%s %s\n", toupper(t->val),
		t->desc, _("enable/disable"));
578
	}
579
    }
580
581
582
583
584
585

#ifdef ENABLE_NANORC
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
#endif
#endif
586
587

    /* If all went well, we didn't overwrite the allocated space for
588
     * help_text. */
589
    assert(strlen(help_text) <= allocsize + 1);
590
591
592
593
594
595
596
597
}
#endif

/* Create a new filestruct node.  Note that we specifically do not set
 * prevnode->next equal to the new line. */
filestruct *make_new_node(filestruct *prevnode)
{
    filestruct *newnode = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
598

599
600
601
    newnode->data = NULL;
    newnode->prev = prevnode;
    newnode->next = NULL;
602
    newnode->lineno = (prevnode != NULL) ? prevnode->lineno + 1 : 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
603

604
605
606
    return newnode;
}

607
/* Make a copy of a filestruct node. */
Chris Allegretta's avatar
Chris Allegretta committed
608
filestruct *copy_node(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
609
{
610
611
    filestruct *dst;

Chris Allegretta's avatar
Chris Allegretta committed
612
    assert(src != NULL);
613
614
615

    dst = (filestruct *)nmalloc(sizeof(filestruct));

616
    dst->data = mallocstrcpy(NULL, src->data);
Chris Allegretta's avatar
Chris Allegretta committed
617
618
619
    dst->next = src->next;
    dst->prev = src->prev;
    dst->lineno = src->lineno;
620

Chris Allegretta's avatar
Chris Allegretta committed
621
622
623
    return dst;
}

624
/* Splice a node into an existing filestruct. */
625
626
void splice_node(filestruct *begin, filestruct *newnode, filestruct
	*end)
627
{
628
    assert(newnode != NULL && begin != NULL);
629

630
631
632
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
633
634
635
636
    if (end != NULL)
	end->prev = newnode;
}

637
/* Unlink a node from the rest of the filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
638
void unlink_node(const filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
639
{
Chris Allegretta's avatar
Chris Allegretta committed
640
    assert(fileptr != NULL);
641

642
643
644
645
646
647
648
    if (fileptr->prev != NULL)
	fileptr->prev->next = fileptr->next;
    if (fileptr->next != NULL)
	fileptr->next->prev = fileptr->prev;
}

/* Delete a node from the filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
649
void delete_node(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
650
{
651
    assert(fileptr != NULL && fileptr->data != NULL);
652

653
654
655
    if (fileptr->data != NULL)
	free(fileptr->data);
    free(fileptr);
656
657
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
658
/* Duplicate a whole filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
659
filestruct *copy_filestruct(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
660
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
661
    filestruct *head, *copy;
Chris Allegretta's avatar
Chris Allegretta committed
662

Chris Allegretta's avatar
Chris Allegretta committed
663
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
664

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
665
666
667
    copy = copy_node(src);
    copy->prev = NULL;
    head = copy;
Chris Allegretta's avatar
Chris Allegretta committed
668
    src = src->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
669

Chris Allegretta's avatar
Chris Allegretta committed
670
    while (src != NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
671
672
673
	copy->next = copy_node(src);
	copy->next->prev = copy;
	copy = copy->next;
Chris Allegretta's avatar
Chris Allegretta committed
674

Chris Allegretta's avatar
Chris Allegretta committed
675
	src = src->next;
Chris Allegretta's avatar
Chris Allegretta committed
676
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
677
    copy->next = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
678
679
680
681

    return head;
}

682
/* Frees a filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
683
void free_filestruct(filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
684
{
685
686
687
688
689
    assert(src != NULL);

    while (src->next != NULL) {
	src = src->next;
	delete_node(src->prev);
690
    }
691
    delete_node(src);
692
693
}

694
695
696
697
698
699
/* Partition a filestruct so it begins at (top, top_x) and ends at (bot,
 * bot_x). */
partition *partition_filestruct(filestruct *top, size_t top_x,
	filestruct *bot, size_t bot_x)
{
    partition *p;
700

701
    assert(top != NULL && bot != NULL && fileage != NULL && filebot != NULL);
702
703
704
705

    /* Initialize the partition. */
    p = (partition *)nmalloc(sizeof(partition));

706
707
708
709
710
711
712
713
714
715
716
717
718
    /* If the top and bottom of the partition are different from the top
     * and bottom of the filestruct, save the latter and then set them
     * to top and bot. */
    if (top != fileage) {
	p->fileage = fileage;
	fileage = top;
    } else
	p->fileage = NULL;
    if (bot != filebot) {
	p->filebot = filebot;
	filebot = bot;
    } else
	p->filebot = NULL;
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748

    /* Save the line above the top of the partition, detach the top of
     * the partition from it, and save the text before top_x in
     * top_data. */
    p->top_prev = top->prev;
    top->prev = NULL;
    p->top_data = mallocstrncpy(NULL, top->data, top_x + 1);
    p->top_data[top_x] = '\0';

    /* Save the line below the bottom of the partition, detach the
     * bottom of the partition from it, and save the text after bot_x in
     * bot_data. */
    p->bot_next = bot->next;
    bot->next = NULL;
    p->bot_data = mallocstrcpy(NULL, bot->data + bot_x);

    /* Remove all text after bot_x at the bottom of the partition. */
    null_at(&bot->data, bot_x);

    /* Remove all text before top_x at the top of the partition. */
    charmove(top->data, top->data + top_x, strlen(top->data) -
	top_x + 1);
    align(&top->data);

    /* Return the partition. */
    return p;
}

/* Unpartition a filestruct so it begins at (fileage, 0) and ends at
 * (filebot, strlen(filebot)) again. */
749
void unpartition_filestruct(partition **p)
750
751
{
    char *tmp;
752

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
753
    assert(p != NULL && fileage != NULL && filebot != NULL);
754
755
756
757
758

    /* Reattach the line above the top of the partition, and restore the
     * text before top_x from top_data.  Free top_data when we're done
     * with it. */
    tmp = mallocstrcpy(NULL, fileage->data);
759
    fileage->prev = (*p)->top_prev;
760
761
    if (fileage->prev != NULL)
	fileage->prev->next = fileage;
762
    fileage->data = charealloc(fileage->data, strlen((*p)->top_data) +
763
	strlen(fileage->data) + 1);
764
765
    strcpy(fileage->data, (*p)->top_data);
    free((*p)->top_data);
766
767
768
769
770
771
    strcat(fileage->data, tmp);
    free(tmp);

    /* Reattach the line below the bottom of the partition, and restore
     * the text after bot_x from bot_data.  Free bot_data when we're
     * done with it. */
772
    filebot->next = (*p)->bot_next;
773
774
    if (filebot->next != NULL)
	filebot->next->prev = filebot;
775
    filebot->data = charealloc(filebot->data, strlen(filebot->data) +
776
777
778
	strlen((*p)->bot_data) + 1);
    strcat(filebot->data, (*p)->bot_data);
    free((*p)->bot_data);
779

780
781
    /* Restore the top and bottom of the filestruct, if they were
     * different from the top and bottom of the partition. */
782
783
784
785
    if ((*p)->fileage != NULL)
	fileage = (*p)->fileage;
    if ((*p)->filebot != NULL)
	filebot = (*p)->filebot;
786
787

    /* Uninitialize the partition. */
788
789
    free(*p);
    *p = NULL;
790
791
}

792
793
794
795
796
797
798
799
/* Move all the text between (top, top_x) and (bot, bot_x) in the
 * current filestruct to a filestruct beginning with file_top and ending
 * with file_bot.  If no text is between (top, top_x) and (bot, bot_x),
 * don't do anything. */
void move_to_filestruct(filestruct **file_top, filestruct **file_bot,
	filestruct *top, size_t top_x, filestruct *bot, size_t bot_x)
{
    filestruct *top_save;
800
    size_t part_totsize;
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
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
887
888
889
890
891
892
893
894
895
896
897
    bool at_edittop;
#ifndef NANO_SMALL
    bool mark_inside = FALSE;
#endif

    assert(file_top != NULL && file_bot != NULL && top != NULL && bot != NULL);

    /* If (top, top_x)-(bot, bot_x) doesn't cover any text, get out. */
    if (top == bot && top_x == bot_x)
	return;

    /* Partition the filestruct so that it contains only the text from
     * (top, top_x) to (bot, bot_x), keep track of whether the top of
     * the partition is the top of the edit window, and keep track of
     * whether the mark begins inside the partition. */
    filepart = partition_filestruct(top, top_x, bot, bot_x);
    at_edittop = (fileage == edittop);
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
	mark_inside = (mark_beginbuf->lineno >= fileage->lineno &&
		mark_beginbuf->lineno <= filebot->lineno &&
		(mark_beginbuf != fileage || mark_beginx >= top_x) &&
		(mark_beginbuf != filebot || mark_beginx <= bot_x));
#endif

    /* Get the number of characters in the text, and subtract it from
     * totsize. */
    get_totals(top, bot, NULL, &part_totsize);
    totsize -= part_totsize;

    if (*file_top == NULL) {
	/* If file_top is empty, just move all the text directly into
	 * it.  This is equivalent to tacking the text in top onto the
	 * (lack of) text at the end of file_top. */
	*file_top = fileage;
	*file_bot = filebot;
    } else {
	/* Otherwise, tack the text in top onto the text at the end of
	 * file_bot. */
	(*file_bot)->data = charealloc((*file_bot)->data,
		strlen((*file_bot)->data) + strlen(fileage->data) + 1);
	strcat((*file_bot)->data, fileage->data);

	/* Attach the line after top to the line after file_bot.  Then,
	 * if there's more than one line after top, move file_bot down
	 * to bot. */
	(*file_bot)->next = fileage->next;
	if ((*file_bot)->next != NULL) {
	    (*file_bot)->next->prev = *file_bot;
	    *file_bot = filebot;
	}
    }

    /* Since the text has now been saved, remove it from the filestruct.
     * If the top of the partition was the top of the edit window, set
     * edittop to where the text used to start.  If the mark began
     * inside the partition, set the beginning of the mark to where the
     * text used to start. */
    fileage = (filestruct *)nmalloc(sizeof(filestruct));
    fileage->data = mallocstrcpy(NULL, "");
    filebot = fileage;
    if (at_edittop)
	edittop = fileage;
#ifndef NANO_SMALL
    if (mark_inside) {
	mark_beginbuf = fileage;
	mark_beginx = top_x;
    }
#endif

    /* Restore the current line and cursor position. */
    current = fileage;
    current_x = top_x;

    top_save = fileage;

    /* Unpartition the filestruct so that it contains all the text
     * again, minus the saved text. */
    unpartition_filestruct(&filepart);

    /* Renumber starting with the beginning line of the old
     * partition. */
    renumber(top_save);

    if (filebot->data[0] != '\0')
	new_magicline();

    /* Set totlines to the new number of lines in the file. */
    totlines = filebot->lineno;
}

/* Copy all the text from the filestruct beginning with file_top and
 * ending with file_bot to the current filestruct at the current cursor
 * position. */
void copy_from_filestruct(filestruct *file_top, filestruct *file_bot)
{
    filestruct *top_save;
898
    size_t part_totlines, part_totsize;
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
    bool at_edittop;

    assert(file_top != NULL && file_bot != NULL);

    /* Partition the filestruct so that it contains no text, and keep
     * track of whether the top of the partition is the top of the edit
     * window. */
    filepart = partition_filestruct(current, current_x, current,
	current_x);
    at_edittop = (fileage == edittop);

    /* Put the top and bottom of the filestruct at copies of file_top
     * and file_bot. */
    fileage = copy_filestruct(file_top);
    filebot = fileage;
    while (filebot->next != NULL)
	filebot = filebot->next;

    /* Restore the current line and cursor position. */
    current = filebot;
    current_x = strlen(filebot->data);
    if (fileage == filebot)
	current_x += strlen(filepart->top_data);

    /* Get the number of lines and the number of characters in the saved
     * text, and add the latter to totsize. */
    get_totals(fileage, filebot, &part_totlines, &part_totsize);
    totsize += part_totsize;

    /* If the top of the partition was the top of the edit window, set
     * edittop to where the saved text now starts, and update the
     * current y-coordinate to account for the number of lines it
     * has, less one since the first line will be tacked onto the
     * current line. */
    if (at_edittop)
	edittop = fileage;
    current_y += part_totlines - 1;

    top_save = fileage;

    /* Unpartition the filestruct so that it contains all the text
     * again, minus the saved text. */
    unpartition_filestruct(&filepart);

    /* Renumber starting with the beginning line of the old
     * partition. */
    renumber(top_save);

    if (filebot->data[0] != '\0')
	new_magicline();

    /* Set totlines to the new number of lines in the file. */
    totlines = filebot->lineno;
}

Chris Allegretta's avatar
Chris Allegretta committed
954
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
955
956
{
    filestruct *temp;
957
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
958

Chris Allegretta's avatar
Chris Allegretta committed
959
    assert(fileage == NULL || fileage != fileage->next);
960

Chris Allegretta's avatar
Chris Allegretta committed
961
    for (temp = fileage; temp != NULL; temp = temp->next)
Chris Allegretta's avatar
Chris Allegretta committed
962
963
964
	temp->lineno = i++;
}

Chris Allegretta's avatar
Chris Allegretta committed
965
void renumber(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
966
{
Chris Allegretta's avatar
Chris Allegretta committed
967
    if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage)
Chris Allegretta's avatar
Chris Allegretta committed
968
	renumber_all();
Chris Allegretta's avatar
Chris Allegretta committed
969
970
    else {
	int lineno = fileptr->prev->lineno;
971

Chris Allegretta's avatar
Chris Allegretta committed
972
	assert(fileptr != fileptr->next);
973

Chris Allegretta's avatar
Chris Allegretta committed
974
975
976
	for (; fileptr != NULL; fileptr = fileptr->next)
	    fileptr->lineno = ++lineno;
    }
Chris Allegretta's avatar
Chris Allegretta committed
977
978
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
979
980
981
982
983
984
#ifdef HAVE_GETOPT_LONG
#define print1opt(shortflag, longflag, desc) print1opt_full(shortflag, longflag, desc)
#else
#define print1opt(shortflag, longflag, desc) print1opt_full(shortflag, desc)
#endif

985
/* Print one usage string to the screen.  This cuts down on duplicate
986
 * strings to translate, and leaves out the parts that shouldn't be
Chris Allegretta's avatar
Chris Allegretta committed
987
 * translatable (the flag names). */
988
989
990
991
992
void print1opt_full(const char *shortflag
#ifdef HAVE_GETOPT_LONG
	, const char *longflag
#endif
	, const char *desc)
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
{
    printf(" %s\t", shortflag);
    if (strlen(shortflag) < 8)
	printf("\t");

#ifdef HAVE_GETOPT_LONG
    printf("%s\t", longflag);
    if (strlen(longflag) < 8)
	printf("\t\t");
    else if (strlen(longflag) < 16)
	printf("\t");
#endif

1006
1007
1008
    if (desc != NULL)
	printf("%s", _(desc));
    printf("\n");
1009
1010
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1011
void usage(void)
Chris Allegretta's avatar
Chris Allegretta committed
1012
1013
{
#ifdef HAVE_GETOPT_LONG
1014
1015
    printf(
	_("Usage: nano [+LINE,COLUMN] [GNU long option] [option] [file]\n\n"));
Chris Allegretta's avatar
Chris Allegretta committed
1016
    printf(_("Option\t\tLong option\t\tMeaning\n"));
Chris Allegretta's avatar
Chris Allegretta committed
1017
#else
Chris Allegretta's avatar
Chris Allegretta committed
1018
1019
    printf(_("Usage: nano [+LINE] [option] [file]\n\n"));
    printf(_("Option\t\tMeaning\n"));
1020
#endif
1021

1022
    print1opt("-h, -?", "--help", N_("Show this message"));
1023
1024
    print1opt(_("+LINE,COLUMN"), "",
	N_("Start at line LINE, column COLUMN"));
1025
#ifndef NANO_SMALL
1026
    print1opt("-A", "--smarthome", N_("Enable smart home key"));
1027
    print1opt("-B", "--backup", N_("Save backups of existing files"));
1028
    print1opt(_("-C [dir]"), _("--backupdir=[dir]"),
1029
	N_("Directory for saving unique backup files"));
1030
1031
    print1opt("-E", "--tabstospaces",
	N_("Convert typed tabs to spaces"));
1032
#endif
1033
#ifdef ENABLE_MULTIBUFFER
1034
    print1opt("-F", "--multibuffer", N_("Enable multiple file buffers"));
Chris Allegretta's avatar
Chris Allegretta committed
1035
1036
#endif
#ifdef ENABLE_NANORC
1037
#ifndef NANO_SMALL
1038
1039
    print1opt("-H", "--historylog",
	N_("Log & read search/replace string history"));
1040
#endif
1041
1042
    print1opt("-I", "--ignorercfiles",
	N_("Don't look at nanorc files"));
Chris Allegretta's avatar
Chris Allegretta committed
1043
1044
#endif
#ifndef NANO_SMALL
1045
1046
    print1opt("-N", "--noconvert",
	N_("Don't convert files from DOS/Mac format"));
1047
#endif
1048
    print1opt("-O", "--morespace", N_("Use more space for editing"));
1049
#ifndef DISABLE_JUSTIFY
1050
    print1opt(_("-Q [str]"), _("--quotestr=[str]"),
1051
	N_("Quoting string"));
1052
#endif
1053
    print1opt("-R", "--restricted", N_("Restricted mode"));
1054
#ifndef NANO_SMALL
1055
    print1opt("-S", "--smooth", N_("Smooth scrolling"));
1056
#endif
1057
1058
    print1opt(_("-T [#cols]"), _("--tabsize=[#cols]"),
	N_("Set width of a tab in cols to #cols"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1059
#ifndef NANO_SMALL
1060
1061
    print1opt("-U", _("--quickblank"),
	N_("Do quick statusbar blanking"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1062
#endif
1063
1064
    print1opt("-V", "--version",
	N_("Print version information and exit"));
1065
#ifdef ENABLE_COLOR
1066
1067
    print1opt(_("-Y [str]"), _("--syntax=[str]"),
	N_("Syntax definition to use"));
1068
#endif
1069
    print1opt("-c", "--const", N_("Constantly show cursor position"));
1070
1071
    print1opt("-d", "--rebinddelete",
	N_("Fix Backspace/Delete confusion problem"));
1072
#ifndef NANO_SMALL
1073
1074
    print1opt("-i", "--autoindent",
	N_("Automatically indent new lines"));
1075
    print1opt("-k", "--cut", N_("Cut from cursor to end of line"));
1076
#endif
1077
1078
    print1opt("-l", "--nofollow",
	N_("Don't follow symbolic links, overwrite"));
1079
#ifndef DISABLE_MOUSE
1080
    print1opt("-m", "--mouse", N_("Enable mouse"));
Chris Allegretta's avatar
Chris Allegretta committed
1081
#endif
1082
#ifndef DISABLE_OPERATINGDIR
1083
1084
    print1opt(_("-o [dir]"), _("--operatingdir=[dir]"),
	N_("Set operating directory"));
Chris Allegretta's avatar
Chris Allegretta committed
1085
#endif
1086
1087
    print1opt("-p", "--preserve",
	N_("Preserve XON (^Q) and XOFF (^S) keys"));
1088
#ifndef DISABLE_WRAPJUSTIFY
1089
1090
    print1opt(_("-r [#cols]"), _("--fill=[#cols]"),
	N_("Set fill cols to (wrap lines at) #cols"));
1091
#endif
1092
#ifndef DISABLE_SPELLER
1093
1094
    print1opt(_("-s [prog]"), _("--speller=[prog]"),
	N_("Enable alternate speller"));
1095
#endif
1096
1097
    print1opt("-t", "--tempfile",
	N_("Auto save on exit, don't prompt"));
1098
    print1opt("-v", "--view", N_("View (read only) mode"));
1099
#ifndef DISABLE_WRAPPING
1100
    print1opt("-w", "--nowrap", N_("Don't wrap long lines"));
Chris Allegretta's avatar
Chris Allegretta committed
1101
#endif
1102
1103
    print1opt("-x", "--nohelp", N_("Don't show help window"));
    print1opt("-z", "--suspend", N_("Enable suspend"));
Chris Allegretta's avatar
Chris Allegretta committed
1104

1105
    /* This is a special case. */
1106
    print1opt("-a, -b, -e,", "", NULL);
1107
    print1opt("-f, -g, -j", "", N_("(ignored, for Pico compatibility)"));
1108

Chris Allegretta's avatar
Chris Allegretta committed
1109
1110
1111
    exit(0);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1112
void version(void)
Chris Allegretta's avatar
Chris Allegretta committed
1113
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1114
1115
1116
1117
    printf(_(" GNU nano version %s (compiled %s, %s)\n"), VERSION,
	__TIME__, __DATE__);
    printf(
	_(" Email: nano@nano-editor.org	Web: http://www.nano-editor.org/"));
1118
    printf(_("\n Compiled options:"));
1119

1120
#ifdef DISABLE_BROWSER
1121
    printf(" --disable-browser");
1122
#endif
1123
1124
#ifdef DISABLE_HELP
    printf(" --disable-help");
1125
1126
#endif
#ifdef DISABLE_JUSTIFY
1127
    printf(" --disable-justify");
1128
#endif
1129
#ifdef DISABLE_MOUSE
1130
    printf(" --disable-mouse");
1131
#endif
1132
1133
1134
#ifndef ENABLE_NLS
    printf(" --disable-nls");
#endif
1135
1136
1137
#ifdef DISABLE_OPERATINGDIR
    printf(" --disable-operatingdir");
#endif
1138
1139
1140
1141
1142
1143
1144
1145
1146
#ifdef DISABLE_SPELLER
    printf(" --disable-speller");
#endif
#ifdef DISABLE_TABCOMP
    printf(" --disable-tabcomp");
#endif
#ifdef DISABLE_WRAPPING
    printf(" --disable-wrapping");
#endif
1147
1148
1149
#ifdef DISABLE_ROOTWRAP
    printf(" --disable-wrapping-as-root");
#endif
1150
1151
1152
#ifdef ENABLE_COLOR
    printf(" --enable-color");
#endif
1153
1154
1155
1156
1157
1158
#ifdef DEBUG
    printf(" --enable-debug");
#endif
#ifdef NANO_EXTRA
    printf(" --enable-extra");
#endif
1159
1160
1161
1162
1163
1164
#ifdef ENABLE_MULTIBUFFER
    printf(" --enable-multibuffer");
#endif
#ifdef ENABLE_NANORC
    printf(" --enable-nanorc");
#endif
1165
1166
1167
1168
1169
1170
#ifdef NANO_SMALL
    printf(" --enable-tiny");
#endif
#ifdef NANO_WIDE
    printf(" --enable-utf8");
#endif
1171
1172
1173
1174
#ifdef USE_SLANG
    printf(" --with-slang");
#endif
    printf("\n");
Chris Allegretta's avatar
Chris Allegretta committed
1175
1176
}

1177
1178
1179
1180
1181
int no_more_space(void)
{
    return ISSET(MORE_SPACE) ? 1 : 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
1182
1183
int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
1184
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
1185
1186
}

1187
void nano_disabled_msg(void)
1188
{
Chris Allegretta's avatar
Chris Allegretta committed
1189
    statusbar(_("Sorry, support for this function has been disabled"));
1190
1191
}

Chris Allegretta's avatar
Chris Allegretta committed
1192
#ifndef NANO_SMALL
1193
void cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
1194
{
1195
1196
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
1197
1198
}

1199
1200
/* Return TRUE on success. */
bool open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
1201
{
1202
1203
1204
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1205
	/* Original and temporary handlers for SIGINT. */
1206
    bool sig_failed = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1207
	/* Did sigaction() fail without changing the signal handlers? */
Chris Allegretta's avatar
Chris Allegretta committed
1208

1209
1210
1211
    /* Make our pipes. */
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
1212
	return FALSE;
1213
    }
1214

1215
1216
1217
1218
1219
    /* Fork a child. */
    if ((pid = fork()) == 0) {
	close(fd[0]);
	dup2(fd[1], fileno(stdout));
	dup2(fd[1], fileno(stderr));
1220

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1221
	/* If execl() returns at all, there was an error. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1222
	execl("/bin/sh", "sh", "-c", command, NULL);
1223
1224
	exit(0);
    }
1225

1226
1227
1228
1229
1230
1231
    /* Else continue as parent. */
    close(fd[1]);

    if (pid == -1) {
	close(fd[0]);
	statusbar(_("Could not fork"));
1232
	return FALSE;
1233
    }
Chris Allegretta's avatar
Chris Allegretta committed
1234

1235
    /* Before we start reading the forked command's output, we set
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1236
     * things up so that Ctrl-C will cancel the new process. */
1237

1238
1239
    /* Enable interpretation of the special control keys so that we get
     * SIGINT when Ctrl-C is pressed. */
1240
1241
    enable_signals();

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1242
    if (sigaction(SIGINT, NULL, &newaction) == -1) {
1243
	sig_failed = TRUE;
1244
	nperror("sigaction");
Chris Allegretta's avatar
Chris Allegretta committed
1245
    } else {
1246
	newaction.sa_handler = cancel_fork;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1247
	if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
1248
	    sig_failed = TRUE;
1249
1250
	    nperror("sigaction");
	}
Chris Allegretta's avatar
Chris Allegretta committed
1251
    }
1252
1253
    /* Note that now oldaction is the previous SIGINT signal handler,
     * to be restored later. */
1254

1255
    f = fdopen(fd[0], "rb");
1256
    if (f == NULL)
1257
	nperror("fdopen");
1258

1259
    read_file(f, "stdin");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1260

1261
1262
    /* If multibuffer mode is on, we could be here in view mode.  If so,
     * don't set the modification flag. */
1263
1264
1265
1266
1267
1268
    if (!ISSET(VIEW_MODE))
	set_modified();

    if (wait(NULL) == -1)
	nperror("wait");

1269
    if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
1270
1271
	nperror("sigaction");

1272
1273
    /* Disable interpretation of the special control keys so that we can
     * use Ctrl-C for other things. */
1274
1275
    disable_signals();

1276
    return TRUE;
1277
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1278
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1279

1280
void do_verbatim_input(void)
1281
{
1282
1283
1284
    int *kbinput;
    size_t kbinput_len, i;
    char *output;
1285

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1286
    statusbar(_("Verbatim Input"));
1287

1288
1289
    /* Read in all the verbatim characters. */
    kbinput = get_verbatim_kbinput(edit, &kbinput_len);
1290

1291
1292
    /* Display all the verbatim characters at once, not filtering out
     * control characters. */
1293
    output = charalloc(kbinput_len + 1);
1294

1295
1296
1297
1298
    for (i = 0; i < kbinput_len; i++)
	output[i] = (char)kbinput[i];
    output[i] = '\0';

1299
    do_output(output, kbinput_len, TRUE);
1300
1301

    free(output);
1302
1303
1304
1305
1306

    /* If constant cursor position display is on, make sure the current
     * cursor position is properly displayed on the statusbar. */
    if (ISSET(CONST_UPDATE))
	do_cursorpos(TRUE);
1307
1308
}

1309
void do_backspace(void)
Chris Allegretta's avatar
Chris Allegretta committed
1310
{
1311
    if (current != fileage || current_x > 0) {
1312
	do_left(FALSE);
1313
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1314
1315
1316
    }
}

1317
void do_delete(void)
Chris Allegretta's avatar
Chris Allegretta committed
1318
{
1319
    bool do_refresh = FALSE;
1320
1321
1322
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

1323
    assert(current != NULL && current->data != NULL && current_x <= strlen(current->data));
Chris Allegretta's avatar
Chris Allegretta committed
1324

1325
    placewewant = xplustabs();
1326

1327
    if (current->data[current_x] != '\0') {
1328
1329
	int char_buf_len = parse_mbchar(current->data + current_x, NULL,
		NULL, NULL);
1330
	size_t line_len = strlen(current->data + current_x);
1331

1332
	assert(current_x < strlen(current->data));
Chris Allegretta's avatar
Chris Allegretta committed
1333

1334
	/* Let's get dangerous. */
1335
	charmove(&current->data[current_x],
1336
1337
		&current->data[current_x + char_buf_len],
		line_len - char_buf_len + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1338

1339
	null_at(&current->data, current_x + line_len - char_buf_len);
1340
1341
#ifndef NANO_SMALL
	if (current_x < mark_beginx && mark_beginbuf == current)
1342
	    mark_beginx -= char_buf_len;
Chris Allegretta's avatar
Chris Allegretta committed
1343
#endif
1344
	totsize--;
1345
1346
    } else if (current != filebot && (current->next != filebot ||
	current->data[0] == '\0')) {
1347
	/* We can delete the line before filebot only if it is blank: it
1348
	 * becomes the new magicline then. */
1349
	filestruct *foo = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1350

1351
	assert(current_x == strlen(current->data));
1352
1353
1354
1355
1356
1357

	/* If we're deleting at the end of a line, we need to call
	 * edit_refresh(). */
	if (current->data[current_x] == '\0')
	    do_refresh = TRUE;

1358
1359
	current->data = charealloc(current->data,
		current_x + strlen(foo->data) + 1);
1360
1361
1362
1363
1364
1365
1366
	strcpy(current->data + current_x, foo->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == current->next) {
	    mark_beginx += current_x;
	    mark_beginbuf = current;
	}
#endif
1367
	if (filebot == foo)
Chris Allegretta's avatar
Chris Allegretta committed
1368
1369
1370
1371
1372
1373
	    filebot = current;

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
1374
	totsize--;
1375
#ifndef DISABLE_WRAPPING
1376
	wrap_reset();
1377
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1378
    } else
1379
	return;
Chris Allegretta's avatar
Chris Allegretta committed
1380
1381

    set_modified();
1382
1383
1384
1385

#ifdef ENABLE_COLOR
    /* If color syntaxes are turned on, we need to call
     * edit_refresh(). */
1386
    if (!ISSET(NO_COLOR_SYNTAX))
1387
1388
1389
1390
1391
1392
1393
	do_refresh = TRUE;
#endif

    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
1394
1395
}

1396
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1397
{
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
#ifndef NANO_SMALL
    if (ISSET(TABS_TO_SPACES)) {
	char *output;
	size_t output_len = 0, new_pww = placewewant;

	do {
	    new_pww++;
	    output_len++;
	} while (new_pww % tabsize != 0);

	output = charalloc(output_len + 1);

	charset(output, ' ', output_len);
	output[output_len] = '\0';

	do_output(output, output_len, TRUE);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1414
1415

	free(output);
1416
1417
1418
1419
1420
1421
    } else {
#endif
	do_output("\t", 1, TRUE);
#ifndef NANO_SMALL
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1422
1423
}

1424
/* Someone hits Return *gasp!* */
1425
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1426
{
1427
1428
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1429

1430
    assert(current != NULL && current->data != NULL);
1431

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1432
#ifndef NANO_SMALL
1433
1434
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1435
1436
1437
1438
1439
	/* If we are breaking the line in the indentation, the new
	 * indentation should have only current_x characters, and
	 * current_x should not change. */
	extra = indent_length(current->data);
	if (extra > current_x)
1440
	    extra = current_x;
1441
1442
1443
1444
1445
1446
    }
#endif
    newnode->data = charalloc(strlen(current->data + current_x) +
	extra + 1);
    strcpy(&newnode->data[extra], current->data + current_x);
#ifndef NANO_SMALL
1447
    if (ISSET(AUTOINDENT)) {
1448
	strncpy(newnode->data, current->data, extra);
1449
1450
	totsize += mbstrlen(newnode->data);
    }
1451
#endif
1452
1453
1454
1455
1456
    null_at(&current->data, current_x);
#ifndef NANO_SMALL
    if (current == mark_beginbuf && current_x < mark_beginx) {
	mark_beginbuf = newnode;
	mark_beginx += extra - current_x;
1457
    }
1458
1459
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1460

1461
    if (current == filebot)
1462
1463
	filebot = newnode;
    splice_node(current, newnode, current->next);
1464

1465
1466
    renumber(current);
    current = newnode;
1467
1468

    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1469

1470
    totlines++;
1471
    totsize++;
1472
1473
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1474
1475
}

1476
#ifndef NANO_SMALL
1477
1478
1479
1480
1481
/* Move to the next word in the current filestruct.  If allow_punct is
 * TRUE, treat punctuation as part of a word.  If allow_update is TRUE,
 * update the screen afterward.  Return TRUE if we started on a word,
 * and FALSE otherwise. */
bool do_next_word(bool allow_punct, bool allow_update)
Chris Allegretta's avatar
Chris Allegretta committed
1482
{
1483
1484
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1485
    char *char_mb;
1486
    int char_mb_len;
1487
    bool started_on_word = FALSE;
1488

1489
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1490

1491
1492
    char_mb = charalloc(mb_cur_max());

1493
1494
    /* Move forward until we find the character after the last letter of
     * the current word. */
1495
    while (current->data[current_x] != '\0') {
1496
1497
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1498

1499
1500
	/* If we've found it, stop moving forward through the current
	 * line. */
1501
	if (!is_word_mbchar(char_mb, allow_punct))
1502
	    break;
1503

1504
1505
	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
1506
	started_on_word = TRUE;
1507

1508
	current_x += char_mb_len;
1509
    }
Chris Allegretta's avatar
Chris Allegretta committed
1510

1511
    /* Move forward until we find the first letter of the next word. */
1512
1513
1514
    if (current->data[current_x] != '\0')
	current_x += char_mb_len;

1515
    for (; current != NULL; current = current->next) {
1516
	while (current->data[current_x] != '\0') {
1517
	    char_mb_len = parse_mbchar(current->data + current_x,
1518
		char_mb, NULL, NULL);
1519

1520
1521
	    /* If we've found it, stop moving forward through the
	     * current line. */
1522
	    if (is_word_mbchar(char_mb, allow_punct))
1523
1524
		break;

1525
	    current_x += char_mb_len;
1526
	}
Chris Allegretta's avatar
Chris Allegretta committed
1527

1528
1529
	/* If we've found it, stop moving forward to the beginnings of
	 * subsequent lines. */
1530
1531
	if (current->data[current_x] != '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1532

1533
1534
	current_x = 0;
    }
1535

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1536
1537
    free(char_mb);

1538
1539
    /* If we haven't found it, leave the cursor at the end of the
     * file. */
1540
1541
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1542

1543
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1544

1545
1546
1547
1548
1549
1550
1551
1552
    /* If allow_update is TRUE, update the screen. */
    if (allow_update)
	edit_redraw(current_save, pww_save);

    /* Return whether we started on a word. */
    return started_on_word;
}

1553
1554
/* Move to the next word in the current filestruct, not counting
 * punctuation as part of a word, and updating the screen afterward. */
1555
1556
void do_next_word_void(void)
{
1557
    do_next_word(FALSE, TRUE);
1558
}
Chris Allegretta's avatar
Chris Allegretta committed
1559

1560
1561
1562
1563
1564
/* Move to the previous word in the current filestruct.  If allow_punct
 * is TRUE, treat punctuation as part of a word.  If allow_update is
 * TRUE, update the screen afterward.  Return TRUE if we started on a
 * word, and FALSE otherwise. */
bool do_prev_word(bool allow_punct, bool allow_update)
1565
{
1566
1567
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1568
    char *char_mb;
1569
    int char_mb_len;
1570
    bool begin_line = FALSE, started_on_word = FALSE;
1571

1572
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1573

1574
1575
    char_mb = charalloc(mb_cur_max());

1576
1577
1578
    /* Move backward until we find the character before the first letter
     * of the current word. */
    while (!begin_line) {
1579
1580
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1581

1582
1583
	/* If we've found it, stop moving backward through the current
	 * line. */
1584
	if (!is_word_mbchar(char_mb, allow_punct))
1585
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1586

1587
1588
1589
1590
	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
	started_on_word = TRUE;

1591
1592
1593
1594
1595
1596
1597
1598
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);
    }

    /* Move backward until we find the last letter of the previous
     * word. */
1599
1600
1601
1602
1603
    if (current_x == 0)
	begin_line = TRUE;
    else
	current_x = move_mbleft(current->data, current_x);

1604
    for (; current != NULL; current = current->prev) {
1605
1606
	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1607
		char_mb, NULL, NULL);
1608
1609
1610

	    /* If we've found it, stop moving backward through the
	     * current line. */
1611
	    if (is_word_mbchar(char_mb, allow_punct))
1612
		break;
Chris Allegretta's avatar
Chris Allegretta committed
1613

1614
1615
1616
1617
1618
1619
1620
1621
1622
	    if (current_x == 0)
		begin_line = TRUE;
	    else
		current_x = move_mbleft(current->data, current_x);
	}

	/* If we've found it, stop moving backward to the ends of
	 * previous lines. */
	if (!begin_line)
1623
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1624

1625
1626
1627
1628
	if (current->prev != NULL) {
	    begin_line = FALSE;
	    current_x = strlen(current->prev->data);
	}
Chris Allegretta's avatar
Chris Allegretta committed
1629
1630
    }

1631
1632
    /* If we haven't found it, leave the cursor at the beginning of the
     * file. */
1633
    if (current == NULL) {
1634
1635
	current = fileage;
	current_x = 0;
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
    /* If we've found it, move backward until we find the character
     * before the first letter of the previous word. */
    } else if (!begin_line) {
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);

	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1646
		char_mb, NULL, NULL);
1647
1648
1649

	    /* If we've found it, stop moving backward through the
	     * current line. */
1650
	    if (!is_word_mbchar(char_mb, allow_punct))
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
		break;

	    if (current_x == 0)
		begin_line = TRUE;
	    else
		current_x = move_mbleft(current->data, current_x);
	}

	/* If we've found it, move forward to the first letter of the
	 * previous word. */
	if (!begin_line)
	    current_x += char_mb_len;
1663
    }
Chris Allegretta's avatar
Chris Allegretta committed
1664

1665
1666
    free(char_mb);

1667
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1668

1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
    /* If allow_update is TRUE, update the screen. */
    if (allow_update)
	edit_redraw(current_save, pww_save);

    /* Return whether we started on a word. */
    return started_on_word;
}

/* Move to the previous word in the current filestruct, not counting
 * punctuation as part of a word, and updating the screen afterward. */
void do_prev_word_void(void)
{
    do_prev_word(FALSE, TRUE);
1682
}
Chris Allegretta's avatar
Chris Allegretta committed
1683

1684
1685
1686
1687
1688
void do_word_count(void)
{
    size_t words = 0;
    size_t current_x_save = current_x, pww_save = placewewant;
    filestruct *current_save = current;
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
    bool old_mark_set = ISSET(MARK_ISSET);
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

    if (old_mark_set) {
	/* If the mark is on, partition the filestruct so that it
	 * contains only the marked text, keep track of whether the text
	 * will need a magicline added while we're counting words, add
	 * the magicline if necessary, and turn the mark off. */
	mark_order((const filestruct **)&top, &top_x,
	    (const filestruct **)&bot, &bot_x, NULL);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	if ((added_magicline = (filebot->data[0] != '\0')))
	    new_magicline();
	UNSET(MARK_ISSET);
    }
1707

1708
    /* Start at the top of the file. */
1709
1710
1711
1712
    current = fileage;
    current_x = 0;
    placewewant = 0;

1713
1714
1715
1716
1717
    /* Keep moving to the next word (counting punctuation characters as
     * part of a word so that we match the output of "wc -w"), without
     * updating the screen, until we reach the end of the file,
     * incrementing the total word count whenever we're on a word just
     * before moving. */
1718
    while (current != filebot || current_x != 0) {
1719
	if (do_next_word(TRUE, FALSE))
1720
1721
1722
	    words++;
    }

1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
    if (old_mark_set) {
	/* If the mark was on and we added a magicline, remove it
	 * now. */
	if (added_magicline)
	    remove_magicline();

	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
	unpartition_filestruct(&filepart);
	SET(MARK_ISSET);
    }

    /* Restore where we were. */
1736
1737
1738
1739
1740
    current = current_save;
    current_x = current_x_save;
    placewewant = pww_save;

    /* Display the total word count on the statusbar. */
1741
1742
    statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") :
	_("Word Count"), (unsigned long)words);
1743
1744
}

1745
void do_mark(void)
1746
{
1747
1748
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1749
1750
1751
1752
1753
1754
1755
1756
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1757
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1758

1759
#ifndef DISABLE_WRAPPING
1760
1761
void wrap_reset(void)
{
1762
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1763
}
1764
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1765

1766
#ifndef DISABLE_WRAPPING
1767
1768
1769
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1770
bool do_wrap(filestruct *line)
Chris Allegretta's avatar
Chris Allegretta committed
1771
{
1772
    size_t line_len;
1773
	/* Length of the line we wrap. */
1774
    ssize_t wrap_loc;
1775
	/* Index of line->data where we wrap. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1776
#ifndef NANO_SMALL
1777
    const char *indent_string = NULL;
1778
	/* Indentation to prepend to the new line. */
1779
    size_t indent_len = 0;	/* The length of indent_string. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1780
#endif
1781
1782
    const char *after_break;	/* The text after the wrap point. */
    size_t after_break_len;	/* The length of after_break. */
1783
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1784
    const char *next_line = NULL;
1785
	/* The next line, minus indentation. */
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
    size_t next_line_len = 0;	/* The length of next_line. */
    char *new_line = NULL;	/* The line we create. */
    size_t new_line_len = 0;	/* The eventual length of new_line. */

    /* There are three steps.  First, we decide where to wrap.  Then, we
     * create the new wrap line.  Finally, we clean up. */

    /* Step 1, finding where to wrap.  We are going to add a new line
     * after a blank character.  In this step, we call break_line() to
     * get the location of the last blank we can break the line at, and
     * and set wrap_loc to the location of the character after it, so
     * that the blank is preserved at the end of the line.
     *
     * If there is no legal wrap point, or we reach the last character
     * of the line while trying to find one, we should return without
     * wrapping.  Note that if autoindent is turned on, we don't break
     * at the end of it! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1803

1804
1805
1806
1807
1808
    assert(line != NULL && line->data != NULL);

    /* Save the length of the line. */
    line_len = strlen(line->data);

1809
1810
    /* Find the last blank where we can break the line. */
    wrap_loc = break_line(line->data, fill, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1811

1812
1813
1814
    /* If we couldn't break the line, or we've reached the end of it, we
     * don't wrap. */
    if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
1815
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1816

1817
1818
1819
1820
1821
1822
    /* Otherwise, move forward to the character just after the blank. */
    wrap_loc += move_mbright(line->data + wrap_loc, 0);

    /* If we've reached the end of the line, we don't wrap. */
    if (line->data[wrap_loc] == '\0')
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1823

1824
1825
1826
1827
1828
1829
1830
#ifndef NANO_SMALL
    /* If autoindent is turned on, and we're on the character just after
     * the indentation, we don't wrap. */
    if (ISSET(AUTOINDENT)) {
	/* Get the indentation of this line. */
	indent_string = line->data;
	indent_len = indent_length(indent_string);
1831

1832
1833
1834
1835
	if (wrap_loc == indent_len)
	    return FALSE;
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1836

1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
    /* Step 2, making the new wrap line.  It will consist of indentation
     * followed by the text after the wrap point, optionally followed by
     * a space (if the text after the wrap point doesn't end in a blank)
     * and the text of the next line, if they can fit without
     * wrapping, the next line exists, and the same_line_wrap flag is
     * set. */

    /* after_break is the text that will be wrapped to the next line. */
    after_break = line->data + wrap_loc;
    after_break_len = line_len - wrap_loc;

    assert(strlen(after_break) == after_break_len);

    /* We prepend the wrapped text to the next line, if the
     * same_line_wrap flag is set, there is a next line, and prepending
     * would not make the line too long. */
    if (same_line_wrap && line->next != NULL) {
	const char *end = after_break + move_mbleft(after_break,
		after_break_len);

	/* If after_break doesn't end in a blank, make sure it ends in a
	 * space. */
	if (!is_blank_mbchar(end)) {
	    line_len++;
	    line->data = charealloc(line->data, line_len + 1);
	    line->data[line_len - 1] = ' ';
	    line->data[line_len] = '\0';
	    after_break = line->data + wrap_loc;
	    after_break_len++;
	    totsize++;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1868

1869
1870
	next_line = line->next->data;
	next_line_len = strlen(next_line);
Chris Allegretta's avatar
Chris Allegretta committed
1871

1872
	if (after_break_len + next_line_len <= fill) {
1873
	    wrapping = TRUE;
1874
	    new_line_len += next_line_len;
1875
1876
	}
    }
1877

1878
1879
1880
1881
1882
    /* new_line_len is now the length of the text that will be wrapped
     * to the next line, plus (if we're prepending to it) the length of
     * the text of the next line. */
    new_line_len += after_break_len;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1883
#ifndef NANO_SMALL
1884
    if (ISSET(AUTOINDENT)) {
1885
1886
1887
1888
1889
1890
1891
1892
1893
	if (wrapping) {
	    /* If we're wrapping, the indentation will come from the
	     * next line. */
	    indent_string = next_line;
	    indent_len = indent_length(indent_string);
	    next_line += indent_len;
	} else {
	    /* Otherwise, it will come from this line, in which case
	     * we should increase new_line_len to make room for it. */
1894
	    new_line_len += indent_len;
1895
1896
	    totsize += mbstrnlen(indent_string, indent_len);
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1897
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1898
1899
#endif

1900
1901
1902
    /* Now we allocate the new line and copy the text into it. */
    new_line = charalloc(new_line_len + 1);
    new_line[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1903

1904
1905
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
1906
	/* Copy the indentation. */
1907
	strncpy(new_line, indent_string, indent_len);
1908
1909
	new_line[indent_len] = '\0';
	new_line_len += indent_len;
1910
    }
1911
#endif
1912

1913
1914
1915
1916
1917
1918
    /* Copy all the text after the wrap point of the current line. */
    strcat(new_line, after_break);

    /* Break the current line at the wrap point. */
    null_at(&line->data, wrap_loc);

1919
    if (wrapping) {
1920
1921
1922
1923
1924
1925
	/* If we're wrapping, copy the text from the next line, minus
	 * the indentation that we already copied above. */
	strcat(new_line, next_line);

	free(line->next->data);
	line->next->data = new_line;
1926
    } else {
1927
1928
1929
1930
1931
	/* Otherwise, make a new line and copy the text after where we
	 * broke this line to the beginning of the new line. */
	splice_node(current, make_new_node(current), current->next);

	current->next->data = new_line;
1932

1933
	totlines++;
1934
	totsize++;
1935
    }
Chris Allegretta's avatar
Chris Allegretta committed
1936

1937
1938
    /* Step 3, clean up.  Reposition the cursor and mark, and do some
     * other sundry things. */
Chris Allegretta's avatar
Chris Allegretta committed
1939

1940
1941
    /* Set the same_line_wrap flag, so that later wraps of this line
     * will be prepended to the next line. */
1942
    same_line_wrap = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
1943

1944
1945
1946
    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
    if (!wrapping)
1947
	renumber(line);
Chris Allegretta's avatar
Chris Allegretta committed
1948

1949
1950
    /* If the cursor was after the break point, we must move it.  We
     * also clear the same_line_wrap flag in this case. */
1951
    if (current_x > wrap_loc) {
1952
	same_line_wrap = FALSE;
1953
	current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1954
	current_x -= wrap_loc
1955
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1956
		- indent_len
1957
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1958
		;
1959
1960
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1961

1962
#ifndef NANO_SMALL
1963
    /* If the mark was on this line after the wrap point, we move it
1964
1965
1966
1967
     * down.  If it was on the next line and we wrapped onto that line,
     * we move it right. */
    if (mark_beginbuf == line && mark_beginx > wrap_loc) {
	mark_beginbuf = line->next;
1968
	mark_beginx -= wrap_loc - indent_len + 1;
1969
    } else if (wrapping && mark_beginbuf == line->next)
1970
	mark_beginx += after_break_len;
1971
#endif
1972

1973
    return TRUE;
1974
}
1975
#endif /* !DISABLE_WRAPPING */
1976

1977
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1978
/* A word is misspelled in the file.  Let the user replace it.  We
1979
1980
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1981
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1982
    char *save_search, *save_replace;
1983
    size_t match_len;
1984
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1985
    filestruct *edittop_save = edittop, *current_save = current;
1986
	/* Save where we are. */
1987
    bool canceled = FALSE;
1988
	/* The return value. */
1989
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1990
#ifndef NANO_SMALL
1991
    bool backwards_search_set = ISSET(BACKWARDS_SEARCH);
1992
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1993
#ifdef HAVE_REGEX_H
1994
1995
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1996
1997
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1998
1999
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
2000
2001
2002
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
2003
    filestruct *top, *bot;
2004
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2005
#endif
2006

2007
    /* Make sure spell-check is case sensitive. */
2008
    SET(CASE_SENSITIVE);
2009

2010
#ifndef NANO_SMALL
2011
    /* Make sure spell-check goes forward only. */
2012
    UNSET(BACKWARDS_SEARCH);
2013
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2014
#ifdef HAVE_REGEX_H
2015
2016
2017
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
2018

2019
    /* Save the current search/replace strings. */
2020
2021
2022
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
2023

2024
    /* Set the search/replace strings to the misspelled word. */
2025
2026
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
2027

2028
2029
#ifndef NANO_SMALL
    if (old_mark_set) {
2030
	/* If the mark is on, partition the filestruct so that it
2031
2032
2033
	 * contains only the marked text, keep track of whether the text
	 * will have a magicline added when we're done correcting
	 * misspelled words, and turn the mark off. */
2034
	mark_order((const filestruct **)&top, &top_x,
2035
	    (const filestruct **)&bot, &bot_x, &right_side_up);
2036
	filepart = partition_filestruct(top, top_x, bot, bot_x);
2037
	added_magicline = (filebot->data[0] != '\0');
2038
2039
2040
2041
	UNSET(MARK_ISSET);
    }
#endif

2042
    /* Start from the top of the file. */
2043
    edittop = fileage;
2044
    current = fileage;
2045
    current_x = (size_t)-1;
2046
    placewewant = 0;
2047

2048
    /* Find the first whole-word occurrence of word. */
2049
    findnextstr_wrap_reset();
2050
2051
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word,
	&match_len)) {
2052
	if (is_whole_word(current_x, current->data, word)) {
2053
2054
2055
2056
2057
	    size_t xpt = xplustabs();
	    char *exp_word = display_string(current->data, xpt,
		strnlenpt(current->data, current_x + match_len) - xpt,
		FALSE);

2058
	    edit_refresh();
2059

2060
	    do_replace_highlight(TRUE, exp_word);
2061

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2062
	    /* Allow all instances of the word to be corrected. */
2063
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
2064
#ifndef NANO_SMALL
2065
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2066
#endif
2067
			 _("Edit a replacement")) == -1);
2068

2069
2070
2071
	    do_replace_highlight(FALSE, exp_word);

	    free(exp_word);
2072

2073
	    if (!canceled && strcmp(word, answer) != 0) {
2074
		current_x--;
2075
2076
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
2077
	    }
2078
2079

	    break;
2080
	}
2081
    }
Chris Allegretta's avatar
Chris Allegretta committed
2082

2083
2084
#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2085
2086
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2087
2088
	if (added_magicline)
	    remove_magicline();
2089

2090
2091
2092
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2093
	    bot_x += top_x;
2094
	if (right_side_up) {
2095
2096
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2097
	} else {
2098
2099
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2100
	}
2101

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2102
2103
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
2104
	unpartition_filestruct(&filepart);
2105
	SET(MARK_ISSET);
2106
2107
    }
#endif
2108

2109
2110
2111
2112
2113
    /* Restore the search/replace strings. */
    free(last_search);
    last_search = save_search;
    free(last_replace);
    last_replace = save_replace;
Chris Allegretta's avatar
Chris Allegretta committed
2114

2115
    /* Restore where we were. */
2116
    edittop = edittop_save;
2117
2118
    current = current_save;
    current_x = current_x_save;
2119
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
2120

2121
    /* Restore case sensitivity setting. */
2122
2123
2124
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

2125
#ifndef NANO_SMALL
2126
    /* Restore search/replace direction. */
2127
2128
    if (backwards_search_set)
	SET(BACKWARDS_SEARCH);
2129
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2130
#ifdef HAVE_REGEX_H
2131
2132
2133
2134
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
2135

2136
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
2137
2138
}

2139
2140
2141
/* Integrated spell checking using the spell program, filtered through
 * the sort and uniq programs.  Return NULL for normal termination,
 * and the error string otherwise. */
2142
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
2143
{
2144
2145
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
2146
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
2147
2148
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
2149

2150
    /* Create all three pipes up front. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2151
2152
    if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
	pipe(uniq_fd) == -1)
2153
	return _("Could not create pipe");
Chris Allegretta's avatar
Chris Allegretta committed
2154

2155
    statusbar(_("Creating misspelled word list, please wait..."));
2156

2157
    /* A new process to run spell in. */
2158
    if ((pid_spell = fork()) == 0) {
2159
	/* Child continues (i.e, future spell process). */
2160
	close(spell_fd[0]);
2161

2162
	/* Replace the standard input with the temp file. */
2163
2164
2165
2166
2167
2168
	if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
	    goto close_pipes_and_exit;

	if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2169
	close(tempfile_fd);
2170

2171
	/* Send spell's standard output to the pipe. */
2172
2173
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
2174

2175
	close(spell_fd[1]);
2176

2177
	/* Start the spell program; we are using PATH. */
2178
	execlp("spell", "spell", NULL);
2179

2180
	/* This should not be reached if spell is found. */
2181
	exit(1);
2182
    }
Chris Allegretta's avatar
Chris Allegretta committed
2183

2184
    /* Parent continues here. */
2185
2186
    close(spell_fd[1]);

2187
    /* A new process to run sort in. */
2188
    if ((pid_sort = fork()) == 0) {
2189
2190
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
2191
2192
2193
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2194
2195
	close(spell_fd[0]);

2196
	/* Send sort's standard output to the new pipe. */
2197
2198
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2199
2200
2201

	close(sort_fd[1]);

2202
2203
	/* Start the sort program.  Use -f to remove mixed case.  If
	 * this isn't portable, let me know. */
2204
2205
	execlp("sort", "sort", "-f", NULL);

2206
	/* This should not be reached if sort is found. */
2207
2208
2209
	exit(1);
    }

2210
    close(spell_fd[0]);
2211
2212
    close(sort_fd[1]);

2213
    /* A new process to run uniq in. */
2214
    if ((pid_uniq = fork()) == 0) {
2215
2216
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
2217
2218
2219
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2220
2221
	close(sort_fd[0]);

2222
	/* Send uniq's standard output to the new pipe. */
2223
2224
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2225
2226
2227

	close(uniq_fd[1]);

2228
	/* Start the uniq program; we are using PATH. */
2229
2230
	execlp("uniq", "uniq", NULL);

2231
	/* This should not be reached if uniq is found. */
2232
2233
2234
	exit(1);
    }

2235
    close(sort_fd[0]);
2236
    close(uniq_fd[1]);
2237

2238
    /* The child process was not forked successfully. */
2239
2240
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
2241
	return _("Could not fork");
2242
    }
2243

2244
    /* Get the system pipe buffer size. */
2245
2246
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
2247
	return _("Could not get size of pipe buffer");
2248
    }
Chris Allegretta's avatar
Chris Allegretta committed
2249

2250
    /* Read in the returned spelling errors. */
2251
2252
2253
    read_buff_read = 0;
    read_buff_size = pipe_buff_size + 1;
    read_buff = read_buff_ptr = charalloc(read_buff_size);
Chris Allegretta's avatar
Chris Allegretta committed
2254

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2255
2256
    while ((bytesread = read(uniq_fd[0], read_buff_ptr,
	pipe_buff_size)) > 0) {
2257
2258
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2259
2260
	read_buff = read_buff_ptr = charealloc(read_buff,
		read_buff_size);
2261
2262
	read_buff_ptr += read_buff_read;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2263

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2264
    *read_buff_ptr = '\0';
2265
    close(uniq_fd[0]);
2266

2267
    /* Process the spelling errors. */
2268
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
2269

2270
    while (*read_buff_ptr != '\0') {
2271
	if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2272
	    *read_buff_ptr = '\0';
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
	    if (read_buff_word != read_buff_ptr) {
		if (!do_int_spell_fix(read_buff_word)) {
		    read_buff_word = read_buff_ptr;
		    break;
		}
	    }
	    read_buff_word = read_buff_ptr + 1;
	}
	read_buff_ptr++;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2283

2284
    /* Special case: the last word doesn't end with '\r' or '\n'. */
2285
2286
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
2287

2288
2289
    free(read_buff);
    replace_abort();
2290
    edit_refresh();
2291

2292
    /* Process the end of the spell process. */
2293
2294
2295
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
2296

2297
2298
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
2299

2300
2301
2302
2303
2304
2305
2306
2307
    if (WIFEXITED(sort_status)  == 0 || WEXITSTATUS(sort_status))
	return _("Error invoking \"sort -f\"");

    if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
	return _("Error invoking \"uniq\"");

    /* Otherwise... */
    return NULL;
2308

2309
2310
  close_pipes_and_exit:
    /* Don't leak any handles. */
2311
2312
2313
2314
2315
2316
2317
2318
    close(tempfile_fd);
    close(spell_fd[0]);
    close(spell_fd[1]);
    close(sort_fd[0]);
    close(sort_fd[1]);
    close(uniq_fd[0]);
    close(uniq_fd[1]);
    exit(1);
Chris Allegretta's avatar
Chris Allegretta committed
2319
2320
}

2321
2322
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
2323
const char *do_alt_speller(char *tempfile_name)
2324
{
2325
2326
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
2327
    ssize_t current_y_save = current_y;
2328
2329
2330
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
2331
2332
    static char **spellargs = NULL;
    FILE *f;
2333
#ifndef NANO_SMALL
2334
    bool old_mark_set = ISSET(MARK_ISSET);
2335
2336
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
2337
2338
2339
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
2340
2341
    filestruct *top, *bot;
    size_t top_x, bot_x;
2342
    ssize_t mbb_lineno_save = 0;
2343
	/* We're going to close the current file, and open the output of
2344
2345
2346
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
2347
    size_t totsize_save = totsize;
2348
2349
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
2350

2351
    if (old_mark_set) {
2352
2353
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
2354
	mbb_lineno_save = mark_beginbuf->lineno;
2355
2356
	UNSET(MARK_ISSET);
    }
2357
#endif
2358

2359
    endwin();
2360

2361
    /* Set up an argument list to pass execvp(). */
2362
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2363
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
2364

2365
2366
2367
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2368
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
2369
2370
2371
2372
2373
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
2374

2375
    /* Start a new process for the alternate speller. */
2376
    if ((pid_spell = fork()) == 0) {
2377
	/* Start alternate spell program; we are using PATH. */
2378
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
2379

2380
2381
2382
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
2383

2384
2385
    /* Could not fork?? */
    if (pid_spell < 0)
2386
	return _("Could not fork");
2387

2388
    /* Wait for alternate speller to complete. */
2389
    wait(&alt_spell_status);
2390

2391
2392
2393
2394
2395
2396
2397
2398
    refresh();

    /* Restore the terminal to its previous state. */
    terminal_init();

    /* Turn the cursor back on for sure. */
    curs_set(1);

2399
2400
    if (!WIFEXITED(alt_spell_status) ||
		WEXITSTATUS(alt_spell_status) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2401
	char *altspell_error;
2402
2403
	char *invoke_error = _("Could not invoke \"%s\"");

2404
2405
2406
2407
2408
#ifndef NANO_SMALL
	/* Turn the mark back on if it was on before. */
	if (old_mark_set)
	    SET(MARK_ISSET);
#endif
2409

2410
2411
2412
	altspell_error =
		charalloc(strlen(invoke_error) +
		strlen(alt_speller) + 2);
2413
	sprintf(altspell_error, invoke_error, alt_speller);
2414
2415
	return altspell_error;
    }
2416

2417
#ifndef NANO_SMALL
2418
    if (old_mark_set) {
2419
	size_t part_totsize;
2420
2421
2422
2423
2424
2425

	/* If the mark was on, partition the filestruct so that it
	 * contains only the marked text, and keep track of whether the
	 * temp file (which should contain the spell-checked marked
	 * text) will have a magicline added when it's reloaded. */
	mark_order((const filestruct **)&top, &top_x,
2426
		(const filestruct **)&bot, &bot_x, &right_side_up);
2427
2428
2429
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	added_magicline = (filebot->data[0] != '\0');

2430
2431
2432
2433
	/* Get the number of characters in the marked text, and subtract
	 * it from the saved value of totsize.  Note that we don't need
	 * to save totlines. */
	get_totals(top, bot, NULL, &part_totsize);
2434
	totsize_save -= part_totsize;
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
    }
#endif

    /* Reinitialize the filestruct. */
    free_filestruct(fileage);
    global_init(TRUE);

    /* Reload the temp file.  Do what load_buffer() would do, except for
     * making a new buffer for the temp file if multibuffer support is
     * available. */
    open_file(tempfile_name, FALSE, &f);
    read_file(f, tempfile_name);
    current = fileage;

#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2451
	filestruct *top_save = fileage;
2452

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2453
2454
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2455
2456
2457
	if (added_magicline)
	    remove_magicline();

2458
2459
2460
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2461
	    bot_x += top_x;
2462
	if (right_side_up) {
2463
2464
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2465
	} else {
2466
2467
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2468
	}
2469

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2470
2471
2472
2473
	/* Unpartition the filestruct so that it contains all the text
	 * again.  Note that we've replaced the marked text originally
	 * in the partition with the spell-checked marked text in the
	 * temp file. */
2474
	unpartition_filestruct(&filepart);
2475
2476

	/* Renumber starting with the beginning line of the old
2477
2478
2479
2480
	 * partition.  Also set totlines to the new number of lines in
	 * the file, add the number of characters in the spell-checked
	 * marked text to the saved value of totsize, and then make that
	 * saved value the actual value. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2481
	renumber(top_save);
2482
	totlines = filebot->lineno;
2483
2484
	totsize_save += totsize;
	totsize = totsize_save;
2485
2486
2487

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2488
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2489
	mark_beginbuf = current;
2490
2491
2492
2493

	/* Assign mark_beginx to the location in mark_beginbuf where the
	 * mark began before, adjusted for any shortening of the
	 * line. */
2494
	mark_beginx = current_x;
2495

2496
2497
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2498
2499
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2500

2501
2502
    /* Go back to the old position, mark the file as modified, and
     * update the titlebar. */
2503
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2504
    SET(MODIFIED);
2505
    titlebar(NULL);
2506

2507
    return NULL;
2508
}
2509

2510
void do_spell(void)
2511
{
2512
    int i;
2513
2514
    FILE *temp_file;
    char *temp = safe_tempfile(&temp_file);
2515
    const char *spell_msg;
2516

2517
2518
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2519
	return;
2520
    }
2521

2522
2523
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2524
	i = write_marked(temp, temp_file, TRUE, FALSE);
2525
2526
    else
#endif
2527
	i = write_file(temp, temp_file, TRUE, FALSE, FALSE);
2528
2529

    if (i == -1) {
2530
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2531
	free(temp);
2532
	return;
2533
2534
    }

2535
#ifdef ENABLE_MULTIBUFFER
2536
2537
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2538
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2539
#endif
2540

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2541
    spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
2542
	do_int_speller(temp);
2543
    unlink(temp);
2544
    free(temp);
2545

2546
    /* If the spell-checker printed any error messages onscreen, make
2547
     * sure that they're cleared off. */
2548
    total_refresh();
2549

2550
    if (spell_msg != NULL)
2551
2552
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2553
    else
2554
	statusbar(_("Finished checking spelling"));
2555
}
2556
#endif /* !DISABLE_SPELLER */
2557

2558
#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING)
2559
2560
/* We are trying to break a chunk off line.  We find the last blank such
 * that the display length to there is at most goal + 1.  If there is no
2561
2562
2563
2564
 * such blank, then we find the first blank.  We then take the last
 * blank in that group of blanks.  The terminating '\0' counts as a
 * blank, as does a '\n' if newline is TRUE. */
ssize_t break_line(const char *line, ssize_t goal, bool newline)
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
{
    ssize_t blank_loc = -1;
	/* Current tentative return value.  Index of the last blank we
	 * found with short enough display width.  */
    ssize_t cur_loc = 0;
	/* Current index in line. */
    int line_len;

    assert(line != NULL);

    while (*line != '\0' && goal >= 0) {
	size_t pos = 0;

	line_len = parse_mbchar(line, NULL, NULL, &pos);

	if (is_blank_mbchar(line) || (newline && *line == '\n')) {
	    blank_loc = cur_loc;

	    if (newline && *line == '\n')
		break;
	}

	goal -= pos;
	line += line_len;
	cur_loc += line_len;
    }

    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;

    if (blank_loc == -1) {
	/* No blank was found that was short enough. */
2598
	bool found_blank = FALSE;
2599

2600
2601
	while (*line != '\0') {
	    line_len = parse_mbchar(line, NULL, NULL, NULL);
2602

2603
2604
2605
2606
2607
	    if (is_blank_mbchar(line) || (newline && *line == '\n')) {
		if (!found_blank)
		    found_blank = TRUE;
	    } else if (found_blank)
		return cur_loc - line_len;
2608

2609
2610
	    line += line_len;
	    cur_loc += line_len;
2611
	}
2612
2613

	return -1;
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
    }

    /* Move to the last blank after blank_loc, if there is one. */
    line -= cur_loc;
    line += blank_loc;
    line_len = parse_mbchar(line, NULL, NULL, NULL);
    line += line_len;

    while (*line != '\0' && (is_blank_mbchar(line) ||
	(newline && *line == '\n'))) {
	line_len = parse_mbchar(line, NULL, NULL, NULL);

	line += line_len;
	blank_loc += line_len;
    }

    return blank_loc;
}
2632
#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */
2633

2634
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2635
2636
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2637
2638
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2639
    size_t len = 0;
2640
2641
    char *blank_mb;
    int blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2642

Chris Allegretta's avatar
Chris Allegretta committed
2643
    assert(line != NULL);
2644
2645
2646
2647

    blank_mb = charalloc(mb_cur_max());

    while (*line != '\0') {
2648
	blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL);
2649
2650
2651
2652
2653
2654

	if (!is_blank_mbchar(blank_mb))
	    break;

	line += blank_mb_len;
	len += blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2655
    }
2656
2657
2658

    free(blank_mb);

Chris Allegretta's avatar
Chris Allegretta committed
2659
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2660
}
2661
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2662

Chris Allegretta's avatar
Chris Allegretta committed
2663
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2664
/* justify_format() replaces blanks with spaces and multiple spaces by 1
2665
2666
 * (except it maintains up to 2 after a character in punct optionally
 * followed by a character in brackets, and removes all from the end).
Chris Allegretta's avatar
Chris Allegretta committed
2667
 *
2668
2669
 * justify_format() might make paragraph->data shorter, and change the
 * actual pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2670
 *
2671
2672
2673
 * justify_format() will not look at the first skip characters of
 * paragraph.  skip should be at most strlen(paragraph->data).  The
 * character at paragraph[skip + 1] must not be blank. */
2674
void justify_format(filestruct *paragraph, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2675
{
2676
2677
2678
2679
2680
    char *end, *new_end, *new_paragraph_data;
    size_t shift = 0;
#ifndef NANO_SMALL
    size_t mark_shift = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2681
2682

    /* These four asserts are assumptions about the input data. */
2683
2684
2685
    assert(paragraph != NULL);
    assert(paragraph->data != NULL);
    assert(skip < strlen(paragraph->data));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2686
    assert(!is_blank_mbchar(paragraph->data + skip));
2687
2688
2689

    end = paragraph->data + skip;
    new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
2690
    strncpy(new_paragraph_data, paragraph->data, skip);
2691
2692
2693
    new_end = new_paragraph_data + skip;

    while (*end != '\0') {
2694
2695
	int end_len;

2696
2697
	/* If this character is blank, make sure that it's a space with
	 * no blanks after it. */
2698
2699
2700
	if (is_blank_mbchar(end)) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);

2701
2702
	    *new_end = ' ';
	    new_end++;
2703
	    end += end_len;
2704

2705
2706
2707
2708
2709
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2710

2711
#ifndef NANO_SMALL
2712
		/* Keep track of the change in the current line. */
2713
2714
		if (mark_beginbuf == paragraph &&
			mark_beginx >= end - paragraph->data)
2715
		    mark_shift += end_len;
2716
#endif
2717
	    }
2718
2719
2720
2721
	/* If this character is punctuation optionally followed by a
	 * bracket and then followed by blanks, make sure there are no
	 * more than two blanks after it, and make sure that the blanks
	 * are spaces. */
2722
2723
	} else if (mbstrchr(punct, end) != NULL) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);
2724

2725
	    while (end_len > 0) {
2726
2727
2728
		*new_end = *end;
		new_end++;
		end++;
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
		end_len--;
	    }

	    if (*end != '\0' && mbstrchr(brackets, end) != NULL) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		while (end_len > 0) {
		    *new_end = *end;
		    new_end++;
		    end++;
		    end_len--;
		}
2741
	    }
2742

2743
2744
2745
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2746
2747
		*new_end = ' ';
		new_end++;
2748
		end += end_len;
2749
	    }
2750

2751
2752
2753
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2754
2755
		*new_end = ' ';
		new_end++;
2756
		end += end_len;
2757
	    }
2758

2759
2760
2761
2762
2763
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2764

Chris Allegretta's avatar
Chris Allegretta committed
2765
#ifndef NANO_SMALL
2766
2767
2768
2769
		/* Keep track of the change in the current line. */
		if (mark_beginbuf == paragraph &&
			mark_beginx >= end - paragraph->data)
		    mark_shift += end_len;
Chris Allegretta's avatar
Chris Allegretta committed
2770
#endif
2771
	    }
2772
2773
	/* If this character is neither blank nor punctuation, leave it
	 * alone. */
Chris Allegretta's avatar
Chris Allegretta committed
2774
	} else {
2775
2776
2777
2778
2779
2780
2781
2782
	    end_len = parse_mbchar(end, NULL, NULL, NULL);

	    while (end_len > 0) {
		*new_end = *end;
		new_end++;
		end++;
		end_len--;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2783
	}
Chris Allegretta's avatar
Chris Allegretta committed
2784
2785
    }

2786
2787
2788
2789
    assert(*end == '\0');

    *new_end = *end;

2790
    /* Make sure that there are no spaces at the end of the line. */
2791
2792
2793
2794
2795
    while (new_end > new_paragraph_data + skip &&
	*(new_end - 1) == ' ') {
	new_end--;
	shift++;
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2796

2797
2798
2799
2800
2801
    if (shift > 0) {
	totsize -= shift;
	null_at(&new_paragraph_data, new_end - new_paragraph_data);
	free(paragraph->data);
	paragraph->data = new_paragraph_data;
Chris Allegretta's avatar
Chris Allegretta committed
2802

Chris Allegretta's avatar
Chris Allegretta committed
2803
#ifndef NANO_SMALL
2804
2805
2806
2807
2808
2809
2810
	/* Adjust the mark coordinates to compensate for the change in
	 * the current line. */
	if (mark_beginbuf == paragraph) {
	    mark_beginx -= mark_shift;
	    if (mark_beginx > new_end - new_paragraph_data)
		mark_beginx = new_end - new_paragraph_data;
	}
Chris Allegretta's avatar
Chris Allegretta committed
2811
#endif
2812
2813
    } else
	free(new_paragraph_data);
Chris Allegretta's avatar
Chris Allegretta committed
2814
}
Chris Allegretta's avatar
Chris Allegretta committed
2815

Chris Allegretta's avatar
Chris Allegretta committed
2816
2817
2818
2819
2820
2821
/* The "quote part" of a line is the largest initial substring matching
 * the quote string.  This function returns the length of the quote part
 * of the given line.
 *
 * Note that if !HAVE_REGEX_H then we match concatenated copies of
 * quotestr. */
2822
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2823
{
2824
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2825
    regmatch_t matches;
2826
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2827

2828
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2829
	return 0;
2830
2831
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2832
2833
2834
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2835

2836
    /* Compute quote depth level. */
2837
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2838
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2839
2840
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2841
}
Chris Allegretta's avatar
Chris Allegretta committed
2842

Chris Allegretta's avatar
Chris Allegretta committed
2843
2844
2845
/* a_line and b_line are lines of text.  The quotation part of a_line is
 * the first a_quote characters.  Check that the quotation part of
 * b_line is the same. */
2846
2847
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2848
{
2849
    /* Here is the assumption about a_quote. */
2850
    assert(a_quote == quote_length(a_line));
2851

2852
2853
    return (a_quote == quote_length(b_line) &&
	strncmp(a_line, b_line, a_quote) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2854
}
2855

2856
2857
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2858
bool indents_match(const char *a_line, size_t a_indent, const char
2859
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2860
{
Chris Allegretta's avatar
Chris Allegretta committed
2861
2862
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2863

2864
2865
    return (b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2866
}
Chris Allegretta's avatar
Chris Allegretta committed
2867

2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
/* Is foo the beginning of a paragraph?
 *
 *   A line of text consists of a "quote part", followed by an
 *   "indentation part", followed by text.  The functions quote_length()
 *   and indent_length() calculate these parts.
 *
 *   A line is "part of a paragraph" if it has a part not in the quote
 *   part or the indentation.
 *
 *   A line is "the beginning of a paragraph" if it is part of a
 *   paragraph and
 *	1) it is the top line of the file, or
 *	2) the line above it is not part of a paragraph, or
 *	3) the line above it does not have precisely the same quote
 *	   part, or
 *	4) the indentation of this line is not an initial substring of
 *	   the indentation of the previous line, or
 *	5) this line has no quote part and some indentation, and
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2886
2887
2888
2889
2890
 *	   autoindent isn't turned on.
 *   The reason for number 5) is that if autoindent isn't turned on,
 *   then an indented line is expected to start a paragraph, as in
 *   books.  Thus, nano can justify an indented paragraph only if
 *   autoindent is turned on. */
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
bool begpar(const filestruct *const foo)
{
    size_t quote_len;
    size_t indent_len;
    size_t temp_id_len;

    /* Case 1). */
    if (foo->prev == NULL)
	return TRUE;

    quote_len = quote_length(foo->data);
    indent_len = indent_length(foo->data + quote_len);

    /* Not part of a paragraph. */
    if (foo->data[quote_len + indent_len] == '\0')
	return FALSE;

    /* Case 3). */
    if (!quotes_match(foo->data, quote_len, foo->prev->data))
	return TRUE;

    temp_id_len = indent_length(foo->prev->data + quote_len);

    /* Case 2) or 5) or 4). */
    if (foo->prev->data[quote_len + temp_id_len] == '\0' ||
	(quote_len == 0 && indent_len > 0
#ifndef NANO_SMALL
	&& !ISSET(AUTOINDENT)
#endif
	) || !indents_match(foo->prev->data + quote_len, temp_id_len,
	foo->data + quote_len, indent_len))
	return TRUE;

    return FALSE;
}

/* We find the last beginning-of-paragraph line before the current
 * line. */
2929
void do_para_begin(bool allow_update)
2930
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2931
2932
    const filestruct *current_save = current;
    const size_t pww_save = placewewant;
2933
2934
2935
2936
2937
2938
2939

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2940
	    current_y--;
2941
2942
2943
	} while (!begpar(current));
    }

2944
2945
    if (allow_update)
	edit_redraw(current_save, pww_save);
2946
2947
}

2948
void do_para_begin_void(void)
2949
{
2950
2951
2952
2953
2954
2955
2956
    do_para_begin(TRUE);
}

/* Is foo inside a paragraph? */
bool inpar(const filestruct *const foo)
{
    size_t quote_len;
2957

2958
2959
2960
2961
2962
2963
2964
    if (foo == NULL)
	return FALSE;

    quote_len = quote_length(foo->data);

    return foo->data[quote_len + indent_length(foo->data +
	quote_len)] != '\0';
2965
2966
2967
2968
2969
}

/* A line is the last line of a paragraph if it is in a paragraph, and
 * the next line isn't, or is the beginning of a paragraph.  We move
 * down to the end of a paragraph, then one line farther. */
2970
void do_para_end(bool allow_update)
2971
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2972
2973
    const filestruct *const current_save = current;
    const size_t pww_save = placewewant;
2974
2975
2976
2977

    current_x = 0;
    placewewant = 0;

2978
    while (current->next != NULL && !inpar(current))
2979
2980
	current = current->next;

2981
    while (current->next != NULL && inpar(current->next) &&
2982
	    !begpar(current->next)) {
2983
	current = current->next;
2984
2985
	current_y++;
    }
2986
2987
2988
2989

    if (current->next != NULL)
	current = current->next;

2990
2991
2992
2993
2994
2995
2996
    if (allow_update)
	edit_redraw(current_save, pww_save);
}

void do_para_end_void(void)
{
    do_para_end(TRUE);
2997
2998
}

2999
3000
3001
3002
/* Put the next par_len lines, starting with first_line, into the
 * justify buffer, leaving copies of those lines in place.  Assume there
 * are enough lines after first_line.  Return the new copy of
 * first_line. */
3003
3004
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3005
{
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
    filestruct *top = first_line;
	/* The top of the paragraph we're backing up. */
    filestruct *bot = first_line;
	/* The bottom of the paragraph we're backing up. */
    size_t i;
	/* Generic loop variable. */
    size_t current_x_save = current_x;
    int fl_lineno_save = first_line->lineno;
    int edittop_lineno_save = edittop->lineno;
    int current_lineno_save = current->lineno;
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
    int mbb_lineno_save = 0;
3019
    size_t mark_beginx_save = 0;
Chris Allegretta's avatar
Chris Allegretta committed
3020

3021
    if (old_mark_set) {
3022
	mbb_lineno_save = mark_beginbuf->lineno;
3023
3024
	mark_beginx_save = mark_beginx;
    }
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
#endif

    /* Move bot down par_len lines to the newline after the last line of
     * the paragraph. */
    for (i = par_len; i > 0; i--)
	bot = bot->next;

    /* Move the paragraph from the main filestruct to the justify
     * buffer. */
    move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0);

    /* Copy the paragraph from the justify buffer to the main
     * filestruct. */
    copy_from_filestruct(jusbuffer, jusbottom);
Chris Allegretta's avatar
Chris Allegretta committed
3039

3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
    /* Move upward from the last line of the paragraph to the first
     * line, putting first_line, edittop, current, and mark_beginbuf at
     * the same lines in the copied paragraph that they had in the
     * original paragraph. */
    top = current->prev;
    for (i = par_len; i > 0; i--) {
	if (top->lineno == fl_lineno_save)
	    first_line = top;
	if (top->lineno == edittop_lineno_save)
	    edittop = top;
	if (top->lineno == current_lineno_save)
	    current = top;
Chris Allegretta's avatar
Chris Allegretta committed
3052
#ifndef NANO_SMALL
3053
	if (old_mark_set && top->lineno == mbb_lineno_save) {
3054
	    mark_beginbuf = top;
3055
3056
	    mark_beginx = mark_beginx_save;
	}
Chris Allegretta's avatar
Chris Allegretta committed
3057
#endif
3058
	top = top->prev;
Chris Allegretta's avatar
Chris Allegretta committed
3059
    }
3060
3061
3062
3063
3064
3065
3066

    /* Put current_x at the same place in the copied paragraph that it
     * had in the original paragraph. */
    current_x = current_x_save;

    set_modified();

Chris Allegretta's avatar
Chris Allegretta committed
3067
3068
3069
    return first_line;
}

3070
3071
/* Find the beginning of the current paragraph if we're in one, or the
 * beginning of the next paragraph if we're not.  Afterwards, save the
3072
3073
 * quote length and paragraph length in *quote and *par.  Return TRUE if
 * we found a paragraph, or FALSE if there was an error or we didn't
3074
 * find a paragraph.
Chris Allegretta's avatar
Chris Allegretta committed
3075
 *
3076
3077
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
3078
bool find_paragraph(size_t *const quote, size_t *const par)
3079
{
Chris Allegretta's avatar
Chris Allegretta committed
3080
    size_t quote_len;
3081
3082
	/* Length of the initial quotation of the paragraph we search
	 * for. */
Chris Allegretta's avatar
Chris Allegretta committed
3083
    size_t par_len;
3084
3085
3086
	/* Number of lines in the paragraph we search for. */
    filestruct *current_save;
	/* The line at the beginning of the paragraph we search for. */
3087
    ssize_t current_y_save;
3088
3089
	/* The y-coordinate at the beginning of the paragraph we search
	 * for. */
3090

Chris Allegretta's avatar
Chris Allegretta committed
3091
#ifdef HAVE_REGEX_H
3092
3093
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
3094
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3095
    }
Chris Allegretta's avatar
Chris Allegretta committed
3096
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3097

Chris Allegretta's avatar
Chris Allegretta committed
3098
    assert(current != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
3099

3100
    /* Move back to the beginning of the current line. */
3101
    current_x = 0;
3102
    placewewant = 0;
3103

3104
3105
    /* Find the first line of the current or next paragraph.  First, if
     * the current line isn't in a paragraph, move forward to the line
3106
3107
3108
3109
3110
3111
     * after the last line of the next paragraph.  If we end up on the
     * same line, or the line before that isn't in a paragraph, it means
     * that there aren't any paragraphs left, so get out.  Otherwise,
     * move back to the last line of the paragraph.  If the current line
     * is in a paragraph and it isn't the first line of that paragraph,
     * move back to the first line. */
3112
    if (!inpar(current)) {
3113
3114
	filestruct *current_save = current;

3115
	do_para_end(FALSE);
3116
	if (current == current_save || !inpar(current->prev))
3117
	    return FALSE;
3118
3119
	if (current->prev != NULL)
	    current = current->prev;
3120
    }
3121
3122
    if (!begpar(current))
	do_para_begin(FALSE);
3123

3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
    /* Now current is the first line of the paragraph.  Set quote_len to
     * the quotation length of that line, and set par_len to the number
     * of lines in this paragraph. */
    quote_len = quote_length(current->data);
    current_save = current;
    current_y_save = current_y;
    do_para_end(FALSE);
    par_len = current->lineno - current_save->lineno;
    current = current_save;
    current_y = current_y_save;
3134

3135
3136
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
3137

3138
3139
    *quote = quote_len;
    *par = par_len;
3140

3141
    return TRUE;
3142
3143
}

3144
3145
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
3146
void do_justify(bool full_justify)
3147
{
3148
3149
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
3150
	 * For restoring after unjustify. */
3151
    filestruct *last_par_line;
3152
3153
	/* Will be the line containing the newline after the last line
	 * of the result.  Also for restoring after unjustify. */
3154
3155

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3156
     * unjustifies.  Note that we don't need to save totlines. */
3157
    size_t current_x_save = current_x, pww_save = placewewant;
3158
    ssize_t current_y_save = current_y;
3159
3160
    unsigned long flags_save = flags;
    size_t totsize_save = totsize;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3161
    filestruct *edittop_save = edittop, *current_save = current;
3162
3163
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
3164
    size_t mark_beginx_save = mark_beginx;
3165
#endif
3166
    int kbinput;
3167
    bool meta_key, func_key, s_or_t, ran_func, finished;
3168

3169
3170
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
3171
	current = fileage;
3172
3173

    last_par_line = current;
3174
3175

    while (TRUE) {
3176
3177
	size_t i;
	    /* Generic loop variable. */
3178
3179
3180
	size_t quote_len;
	    /* Length of the initial quotation of the paragraph we
	     * justify. */
3181
3182
3183
	size_t indent_len;
	    /* Length of the initial indentation of the paragraph we
	     * justify. */
3184
	size_t par_len;
3185
3186
3187
	    /* Number of lines in the paragraph we justify. */
	ssize_t break_pos;
	    /* Where we will break lines. */
3188
	char *indent_string;
3189
3190
3191
	    /* The first indentation that doesn't match the initial
	     * indentation of the paragraph we justify.  This is put at
	     * the beginning of every line broken off the first
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3192
3193
3194
3195
3196
	     * justified line of the paragraph.  (Note that this works
	     * because a paragraph can only contain two indentations at
	     * most: the initial one, and a different one starting on a
	     * line after the first.  See the comment at begpar() for
	     * more about when a line is part of a paragraph.) */
3197
3198
3199
3200

	/* Find the first line of the paragraph to be justified.  That
	 * is the start of this paragraph if we're in one, or the start
	 * of the next otherwise.  Save the quote length and paragraph
3201
	 * length (number of lines).  Don't refresh the screen yet,
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
	 * since we'll do that after we justify.
	 *
	 * If the search failed, we do one of two things.  If we're
	 * justifying the whole file, we've found at least one
	 * paragraph, and the search didn't leave us on the last line of
	 * the file, it means that we should justify all the way to the
	 * last line of the file, so set the last line of the text to be
	 * justified to the last line of the file and break out of the
	 * loop.  Otherwise, it means that there are no paragraph(s) to
	 * justify, so refresh the screen and get out. */
3212
	if (!find_paragraph(&quote_len, &par_len)) {
3213
3214
	    if (full_justify && first_par_line != NULL &&
		first_par_line != filebot) {
3215
		last_par_line = filebot;
3216
3217
3218
		break;
	    } else {
		edit_refresh();
3219
		return;
3220
3221
	    }
	}
3222

3223
3224
3225
3226
3227
3228
	/* If we haven't already done it, copy the original paragraph(s)
	 * to the justify buffer. */
	if (first_par_line == NULL)
	    first_par_line = backup_lines(current, full_justify ?
		filebot->lineno - current->lineno : par_len, quote_len);

3229
3230
3231
	/* Initialize indent_string to a blank string. */
	indent_string = mallocstrcpy(NULL, "");

3232
3233
3234
3235
3236
3237
3238
3239
3240
	/* Find the first indentation in the paragraph that doesn't
	 * match the indentation of the first line, and save itin
	 * indent_string.  If all the indentations are the same, save
	 * the indentation of the first line in indent_string. */
	{
	    const filestruct *indent_line = current;
	    bool past_first_line = FALSE;

	    for (i = 0; i < par_len; i++) {
3241
		indent_len = quote_len +
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
			indent_length(indent_line->data + quote_len);

		if (indent_len != strlen(indent_string)) {
		    indent_string = mallocstrncpy(indent_string,
			indent_line->data, indent_len + 1);
		    indent_string[indent_len] = '\0';

		    if (past_first_line)
			break;
		}

		if (indent_line == current)
		    past_first_line = TRUE;

		indent_line = indent_line->next;
	    }
	}

	/* Now tack all the lines of the paragraph together, skipping
	 * the quoting and indentation on all lines after the first. */
3262
3263
3264
3265
3266
3267
	for (i = 0; i < par_len - 1; i++) {
	    filestruct *next_line = current->next;
	    size_t line_len = strlen(current->data);
	    size_t next_line_len = strlen(current->next->data);

	    indent_len = quote_len + indent_length(current->next->data +
3268
		quote_len);
3269

3270
3271
3272
	    next_line_len -= indent_len;
	    totsize -= indent_len;

3273
	    /* We're just about to tack the next line onto this one.  If
3274
	     * this line isn't empty, make sure it ends in a space. */
3275
3276
3277
3278
3279
3280
3281
	    if (line_len > 0 && current->data[line_len - 1] != ' ') {
		line_len++;
		current->data = charealloc(current->data, line_len + 1);
		current->data[line_len - 1] = ' ';
		current->data[line_len] = '\0';
		totsize++;
	    }
3282

3283
3284
3285
3286
3287
3288
3289
	    current->data = charealloc(current->data, line_len +
		next_line_len + 1);
	    strcat(current->data, next_line->data + indent_len);

	    /* Don't destroy edittop! */
	    if (edittop == next_line)
		edittop = current;
3290

Chris Allegretta's avatar
Chris Allegretta committed
3291
#ifndef NANO_SMALL
3292
3293
3294
3295
	    /* Adjust the mark coordinates to compensate for the change
	     * in the next line. */
	    if (mark_beginbuf == next_line) {
		mark_beginbuf = current;
3296
		mark_beginx += line_len - indent_len;
3297
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3298
#endif
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311

	    unlink_node(next_line);
	    delete_node(next_line);

	    /* If we've removed the next line, we need to go through
	     * this line again. */
	    i--;

	    par_len--;
	    totlines--;
	    totsize--;
	}

3312
3313
3314
	/* Call justify_format() on the paragraph, which will remove
	 * excess spaces from it and change all blank characters to
	 * spaces. */
3315
3316
3317
3318
3319
3320
	justify_format(current, quote_len +
		indent_length(current->data + quote_len));

	while (par_len > 0 && strlenpt(current->data) > fill) {
	    size_t line_len = strlen(current->data);

3321
	    indent_len = strlen(indent_string);
3322
3323
3324
3325

	    /* If this line is too long, try to wrap it to the next line
	     * to make it short enough. */
	    break_pos = break_line(current->data + indent_len,
3326
		fill - strnlenpt(current->data, indent_len), FALSE);
3327
3328
3329
3330
3331

	    /* We can't break the line, or don't need to, so get out. */
	    if (break_pos == -1 || break_pos + indent_len == line_len)
		break;

3332
3333
3334
	    /* Move forward to the character after the indentation and
	     * just after the space. */
	    break_pos += indent_len + 1;
3335

3336
	    assert(break_pos <= line_len);
3337

3338
3339
3340
	    /* Make a new line, and copy the text after where we're
	     * going to break this line to the beginning of the new
	     * line. */
3341
3342
	    splice_node(current, make_new_node(current), current->next);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3343
3344
	    /* If this paragraph is non-quoted, and autoindent isn't
	     * turned on, set the indentation length to zero so that the
3345
3346
	     * indentation is treated as part of the line. */
	    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
3347
#ifndef NANO_SMALL
3348
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
3349
#endif
3350
3351
		)
		indent_len = 0;
3352

3353
3354
3355
	    /* Copy the text after where we're going to break the
	     * current line to the next line. */
	    current->next->data = charalloc(indent_len + 1 + line_len -
3356
		break_pos);
3357
	    strncpy(current->next->data, indent_string, indent_len);
3358
	    strcpy(current->next->data + indent_len, current->data +
3359
		break_pos);
Chris Allegretta's avatar
Chris Allegretta committed
3360

3361
3362
	    par_len++;
	    totlines++;
3363
	    totsize += indent_len + 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3364

Chris Allegretta's avatar
Chris Allegretta committed
3365
#ifndef NANO_SMALL
3366
3367
3368
3369
	    /* Adjust the mark coordinates to compensate for the change
	     * in the current line. */
	    if (mark_beginbuf == current && mark_beginx > break_pos) {
		mark_beginbuf = current->next;
3370
		mark_beginx -= break_pos - indent_len;
3371
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3372
#endif
3373

3374
3375
	    /* Break the current line. */
	    null_at(&current->data, break_pos);
3376
3377
3378
3379
3380

	    /* Go to the next line. */
	    par_len--;
	    current_y++;
	    current = current->next;
3381
3382
	}

3383
3384
3385
3386
	/* We're done breaking lines, so we don't need indent_string
	 * anymore. */
	free(indent_string);

3387
3388
3389
3390
3391
	/* Go to the next line, the line after the last line of the
	 * paragraph. */
	current_y++;
	current = current->next;

3392
3393
3394
	/* We've just justified a paragraph. If we're not justifying the
	 * entire file, break out of the loop.  Otherwise, continue the
	 * loop so that we justify all the paragraphs in the file. */
3395
3396
	if (!full_justify)
	    break;
3397
    }
3398

3399
3400
3401
    /* We are now done justifying the paragraph or the file, so clean
     * up.  totlines, totsize, and current_y have been maintained above.
     * Set last_par_line to the new end of the paragraph, update
3402
3403
3404
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
3405
    last_par_line = current;
3406
3407
3408
3409
3410
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
3411

3412
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3413

3414
    statusbar(_("Can now UnJustify!"));
3415

3416
    /* Display the shortcut list with UnJustify. */
3417
    shortcut_init(TRUE);
3418
    display_main_list();
3419

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3420
3421
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
3422
3423
    kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
	&finished, FALSE);
3424

3425
3426
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
3427
	/* Restore the justify we just did (ungrateful user!). */
Chris Allegretta's avatar
Chris Allegretta committed
3428
3429
	current = current_save;
	current_x = current_x_save;
3430
	placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
3431
3432
3433
	current_y = current_y_save;
	edittop = edittop_save;

3434
	/* Splice the justify buffer back into the file, but only if we
3435
3436
	 * actually justified something. */
	if (first_par_line != NULL) {
3437
	    filestruct *bot_save;
3438

3439
3440
3441
3442
	    /* Partition the filestruct so that it contains only the
	     * text of the justified paragraph. */
	    filepart = partition_filestruct(first_par_line, 0,
		last_par_line, 0);
3443

3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
	    /* Remove the text of the justified paragraph, and
	     * put the text in the justify buffer in its place. */
	    free_filestruct(fileage);
	    fileage = jusbuffer;
	    filebot = jusbottom;

	    bot_save = filebot;

	    /* Unpartition the filestruct so that it contains all the
	     * text again.  Note that the justified paragraph has been
	     * replaced with the unjustified paragraph. */
	    unpartition_filestruct(&filepart);

	     /* Renumber starting with the ending line of the old
	      * partition. */
	    if (bot_save->next != NULL)
		renumber(bot_save->next);

	    /* Restore global variables from before the justify. */
	    totsize = totsize_save;
	    totlines = filebot->lineno;
Chris Allegretta's avatar
Chris Allegretta committed
3465
#ifndef NANO_SMALL
3466
3467
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
3468
#endif
3469
3470
3471
3472
3473
3474
3475
3476
3477
	    flags = flags_save;

	    /* Clear the justify buffer. */
	    jusbuffer = NULL;

	    if (!ISSET(MODIFIED))
		titlebar(NULL);
	    edit_refresh();
	}
3478
    } else {
3479
	unget_kbinput(kbinput, meta_key, func_key);
3480

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3481
	/* Blow away the text in the justify buffer. */
3482
3483
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
3484
    }
3485

3486
    blank_statusbar();
3487

3488
    /* Display the shortcut list with UnCut. */
3489
    shortcut_init(FALSE);
3490
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
3491
}
3492

3493
void do_justify_void(void)
3494
{
3495
    do_justify(FALSE);
3496
3497
}

3498
void do_full_justify(void)
3499
{
3500
    do_justify(TRUE);
3501
}
3502
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
3503

3504
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
3505
{
3506
3507
    int i;

3508
3509
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
3510
    else if (ISSET(TEMP_FILE))
3511
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3512
    else
3513
3514
3515
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

3516
3517
#ifdef DEBUG
    dump_buffer(fileage);
3518
#endif
3519

3520
    if (i == 0 || (i == 1 && do_writeout(TRUE) == 0)) {
3521
#ifdef ENABLE_MULTIBUFFER
3522
	/* Exit only if there are no more open file buffers. */
3523
	if (!close_open_file())
3524
#endif
3525
	    finish();
3526
    } else if (i != 1)
3527
3528
3529
3530
3531
3532
3533
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
3534
3535
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
3536
3537
3538
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
3539
    sigaction(SIGQUIT, &act, NULL);
3540

3541
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
3542
    act.sa_handler = handle_hupterm;
3543
    sigaction(SIGHUP, &act, NULL);
3544
    sigaction(SIGTERM, &act, NULL);
3545

3546
#ifndef NANO_SMALL
3547
    /* Trap SIGWINCH because we want to handle window resizes. */
3548
3549
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
3550
    allow_pending_sigwinch(FALSE);
3551
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3552

3553
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
3554
3555
3556
3557
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
3558
3559
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
3560
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
3561

3562
3563
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
3564

3565
3566
3567
3568
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
3569

3570
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
3571
void handle_hupterm(int signal)
3572
{
3573
    die(_("Received SIGHUP or SIGTERM\n"));
3574
}
3575

3576
/* Handler for SIGTSTP (suspend). */
3577
void do_suspend(int signal)
3578
3579
{
    endwin();
3580
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
3581
    fflush(stdout);
3582

3583
    /* Restore the old terminal settings. */
3584
    tcsetattr(0, TCSANOW, &oldterm);
3585

3586
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3587
     * suspended. */
3588
3589
3590
3591
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3592
    /* Do what mutt does: send ourselves a SIGSTOP. */
3593
3594
    kill(0, SIGSTOP);
}
3595

3596
/* Handler for SIGCONT (continue after suspend). */
3597
void do_cont(int signal)
3598
{
3599
#ifndef NANO_SMALL
3600
3601
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3602
    handle_sigwinch(0);
3603
#else
3604
3605
    /* Just update the screen. */
    doupdate();
3606
3607
3608
3609
3610
3611
3612
#endif
}

#ifndef NANO_SMALL
void handle_sigwinch(int s)
{
    const char *tty = ttyname(0);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3613
    int fd, result = 0;
3614
3615
    struct winsize win;

3616
    if (tty == NULL)
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
	return;
    fd = open(tty, O_RDWR);
    if (fd == -1)
	return;
    result = ioctl(fd, TIOCGWINSZ, &win);
    close(fd);
    if (result == -1)
	return;

    /* Could check whether the COLS or LINES changed, and return
     * otherwise.  EXCEPT, that COLS and LINES are ncurses global
     * variables, and in some cases ncurses has already updated them. 
     * But not in all cases, argh. */
    COLS = win.ws_col;
    LINES = win.ws_row;

3633
3634
    check_die_too_small();
    resize_variables();
3635

3636
3637
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
3638
	unpartition_filestruct(&filepart);
3639

3640
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3641
3642
    /* If the justify buffer isn't empty, blow away the text in it and
     * display the shortcut list with UnCut. */
3643
3644
3645
3646
3647
3648
3649
    if (jusbuffer != NULL) {
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
	shortcut_init(FALSE);
    }
#endif

3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
#ifdef USE_SLANG
    /* Slang curses emulation brain damage, part 1: If we just do what
     * curses does here, it'll only work properly if the resize made the
     * window smaller.  Do what mutt does: Leave and immediately reenter
     * Slang screen management mode. */
    SLsmg_reset_smg();
    SLsmg_init_smg();
#else
    /* Do the equivalent of what Minimum Profit does: Leave and
     * immediately reenter curses mode. */
    endwin();
    refresh();
#endif
3663

3664
3665
3666
    /* Restore the terminal to its previous state. */
    terminal_init();

3667
3668
3669
    /* Turn the cursor back on for sure. */
    curs_set(1);

3670
3671
3672
3673
3674
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

3675
    /* Redraw the contents of the windows that need it. */
3676
    blank_statusbar();
3677
    currshortcut = main_list;
3678
3679
    total_refresh();

3680
3681
3682
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3683
    /* Jump back to the main loop. */
3684
3685
    siglongjmp(jmpbuf, 1);
}
3686

3687
void allow_pending_sigwinch(bool allow)
3688
3689
3690
3691
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
3692
    sigprocmask(allow ? SIG_UNBLOCK : SIG_BLOCK, &winch, NULL);
3693
}
3694
#endif /* !NANO_SMALL */
3695

3696
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3697
void do_toggle(const toggle *which)
3698
{
3699
    bool enabled;
3700

3701
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3702

3703
    switch (which->val) {
3704
#ifndef DISABLE_MOUSE
3705
3706
3707
	case TOGGLE_MOUSE_KEY:
	    mouse_init();
	    break;
3708
#endif
3709
	case TOGGLE_MORESPACE_KEY:
3710
3711
	case TOGGLE_NOHELP_KEY:
	    window_init();
3712
	    total_refresh();
3713
	    break;
3714
3715
	case TOGGLE_SUSPEND_KEY:
	    signal_init();
3716
	    break;
3717
#ifdef ENABLE_NANORC
3718
	case TOGGLE_WHITESPACE_KEY:
3719
	    titlebar(NULL);
3720
3721
	    edit_refresh();
	    break;
3722
3723
3724
3725
3726
#endif
#ifdef ENABLE_COLOR
	case TOGGLE_SYNTAX_KEY:
	    edit_refresh();
	    break;
3727
#endif
3728
    }
Chris Allegretta's avatar
Chris Allegretta committed
3729

Chris Allegretta's avatar
Chris Allegretta committed
3730
    enabled = ISSET(which->flag);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3731

3732
3733
3734
3735
3736
3737
3738
3739
    if (which->val == TOGGLE_NOHELP_KEY
#ifndef DISABLE_WRAPPING
	|| which->val == TOGGLE_WRAP_KEY
#endif
#ifdef ENABLE_COLOR
	|| which->val == TOGGLE_SYNTAX_KEY
#endif
	)
Chris Allegretta's avatar
Chris Allegretta committed
3740
	enabled = !enabled;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3741
3742
3743

    statusbar("%s %s", which->desc, enabled ? _("enabled") :
	_("disabled"));
Chris Allegretta's avatar
Chris Allegretta committed
3744
}
3745
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3746

3747
void disable_extended_io(void)
3748
3749
3750
3751
3752
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_lflag &= ~IEXTEN;
3753
    term.c_oflag &= ~OPOST;
3754
3755
3756
    tcsetattr(0, TCSANOW, &term);
}

3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
void disable_signals(void)
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_lflag &= ~ISIG;
    tcsetattr(0, TCSANOW, &term);
}

#ifndef NANO_SMALL
void enable_signals(void)
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_lflag |= ISIG;
    tcsetattr(0, TCSANOW, &term);
}
#endif

void disable_flow_control(void)
{
    struct termios term;

    tcgetattr(0, &term);
3782
    term.c_iflag &= ~IXON;
3783
3784
3785
3786
3787
3788
3789
3790
    tcsetattr(0, TCSANOW, &term);
}

void enable_flow_control(void)
{
    struct termios term;

    tcgetattr(0, &term);
3791
    term.c_iflag |= IXON;
3792
3793
3794
    tcsetattr(0, TCSANOW, &term);
}

3795
3796
3797
3798
/* Set up the terminal state.  Put the terminal in cbreak mode (read one
 * character at a time and interpret the special control keys), disable
 * translation of carriage return (^M) into newline (^J) so that we can
 * tell the difference between the Enter key and Ctrl-J, and disable
3799
 * echoing of characters as they're typed.  Finally, disable extended
3800
3801
3802
 * input and output processing, disable interpretation of the special
 * control keys, and if we're not in preserve mode, disable
 * interpretation of the flow control characters too. */
3803
3804
3805
3806
3807
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3808
    disable_extended_io();
3809
3810
3811
3812
3813
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3814
int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
3815
	*ran_func, bool *finished, bool allow_funcs)
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
{
    int input;
	/* The character we read in. */
    static int *kbinput = NULL;
	/* The input buffer. */
    static size_t kbinput_len = 0;
	/* The length of the input buffer. */
    const shortcut *s;
    bool have_shortcut;
#ifndef NANO_SMALL
    const toggle *t;
    bool have_toggle;
#endif

    *s_or_t = FALSE;
3831
    *ran_func = FALSE;
3832
    *finished = FALSE;
3833
3834
3835
3836
3837
3838
3839

    /* Read in a character. */
    input = get_kbinput(edit, meta_key, func_key);

#ifndef DISABLE_MOUSE
    /* If we got a mouse click and it was on a shortcut, read in the
     * shortcut character. */
3840
    if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) {
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
	if (do_mouse())
	    input = get_kbinput(edit, meta_key, func_key);
	else
	    input = ERR;
    }
#endif

    /* Check for a shortcut in the main list. */
    s = get_shortcut(main_list, &input, meta_key, func_key);

    /* If we got a shortcut from the main list, or a "universal"
     * edit window shortcut, set have_shortcut to TRUE. */
    have_shortcut = (s != NULL || input == NANO_XON_KEY ||
	input == NANO_XOFF_KEY || input == NANO_SUSPEND_KEY);

#ifndef NANO_SMALL
    /* Check for a toggle in the main list. */
    t = get_toggle(input, *meta_key);

    /* If we got a toggle from the main list, set have_toggle to
     * TRUE. */
    have_toggle = (t != NULL);
#endif

    /* Set s_or_t to TRUE if we got a shortcut or toggle. */
    *s_or_t = (have_shortcut
#ifndef NANO_SMALL
	|| have_toggle
#endif
	);

    if (allow_funcs) {
3873
3874
3875
3876
3877
	/* If we got a character, and it isn't a shortcut or toggle,
	 * it's a normal text character.  Display the warning if we're
	 * in view mode, or add the character to the input buffer if
	 * we're not. */
	if (input != ERR && *s_or_t == FALSE) {
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
	    if (ISSET(VIEW_MODE))
		print_view_warning();
	    else {
		kbinput_len++;
		kbinput = (int *)nrealloc(kbinput, kbinput_len *
			sizeof(int));
		kbinput[kbinput_len - 1] = input;
	    }
	}

	/* If we got a shortcut or toggle, or if there aren't any other
	 * characters waiting after the one we read in, we need to
	 * display all the characters in the input buffer if it isn't
	 * empty.  Note that it should be empty if we're in view
	 * mode. */
	 if (*s_or_t == TRUE || get_buffer_len() == 0) {
	    if (kbinput != NULL) {
		/* Display all the characters in the input buffer at
3896
		 * once, filtering out control characters. */
3897
3898
3899
3900
3901
3902
3903
		char *output = charalloc(kbinput_len + 1);
		size_t i;

		for (i = 0; i < kbinput_len; i++)
		    output[i] = (char)kbinput[i];
		output[i] = '\0';

3904
		do_output(output, kbinput_len, FALSE);
3905
3906

		free(output);
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916

		/* Empty the input buffer. */
		kbinput_len = 0;
		free(kbinput);
		kbinput = NULL;
	    }
	}

	if (have_shortcut) {
	    switch (input) {
3917
		/* Handle the "universal" edit window shortcuts. */
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
		case NANO_XON_KEY:
		    statusbar(_("XON ignored, mumble mumble."));
		    break;
		case NANO_XOFF_KEY:
		    statusbar(_("XOFF ignored, mumble mumble."));
		    break;
#ifndef NANO_SMALL
		case NANO_SUSPEND_KEY:
		    if (ISSET(SUSPEND))
			do_suspend(0);
		    break;
#endif
3930
		/* Handle the normal edit window shortcuts, setting
3931
3932
3933
		 * ran_func to TRUE if we try to run their associated
		 * functions and setting finished to TRUE to indicate
		 * that we're done after trying to run their associated
3934
		 * functions. */
3935
3936
3937
3938
3939
3940
		default:
		    /* Blow away the text in the cutbuffer if we aren't
		     * cutting text. */
		    if (s->func != do_cut_text)
			cutbuffer_reset();

3941
		    if (s->func != NULL) {
3942
			*ran_func = TRUE;
3943
3944
3945
3946
			if (ISSET(VIEW_MODE) && !s->viewok)
			    print_view_warning();
			else
			    s->func();
3947
		    }
3948
		    *finished = TRUE;
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
		    break;
	    }
	}
#ifndef NANO_SMALL
	else if (have_toggle) {
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
	    /* Toggle the flag associated with this shortcut. */
	    if (allow_funcs)
		do_toggle(t);
	}
#endif
	else
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
    }

    return input;
}

#ifndef DISABLE_MOUSE
bool do_mouse(void)
{
    int mouse_x, mouse_y;
    bool retval;

    retval = get_mouseinput(&mouse_x, &mouse_y, TRUE);

    if (!retval) {
	/* We can click in the edit window to move the cursor. */
	if (wenclose(edit, mouse_y, mouse_x)) {
	    bool sameline;
		/* Did they click on the line with the cursor?  If they
		 * clicked on the cursor, we set the mark. */
	    size_t xcur;
		/* The character they clicked on. */

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3988
	    /* Subtract out the size of topwin. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3989
	    mouse_y -= 2 - no_more_space();
3990
3991
3992
3993

	    sameline = (mouse_y == current_y);

	    /* Move to where the click occurred. */
3994
3995
	    for (; current_y < mouse_y && current->next != NULL;
		current_y++)
3996
		current = current->next;
3997
3998
	    for (; current_y > mouse_y && current->prev != NULL;
		current_y--)
3999
4000
		current = current->prev;

4001
4002
	    xcur = actual_x(current->data,
		get_page_start(xplustabs()) + mouse_x);
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026

#ifndef NANO_SMALL
	    /* Clicking where the cursor is toggles the mark, as does
	     * clicking beyond the line length with the cursor at the
	     * end of the line. */
	    if (sameline && xcur == current_x) {
		if (ISSET(VIEW_MODE)) {
		    print_view_warning();
		    return retval;
		}
		do_mark();
	    }
#endif

	    current_x = xcur;
	    placewewant = xplustabs();
	    edit_refresh();
	}
    }

    return retval;
}
#endif /* !DISABLE_MOUSE */

4027
4028
/* The user typed ouuput_len multibyte characters.  Add them to the edit
 * buffer, filtering out all control characters if allow_cntrls is
4029
4030
 * TRUE. */
void do_output(char *output, size_t output_len, bool allow_cntrls)
4031
{
4032
    size_t current_len, i = 0;
4033
    bool old_const_update = ISSET(CONST_UPDATE);
4034
4035
4036
4037
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

4038
4039
    char *char_buf = charalloc(mb_cur_max());
    int char_buf_len;
4040
4041
4042

    assert(current != NULL && current->data != NULL);

4043
4044
    current_len = strlen(current->data);

4045
    /* Turn off constant cursor position display. */
4046
    UNSET(CONST_UPDATE);
4047

4048
    while (i < output_len) {
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
	/* If allow_cntrls is FALSE, filter out nulls and newlines,
	 * since they're control characters. */
	if (allow_cntrls) {
	    /* Null to newline, if needed. */
	    if (output[i] == '\0')
		output[i] = '\n';
	    /* Newline to Enter, if needed. */
	    else if (output[i] == '\n') {
		do_enter();
		i++;
		continue;
	    }
4061
4062
	}

4063
4064
4065
	/* Interpret the next multibyte character.  If it's an invalid
	 * multibyte character, interpret it as though it's a byte
	 * character. */
4066
	char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL);
4067
4068

	i += char_buf_len;
4069

4070
4071
4072
4073
	/* If allow_cntrls is FALSE, filter out a control character. */
	if (!allow_cntrls && is_cntrl_mbchar(output + i - char_buf_len))
	    continue;

4074
4075
4076
4077
4078
4079
	/* When a character is inserted on the current magicline, it
	 * means we need a new one! */
	if (filebot == current)
	    new_magicline();

	/* More dangerousness fun =) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4080
4081
	current->data = charealloc(current->data,
		current_len + (char_buf_len * 2));
4082

4083
	assert(current_x <= current_len);
4084

4085
	charmove(&current->data[current_x + char_buf_len],
4086
		&current->data[current_x],
4087
		current_len - current_x + char_buf_len);
4088
	strncpy(&current->data[current_x], char_buf, char_buf_len);
4089
	current_len += char_buf_len;
4090
	totsize++;
4091
4092
4093
4094
4095
	set_modified();

#ifndef NANO_SMALL
	/* Note that current_x has not yet been incremented. */
	if (current == mark_beginbuf && current_x < mark_beginx)
4096
	    mark_beginx += char_buf_len;
4097
4098
#endif

4099
	do_right(FALSE);
4100
4101

#ifndef DISABLE_WRAPPING
4102
4103
	/* If we're wrapping text, we need to call edit_refresh(). */
	if (!ISSET(NO_WRAP)) {
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
	    bool do_refresh_save = do_refresh;

	    do_refresh = do_wrap(current);

	    /* If we needed to call edit_refresh() before this, we'll
	     * still need to after this. */
	    if (do_refresh_save)
		do_refresh = TRUE;
	}
#endif

#ifdef ENABLE_COLOR
	/* If color syntaxes are turned on, we need to call
	 * edit_refresh(). */
4118
	if (!ISSET(NO_COLOR_SYNTAX))
4119
4120
4121
4122
	    do_refresh = TRUE;
#endif
    }

4123
4124
    /* Turn constant cursor position display back on if it was on
     * before. */
4125
4126
    if (old_const_update)
	SET(CONST_UPDATE);
4127

4128
    free(char_buf);
4129

4130
4131
4132
4133
4134
4135
    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
}

4136
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
4137
4138
{
    int optchr;
4139
    ssize_t startline = 1;
4140
	/* Line to try and start at. */
4141
4142
    ssize_t startcol = 1;
	/* Column to try and start at. */
4143
#ifndef DISABLE_WRAPJUSTIFY
4144
4145
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
4146
#endif
4147
4148
4149
4150
4151
#ifdef ENABLE_MULTIBUFFER
    bool old_multibuffer;
	/* The old value of the multibuffer option, restored after we
	 * load all files on the command line. */
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4152
#ifdef HAVE_GETOPT_LONG
4153
    const struct option long_options[] = {
4154
	{"help", 0, NULL, 'h'},
4155
#ifdef ENABLE_MULTIBUFFER
4156
	{"multibuffer", 0, NULL, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
4157
4158
#endif
#ifdef ENABLE_NANORC
4159
	{"ignorercfiles", 0, NULL, 'I'},
4160
#endif
4161
	{"morespace", 0, NULL, 'O'},
4162
#ifndef DISABLE_JUSTIFY
4163
	{"quotestr", 1, NULL, 'Q'},
4164
#endif
4165
	{"restricted", 0, NULL, 'R'},
4166
4167
	{"tabsize", 1, NULL, 'T'},
	{"version", 0, NULL, 'V'},
4168
#ifdef ENABLE_COLOR
4169
	{"syntax", 1, NULL, 'Y'},
4170
#endif
4171
4172
4173
	{"const", 0, NULL, 'c'},
	{"rebinddelete", 0, NULL, 'd'},
	{"nofollow", 0, NULL, 'l'},
4174
#ifndef DISABLE_MOUSE
4175
	{"mouse", 0, NULL, 'm'},
4176
#endif
4177
#ifndef DISABLE_OPERATINGDIR
4178
	{"operatingdir", 1, NULL, 'o'},
4179
#endif
4180
	{"preserve", 0, NULL, 'p'},
4181
#ifndef DISABLE_WRAPJUSTIFY
4182
	{"fill", 1, NULL, 'r'},
4183
4184
#endif
#ifndef DISABLE_SPELLER
4185
	{"speller", 1, NULL, 's'},
4186
#endif
4187
4188
	{"tempfile", 0, NULL, 't'},
	{"view", 0, NULL, 'v'},
4189
#ifndef DISABLE_WRAPPING
4190
	{"nowrap", 0, NULL, 'w'},
4191
#endif
4192
4193
	{"nohelp", 0, NULL, 'x'},
	{"suspend", 0, NULL, 'z'},
4194
#ifndef NANO_SMALL
4195
4196
	{"smarthome", 0, NULL, 'A'},
	{"backup", 0, NULL, 'B'},
4197
4198
	{"backupdir", 1, NULL, 'C'},
	{"tabstospaces", 0, NULL, 'E'},
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4199
	{"historylog", 0, NULL, 'H'},
4200
4201
	{"noconvert", 0, NULL, 'N'},
	{"smooth", 0, NULL, 'S'},
4202
	{"quickblank", 0, NULL, 'U'},
4203
4204
	{"autoindent", 0, NULL, 'i'},
	{"cut", 0, NULL, 'k'},
4205
#endif
4206
	{NULL, 0, NULL, 0}
Chris Allegretta's avatar
Chris Allegretta committed
4207
4208
4209
    };
#endif

4210
4211
#ifdef NANO_WIDE
    {
4212
4213
	/* If the locale set exists and includes the case-insensitive
	 * string "UTF8" or "UTF-8", we should use UTF-8. */
4214
4215
	char *locale = setlocale(LC_ALL, "");

4216
4217
4218
	if (locale != NULL && (strcasestr(locale, "UTF8") != NULL ||
		strcasestr(locale, "UTF-8") != NULL)) {
	    SET(USE_UTF8);
4219
#ifdef USE_SLANG
4220
	    SLutf8_enable(TRUE);
4221
#endif
4222
	}
4223
4224
    }
#else
Chris Allegretta's avatar
Chris Allegretta committed
4225
    setlocale(LC_ALL, "");
4226
4227
#endif

4228
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
4229
4230
4231
4232
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4233
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4234
4235
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
4236
    if (geteuid() == NANO_ROOT_UID)
4237
4238
	SET(NO_WRAP);
#endif
4239

4240
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
4241
#ifdef HAVE_GETOPT_LONG
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4242
	getopt_long(argc, argv,
4243
		"h?ABC:EFHINOQ:RST:UVY:abcdefgijklmo:pr:s:tvwxz",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4244
		long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
4245
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4246
	getopt(argc, argv,
4247
		"h?ABC:EFHINOQ:RST:UVY:abcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
4248
#endif
4249
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
4250
4251

	switch (optchr) {
4252
4253
4254
4255
4256
4257
4258
4259
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
4260
#ifndef NANO_SMALL
4261
4262
4263
4264
4265
4266
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
4267
	    case 'C':
4268
4269
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
4270
4271
4272
	    case 'E':
		SET(TABS_TO_SPACES);
		break;
4273
#endif
4274
#ifdef ENABLE_MULTIBUFFER
4275
4276
4277
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
4278
4279
#endif
#ifdef ENABLE_NANORC
4280
#ifndef NANO_SMALL
4281
4282
4283
	    case 'H':
		SET(HISTORYLOG);
		break;
4284
#endif
4285
4286
4287
	    case 'I':
		SET(NO_RCFILE);
		break;
4288
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4289
#ifndef NANO_SMALL
4290
4291
4292
	    case 'N':
		SET(NO_CONVERT);
		break;
4293
#endif
4294
4295
4296
	    case 'O':
		SET(MORE_SPACE);
		break;
4297
#ifndef DISABLE_JUSTIFY
4298
4299
4300
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
4301
#endif
4302
4303
4304
	    case 'R':
		SET(RESTRICTED);
		break;
4305
#ifndef NANO_SMALL
4306
	    case 'S':
4307
		SET(SMOOTH_SCROLL);
4308
		break;
4309
#endif
4310
4311
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
4312
4313
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4314
4315
4316
		    exit(1);
		}
		break;
4317
4318
4319
4320
4321
#ifndef NANO_SMALL
	    case 'U':
		SET(QUICK_BLANK);
		break;
#endif
4322
4323
4324
	    case 'V':
		version();
		exit(0);
4325
#ifdef ENABLE_COLOR
4326
4327
4328
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
4329
#endif
4330
	    case 'c':
4331
		SET(CONST_UPDATE);
4332
4333
4334
4335
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
4336
#ifndef NANO_SMALL
4337
4338
4339
4340
4341
4342
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
4343
#endif
4344
4345
4346
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
4347
#ifndef DISABLE_MOUSE
4348
4349
4350
	    case 'm':
		SET(USE_MOUSE);
		break;
4351
#endif
4352
#ifndef DISABLE_OPERATINGDIR
4353
4354
4355
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
4356
#endif
4357
4358
4359
	    case 'p':
		SET(PRESERVE);
		break;
4360
#ifndef DISABLE_WRAPJUSTIFY
4361
4362
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
4363
4364
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4365
4366
4367
4368
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
4369
#endif
4370
#ifndef DISABLE_SPELLER
4371
4372
4373
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
4374
#endif
4375
4376
4377
4378
4379
4380
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
4381
#ifndef DISABLE_WRAPPING
4382
4383
4384
	    case 'w':
		SET(NO_WRAP);
		break;
4385
#endif
4386
4387
4388
4389
4390
4391
4392
4393
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
4394
4395
4396
	}
    }

4397
4398
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
4399
4400
4401
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

4402
4403
4404
    /* If we're using restricted mode, disable suspending, backups, and
     * reading rcfiles, since they all would allow reading from or
     * writing to files not specified on the command line. */
4405
4406
4407
4408
4409
4410
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
4411
/* We've read through the command line options.  Now back up the flags
4412
4413
 * and values that are set, and read the rcfile(s).  If the values
 * haven't changed afterward, restore the backed-up values. */
Chris Allegretta's avatar
Chris Allegretta committed
4414
4415
4416
4417
4418
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE)) {
#ifndef DISABLE_OPERATINGDIR
	char *operating_dir_cpy = operating_dir;
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4419
#ifndef DISABLE_WRAPJUSTIFY
4420
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
4421
#endif
4422
4423
4424
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4425
4426
4427
4428
4429
4430
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
4431
	ssize_t tabsize_cpy = tabsize;
4432
	unsigned long flags_cpy = flags;
Chris Allegretta's avatar
Chris Allegretta committed
4433

4434
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4435
	operating_dir = NULL;
4436
#endif
4437
4438
4439
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
4440
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4441
	quotestr = NULL;
4442
4443
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
4444
	alt_speller = NULL;
4445
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4446
4447
4448
4449
4450
4451
4452
4453
4454

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
4455
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4456
4457
4458
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
4459
4460
4461
4462
4463
4464
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
#ifndef DISABLE_JUSTIFY
	if (quotestr_cpy != NULL) {
	    free(quotestr);
	    quotestr = quotestr_cpy;
	}
#endif
#ifndef DISABLE_SPELLER
	if (alt_speller_cpy != NULL) {
	    free(alt_speller);
	    alt_speller = alt_speller_cpy;
	}
#endif
4477
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
4478
4479
4480
4481
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4482
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
4483
4484
4485
4486
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

4487
4488
4489
4490
4491
4492
4493
4494
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4495
#ifndef NANO_SMALL
4496
    /* Set up the backup directory (unless we're using restricted mode,
4497
4498
4499
4500
     * in which case backups are disabled, since they would allow
     * reading from or writing to files not specified on the command
     * line).  This entails making sure it exists and is a directory, so
     * that backup files will be saved there. */
4501
4502
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4503
4504
#endif

4505
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4506
    /* Set up the operating directory.  This entails chdir()ing there,
4507
     * so that file reads and writes will be based there. */
4508
4509
4510
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4511
#ifndef DISABLE_JUSTIFY
4512
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4513
	punct = mallocstrcpy(punct, ".?!");
4514
4515

    if (brackets == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4516
	brackets = mallocstrcpy(brackets, "'\")}]>");
4517

Chris Allegretta's avatar
Chris Allegretta committed
4518
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4519
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
4520
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4521
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
4522
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4523
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
4524
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4525
		);
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
#ifdef HAVE_REGEX_H
    quoterc = regcomp(&quotereg, quotestr, REG_EXTENDED);

    if (quoterc == 0) {
	/* We no longer need quotestr, just quotereg. */
	free(quotestr);
	quotestr = NULL;
    } else {
	size_t size = regerror(quoterc, &quotereg, NULL, 0);

	quoteerr = charalloc(size);
	regerror(quoterc, &quotereg, quoteerr, size);
    }
#else
    quotelen = strlen(quotestr);
#endif /* !HAVE_REGEX_H */
Chris Allegretta's avatar
Chris Allegretta committed
4542
#endif /* !DISABLE_JUSTIFY */
4543

4544
4545
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
4546
     * command line and/or rcfile(s), check $SPELL for one, as Pico
4547
     * does (unless we're using restricted mode, in which case spell
4548
4549
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
4550
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
4551
4552
4553
4554
4555
4556
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

4557
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4558
    /* If whitespace wasn't specified, set its default value. */
4559
    if (whitespace == NULL) {
4560
	whitespace = mallocstrcpy(NULL, "  ");
4561
4562
4563
	whitespace_len[0] = 1;
	whitespace_len[1] = 1;
    }
4564
4565
#endif

4566
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4567
    if (tabsize == -1)
4568
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4569

4570
    /* Back up the old terminal settings so that they can be restored. */
4571
    tcgetattr(0, &oldterm);
4572

4573
4574
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4575
    initscr();
4576
    terminal_init();
4577

4578
4579
4580
    /* Turn the cursor on for sure. */
    curs_set(1);

4581
    /* Set up the global variables and the shortcuts. */
4582
4583
    global_init(FALSE);
    shortcut_init(FALSE);
4584
4585

    /* Set up the signal handlers. */
4586
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4587
4588

#ifdef DEBUG
4589
    fprintf(stderr, "Main: set up windows\n");
Chris Allegretta's avatar
Chris Allegretta committed
4590
4591
#endif

4592
    window_init();
4593
#ifndef DISABLE_MOUSE
4594
    mouse_init();
4595
#endif
4596

Chris Allegretta's avatar
Chris Allegretta committed
4597
#ifdef DEBUG
4598
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4599
#endif
4600

4601
4602
4603
    /* If there's a +LINE or +LINE,COLUMN flag here, it is the first
     * non-option argument, and it is followed by at least one other
     * argument, the filename it applies to. */
4604
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
4605
	parse_line_column(&argv[optind][1], &startline, &startcol);
4606
4607
4608
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
4609
#ifdef ENABLE_MULTIBUFFER
4610
4611
4612
4613
4614
4615
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

    /* Read all the files after the first one on the command line into
     * new buffers. */
    {
4616
4617
	int i = optind + 1;
	ssize_t iline = 1, icol = 1;
4618

4619
	for (; i < argc; i++) {
4620
4621
4622
4623
	    /* If there's a +LINE or +LINE,COLUMN flag here, it is
	     * followed by at least one other argument, the filename it
	     * applies to. */
	    if (i < argc - 1 && argv[i][0] == '+' && iline == 1 &&
4624
		icol == 1)
4625
		parse_line_column(&argv[i][1], &iline, &icol);
4626
	    else {
4627
		load_buffer(argv[i]);
4628

4629
		if (iline > 1 || icol > 1) {
4630
		    do_gotolinecolumn(iline, icol, FALSE, FALSE, FALSE);
4631
4632
		    iline = 1;
		    icol = 1;
4633
4634
4635
		}
	    }
	}
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
    }
#endif

    /* Read the first file on the command line into either the current
     * buffer or a new buffer, depending on whether multibuffer mode is
     * enabled. */
    if (optind < argc)
	load_buffer(argv[optind]);

    /* We didn't open any files if all the command line arguments were
     * invalid files like directories or if there were no command line
     * arguments given.  In this case, we have to load a blank buffer.
     * Also, we unset view mode to allow editing. */
    if (filename == NULL) {
	filename = mallocstrcpy(NULL, "");
	new_file();
	UNSET(VIEW_MODE);

	/* Add this new entry to the open_files structure if we have
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4655
4656
	 * multibuffer support, or to the main filestruct if we
	 * don't. */
4657
	load_file();
Chris Allegretta's avatar
Chris Allegretta committed
4658
    }
4659
4660
4661
4662

#ifdef ENABLE_MULTIBUFFER
    if (!old_multibuffer)
	UNSET(MULTIBUFFER);
Chris Allegretta's avatar
Chris Allegretta committed
4663
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4664

4665
4666
4667
4668
4669
4670
4671
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

4672
    if (startline > 1 || startcol > 1)
4673
	do_gotolinecolumn(startline, startcol, FALSE, FALSE, FALSE);
4674

4675
4676
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4677
    sigsetjmp(jmpbuf, 1);
4678
#endif
4679

Robert Siemborski's avatar
Robert Siemborski committed
4680
4681
    edit_refresh();

4682
    while (TRUE) {
4683
	bool meta_key, func_key, s_or_t, ran_func, finished;
4684
4685

	/* Make sure the cursor is in the edit window. */
4686
	reset_cursor();
4687

4688
4689
4690
4691
	/* If constant cursor position display is on, and there are no
	 * keys waiting in the buffer, display the current cursor
	 * position on the statusbar. */
	if (ISSET(CONST_UPDATE) && get_buffer_len() == 0)
4692
	    do_cursorpos(TRUE);
4693

4694
4695
	currshortcut = main_list;

4696
	/* Read in and interpret characters. */
4697
4698
	do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
		TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4699
    }
4700

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4701
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4702
}