nano.c 102 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-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
27
28
29
30
31
32
#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
33
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
34
35
36
#include <errno.h>
#include <ctype.h>
#include <locale.h>
37
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "proto.h"
#include "nano.h"

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

#ifdef HAVE_TERMIO_H
#include <termio.h>
#endif

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

53
54
55
56
#ifndef NANO_SMALL
#include <setjmp.h>
#endif

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

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

70
#ifndef NANO_SMALL
71
72
static sigjmp_buf jmpbuf;	/* Used to return to mainloop after
				   SIGWINCH */
73
74
75
76
static int pid;			/* The PID of the newly forked process
				 * in open_pipe().  It must be global
				 * because the signal handler needs
				 * it. */
77
#endif
78

79
80
/* What we do when we're all set to exit. */
void finish(void)
Chris Allegretta's avatar
Chris Allegretta committed
81
{
82
83
84
85
    if (!ISSET(NO_HELP))
	blank_bottombars();
    else
	blank_statusbar();
86

Chris Allegretta's avatar
Chris Allegretta committed
87
88
89
    wrefresh(bottomwin);
    endwin();

90
    /* Restore the old terminal settings. */
Chris Allegretta's avatar
Chris Allegretta committed
91
    tcsetattr(0, TCSANOW, &oldterm);
Chris Allegretta's avatar
Chris Allegretta committed
92

93
94
95
96
97
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	save_history();
#endif

98
#ifdef DEBUG
99
    thanks_for_all_the_fish();
100
#endif
101

102
    exit(0);
Chris Allegretta's avatar
Chris Allegretta committed
103
104
}

105
/* Die (gracefully?). */
Chris Allegretta's avatar
Chris Allegretta committed
106
void die(const char *msg, ...)
Chris Allegretta's avatar
Chris Allegretta committed
107
108
109
{
    va_list ap;

110
111
112
    endwin();
    curses_ended = TRUE;

113
    /* Restore the old terminal settings. */
114
115
    tcsetattr(0, TCSANOW, &oldterm);

Chris Allegretta's avatar
Chris Allegretta committed
116
117
118
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
119

120
121
122
    /* save the currently loaded file if it's been modified */
    if (ISSET(MODIFIED))
	die_save_file(filename);
123

124
#ifdef ENABLE_MULTIBUFFER
125
    /* then save all of the other modified loaded files, if any */
126
    if (open_files != NULL) {
127
	openfilestruct *tmp;
128
129
130

	tmp = open_files;

131
	while (open_files->prev != NULL)
132
133
	    open_files = open_files->prev;

134
	while (open_files->next != NULL) {
135

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
136
	    /* if we already saved the file above (i.e, if it was the
137
138
	       currently loaded file), don't save it again */
	    if (tmp != open_files) {
Chris Allegretta's avatar
Chris Allegretta committed
139
140
141
142
		/* make sure open_files->fileage and fileage, and
		   open_files->filebot and filebot, are in sync; they
		   might not be if lines have been cut from the top or
		   bottom of the file */
143
		fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
144
		filebot = open_files->filebot;
145
		/* save the file if it's been modified */
Chris Allegretta's avatar
Chris Allegretta committed
146
		if (open_files->file_flags & MODIFIED)
147
		    die_save_file(open_files->filename);
148
149
150
151
152
153
	    }
	    open_files = open_files->next;
	}
    }
#endif

Chris Allegretta's avatar
Chris Allegretta committed
154
    exit(1); /* We have a problem: exit w/ errorlevel(1) */
155
156
}

Chris Allegretta's avatar
Chris Allegretta committed
157
void die_save_file(const char *die_filename)
158
{
Chris Allegretta's avatar
Chris Allegretta committed
159
    char *ret;
160
    bool failed = TRUE;
161

162
163
164
    /* 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. */
165
166
167
    if (ISSET(RESTRICTED))
	return;

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

    ret = get_next_filename(die_filename);
Chris Allegretta's avatar
Chris Allegretta committed
174
    if (ret[0] != '\0')
175
	failed = (write_file(ret, TRUE, FALSE, TRUE) == -1);
Chris Allegretta's avatar
Chris Allegretta committed
176

177
    if (!failed)
Chris Allegretta's avatar
Chris Allegretta committed
178
	fprintf(stderr, _("\nBuffer written to %s\n"), ret);
179
    else
180
	fprintf(stderr, _("\nBuffer not written to %s (too many backup files?)\n"), ret);
181
182

    free(ret);
Chris Allegretta's avatar
Chris Allegretta committed
183
184
}

185
/* Die with an error message that the screen was too small if, well, the
Chris Allegretta's avatar
Chris Allegretta committed
186
 * screen is too small. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
187
void die_too_small(void)
188
{
Chris Allegretta's avatar
Chris Allegretta committed
189
    die(_("Window size is too small for nano...\n"));
190
191
}

Chris Allegretta's avatar
Chris Allegretta committed
192
193
194
195
196
void print_view_warning(void)
{
    statusbar(_("Key illegal in VIEW mode"));
}

197
/* Initialize global variables -- no better way for now.  If
198
199
 * save_cutbuffer is TRUE, don't set cutbuffer to NULL. */
void global_init(bool save_cutbuffer)
Chris Allegretta's avatar
Chris Allegretta committed
200
201
202
{
    current_x = 0;
    current_y = 0;
203

204
205
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
206
207
	die_too_small();

Chris Allegretta's avatar
Chris Allegretta committed
208
    fileage = NULL;
209
210
    if (!save_cutbuffer)
	cutbuffer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
211
212
213
    current = NULL;
    edittop = NULL;
    totlines = 0;
214
    totsize = 0;
Chris Allegretta's avatar
Chris Allegretta committed
215
    placewewant = 0;
216

217
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
218
    fill = wrap_at;
219
    if (fill <= 0)
Chris Allegretta's avatar
Chris Allegretta committed
220
	fill += COLS;
221
222
    if (fill < 0)
	fill = 0;
223
#endif
224

225
    hblank = charalloc(COLS + 1);
226
    memset(hblank, ' ', COLS);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
227
    hblank[COLS] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
228
229
}

230
231
void window_init(void)
{
232
233
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS)
234
235
	die_too_small();

236
237
    if (topwin != NULL)
	delwin(topwin);
238
239
    if (edit != NULL)
	delwin(edit);
240
241
242
    if (bottomwin != NULL)
	delwin(bottomwin);

243
    /* Set up the windows. */
244
    topwin = newwin(2, COLS, 0, 0);
245
    edit = newwin(editwinrows, COLS, 2, 0);
246
247
    bottomwin = newwin(3 - no_help(), COLS, LINES - 3 + no_help(), 0);

248
    /* Turn the keypad back on. */
249
250
251
252
    keypad(edit, TRUE);
    keypad(bottomwin, TRUE);
}

253
#ifndef DISABLE_MOUSE
254
255
256
257
258
259
260
261
void mouse_init(void)
{
    if (ISSET(USE_MOUSE)) {
	mousemask(BUTTON1_RELEASED, NULL);
	mouseinterval(50);
    } else
	mousemask(0, NULL);
}
262
#endif
263
264
265
266
267
268

#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)
{
269
270
271
    size_t allocsize = 1;	/* Space needed for help_text. */
    const char *htx;		/* Untranslated help message. */
    char *ptr;
272
    const shortcut *s;
273
274
275
276
#ifndef NANO_SMALL
    const toggle *t;
#endif

277
    /* First, set up the initial help text for the current function. */
278
279
    if (currshortcut == whereis_list || currshortcut == replace_list
	     || currshortcut == replace_list_2)
280
	htx = N_("Search Command Help Text\n\n "
281
		"Enter the words or characters you would like to search "
282
		"for, then hit Enter.  If there is a match for the text you "
283
		"entered, the screen will be updated to the location of the "
284
285
286
287
288
289
290
		"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.  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");
291
    else if (currshortcut == gotoline_list)
292
	htx = N_("Go To Line Help Text\n\n "
293
294
295
296
297
298
		"Enter the line number that you wish to go to and hit "
		"Enter.  If there are fewer lines of text than the "
		"number you entered, you will be brought to the last line "
		"of the file.\n\n The following function keys are "
		"available in Go To Line mode:\n\n");
    else if (currshortcut == insertfile_list)
299
	htx = N_("Insert File Help Text\n\n "
300
301
302
303
304
305
306
307
308
309
310
311
312
		"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 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).\n\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 function keys are "
		"available in Insert File mode:\n\n");
    else if (currshortcut == writefile_list)
313
	htx = N_("Write File Help Text\n\n "
314
315
		"Type the name that you wish to save the current file "
		"as and hit Enter to save the file.\n\n If you have "
316
		"selected text with the mark, you will be prompted to "
317
318
319
320
321
322
323
		"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");
#ifndef DISABLE_BROWSER
    else if (currshortcut == browser_list)
324
	htx = N_("File Browser Help Text\n\n "
325
326
327
328
329
330
331
332
333
334
		"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 "
		"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");
    else if (currshortcut == gotodir_list)
335
	htx = N_("Browser Go To Directory Help Text\n\n "
336
337
		"Enter the name of the directory you would like to "
		"browse to.\n\n If tab completion has not been disabled, "
338
		"you can use the Tab key to (attempt to) automatically "
339
340
341
		"complete the directory name.\n\n The following function "
		"keys are available in Browser Go To Directory mode:\n\n");
#endif
342
#ifndef DISABLE_SPELLER
343
    else if (currshortcut == spell_list)
344
	htx = N_("Spell Check Help Text\n\n "
345
346
347
348
349
		"The spell checker checks the spelling of all text "
		"in the current file.  When an unknown word is "
		"encountered, it is highlighted and a replacement can "
		"be edited.  It will then prompt to replace every "
		"instance of the given misspelled word in the "
350
351
352
		"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");
353
#endif
354
355
#ifndef NANO_SMALL
    else if (currshortcut == extcmd_list)
356
	htx = N_("External Command Help Text\n\n "
357
358
359
360
361
		"This menu allows you to insert the output of a command "
		"run by the shell into the current buffer (or a new "
		"buffer in multibuffer mode).\n\n The following keys are "
		"available in this mode:\n\n");
#endif
362
363
364
    else
	/* Default to the main help list. */
	htx = N_(" nano help text\n\n "
365
366
367
368
369
370
371
372
373
374
	  "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 commonly used shortcuts "
	  "in the editor.\n\n "
	  "The notation for shortcuts is as follows: Control-key "
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
375
376
377
378
379
380
381
382
383
	  "sequences are notated with a caret (^) symbol and can be "
	  "entered either by using the Control (Ctrl) key or pressing the "
	  "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.  Also, pressing Esc "
	  "twice and then typing a three-digit number from 000 to 255 "
	  "will enter the character with the corresponding ASCII code.  "
	  "The following keystrokes are available in the main editor "
	  "window.  Alternative keys are shown in parentheses:\n\n");
384

385
386
387
    htx = _(htx);

    allocsize += strlen(htx);
388
389
390

    /* The space needed for the shortcut lists, at most COLS characters,
     * plus '\n'. */
391
    allocsize += (COLS < 21 ? 21 : COLS + 1) * length_of_list(currshortcut);
392
393

#ifndef NANO_SMALL
394
    /* If we're on the main list, we also count the toggle help text.
395
396
     * Each line has "M-%c\t\t\t", which fills 24 columns, plus a space,
     * plus translated text, plus '\n'. */
397
398
399
    if (currshortcut == main_list) {
	size_t endislen = strlen(_("enable/disable"));

400
	for (t = toggles; t != NULL; t = t->next)
401
402
	    allocsize += 8 + strlen(t->desc) + endislen;
    }
403
404
405
406
407
408
#endif /* !NANO_SMALL */

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

409
    /* Allocate space for the help text. */
410
    help_text = charalloc(allocsize);
411

412
413
    /* Now add the text we want. */
    strcpy(help_text, htx);
414
415
    ptr = help_text + strlen(help_text);

416
417
418
419
420
    /* 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. */
421
    for (s = currshortcut; s != NULL; s = s->next) {
422
	int entries = 0;
423

424
	/* Control key. */
425
	if (s->ctrlval != NANO_NO_KEY) {
426
	    entries++;
427
#ifndef NANO_SMALL
428
	    if (s->ctrlval == NANO_HISTORY_KEY)
429
		ptr += sprintf(ptr, "%.7s", _("Up"));
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
430
	    else
431
#endif
432
	    if (s->ctrlval == NANO_CONTROL_SPACE)
433
		ptr += sprintf(ptr, "^%.6s", _("Space"));
434
	    else if (s->ctrlval == NANO_CONTROL_8)
435
436
		ptr += sprintf(ptr, "^?");
	    else
437
		ptr += sprintf(ptr, "^%c", s->ctrlval + 64);
438
	    *(ptr++) = '\t';
439
	}
440

441
442
443
	/* Function key. */
	if (s->funcval != NANO_NO_KEY) {
	    entries++;
444
445
446
447
448
	    /* If this is the first entry, put it in the middle. */
	    if (entries == 1) {
		entries++;
		*(ptr++) = '\t';
	    }
449
	    ptr += sprintf(ptr, "(F%d)", s->funcval - KEY_F0);
450
451
	    *(ptr++) = '\t';
	}
452

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
	/* Primary meta key sequence. */
	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';
	    }
	    /* If the primary meta key sequence is the first entry,
	     * don't put parentheses around it. */
	    if (entries == 1 && s->metaval == NANO_ALT_SPACE)
		ptr += sprintf(ptr, "M-%.5s", _("Space"));
	    else
		ptr += sprintf(ptr, entries == 1 ? "M-%c" : "(M-%c)",
			toupper(s->metaval));
	    *(ptr++) = '\t';
	}
470

471
472
473
474
475
476
477
478
	/* 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';
	    }
479
	    ptr += sprintf(ptr, "(M-%c)", toupper(s->miscval));
480
481
	    *(ptr++) = '\t';
	}
482

483
484
485
486
487
	/* Make sure all the help text starts at the same place. */
	while (entries < 3) {
	    entries++;
	    *(ptr++) = '\t';
	}
488
489

	assert(s->help != NULL);
490
	ptr += sprintf(ptr, "%.*s\n", COLS > 24 ? COLS - 24 : 0, s->help);
491
492
493
494
    }

#ifndef NANO_SMALL
    /* And the toggles... */
495
    if (currshortcut == main_list) {
496
497
	for (t = toggles; t != NULL; t = t->next) {
	    assert(t->desc != NULL);
498
499
	    ptr += sprintf(ptr, "M-%c\t\t\t%s %s\n", toupper(t->val),
		t->desc, _("enable/disable"));
500
	}
501
    }
502
503
504
#endif /* !NANO_SMALL */

    /* If all went well, we didn't overwrite the allocated space for
505
     * help_text. */
506
    assert(strlen(help_text) < allocsize);
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
}
#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));

    newnode->data = NULL;
    newnode->prev = prevnode;
    newnode->next = NULL;
    newnode->lineno = prevnode != NULL ? prevnode->lineno + 1 : 1;

    return newnode;
}

524
/* Make a copy of a node to a pointer (space will be malloc()ed). */
Chris Allegretta's avatar
Chris Allegretta committed
525
filestruct *copy_node(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
526
{
Chris Allegretta's avatar
Chris Allegretta committed
527
    filestruct *dst = (filestruct *)nmalloc(sizeof(filestruct));
Chris Allegretta's avatar
Chris Allegretta committed
528

Chris Allegretta's avatar
Chris Allegretta committed
529
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
530

Chris Allegretta's avatar
Chris Allegretta committed
531
    dst->data = charalloc(strlen(src->data) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
532
533
534
535
536
537
538
539
    dst->next = src->next;
    dst->prev = src->prev;
    strcpy(dst->data, src->data);
    dst->lineno = src->lineno;

    return dst;
}

540
/* Splice a node into an existing filestruct. */
541
542
void splice_node(filestruct *begin, filestruct *newnode, filestruct
	*end)
543
544
545
546
547
548
549
550
551
552
553
{
    if (newnode != NULL) {
	newnode->next = end;
	newnode->prev = begin;
    }
    if (begin != NULL)
	begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

554
/* Unlink a node from the rest of the filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
555
void unlink_node(const filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
556
{
Chris Allegretta's avatar
Chris Allegretta committed
557
    assert(fileptr != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
558

559
560
561
562
563
564
565
566
    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
567
void delete_node(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
568
{
Chris Allegretta's avatar
Chris Allegretta committed
569
570
571
572
573
    if (fileptr != NULL) {
	if (fileptr->data != NULL)
	    free(fileptr->data);
	free(fileptr);
    }
574
575
576
}

/* Okay, now let's duplicate a whole struct! */
Chris Allegretta's avatar
Chris Allegretta committed
577
filestruct *copy_filestruct(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
578
{
Chris Allegretta's avatar
Chris Allegretta committed
579
580
    filestruct *head;	/* copy of src, top of the copied list */
    filestruct *prev;	/* temp that traverses the list */
Chris Allegretta's avatar
Chris Allegretta committed
581

Chris Allegretta's avatar
Chris Allegretta committed
582
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
583

Chris Allegretta's avatar
Chris Allegretta committed
584
585
586
587
588
589
590
591
    prev = copy_node(src);
    prev->prev = NULL;
    head = prev;
    src = src->next;
    while (src != NULL) {
	prev->next = copy_node(src);
	prev->next->prev = prev;
	prev = prev->next;
Chris Allegretta's avatar
Chris Allegretta committed
592

Chris Allegretta's avatar
Chris Allegretta committed
593
	src = src->next;
Chris Allegretta's avatar
Chris Allegretta committed
594
595
    }

Chris Allegretta's avatar
Chris Allegretta committed
596
    prev->next = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
597
598
599
    return head;
}

600
/* Frees a filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
601
void free_filestruct(filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
602
{
Chris Allegretta's avatar
Chris Allegretta committed
603
604
605
606
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_node(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
607
#ifdef DEBUG
608
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_node()");
Chris Allegretta's avatar
Chris Allegretta committed
609
#endif
Chris Allegretta's avatar
Chris Allegretta committed
610
611
	}
	delete_node(src);
612
#ifdef DEBUG
613
	fprintf(stderr, "%s: free'd last node.\n", "delete_node()");
614
615
616
617
#endif
    }
}

618
619
620
621
622
623
624
625
626
627
628
/* 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;
    assert(top != NULL && bot != NULL);

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

629
630
631
632
633
634
635
636
637
638
639
640
641
    /* 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;
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681

    /* 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. */
void unpartition_filestruct(partition *p)
{
    char *tmp;
    assert(p != NULL);

    /* 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);
    fileage->prev = p->top_prev;
682
683
    if (fileage->prev != NULL)
	fileage->prev->next = fileage;
684
685
686
687
688
689
690
691
692
693
694
    fileage->data = charealloc(fileage->data, strlen(p->top_data) +
	strlen(fileage->data) + 1);
    strcpy(fileage->data, p->top_data);
    free(p->top_data);
    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. */
    filebot->next = p->bot_next;
695
696
    if (filebot->next != NULL)
	filebot->next->prev = filebot;
697
698
699
700
701
    filebot->data = charealloc(filebot->data, strlen(filebot->data) +
	strlen(p->bot_data) + 1);
    strcat(filebot->data, p->bot_data);
    free(p->bot_data);

702
703
704
705
706
707
    /* Restore the top and bottom of the filestruct, if they were
     * different from the top and bottom of the partition. */
    if (p->fileage != NULL)
	fileage = p->fileage;
    if (p->filebot != NULL)
	filebot = p->filebot;
708
709
710
711
712
713

    /* Uninitialize the partition. */
    free(p);
    p = NULL;
}

Chris Allegretta's avatar
Chris Allegretta committed
714
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
715
716
{
    filestruct *temp;
717
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
718

Chris Allegretta's avatar
Chris Allegretta committed
719
720
    assert(fileage == NULL || fileage != fileage->next);
    for (temp = fileage; temp != NULL; temp = temp->next)
Chris Allegretta's avatar
Chris Allegretta committed
721
722
723
	temp->lineno = i++;
}

Chris Allegretta's avatar
Chris Allegretta committed
724
void renumber(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
725
{
Chris Allegretta's avatar
Chris Allegretta committed
726
    if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage)
Chris Allegretta's avatar
Chris Allegretta committed
727
	renumber_all();
Chris Allegretta's avatar
Chris Allegretta committed
728
729
    else {
	int lineno = fileptr->prev->lineno;
730

Chris Allegretta's avatar
Chris Allegretta committed
731
732
733
734
	assert(fileptr != fileptr->next);
	for (; fileptr != NULL; fileptr = fileptr->next)
	    fileptr->lineno = ++lineno;
    }
Chris Allegretta's avatar
Chris Allegretta committed
735
736
}

737
738
/* Print one usage string to the screen.  This cuts down on duplicate
 * strings to translate and leaves out the parts that shouldn't be
Chris Allegretta's avatar
Chris Allegretta committed
739
 * translatable (the flag names). */
740
741
void print1opt(const char *shortflag, const char *longflag, const char
	*desc)
742
743
744
745
746
747
748
749
750
751
752
753
754
{
    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

755
    printf("%s\n", _(desc));
756
757
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
758
void usage(void)
Chris Allegretta's avatar
Chris Allegretta committed
759
760
{
#ifdef HAVE_GETOPT_LONG
Chris Allegretta's avatar
Chris Allegretta committed
761
762
    printf(_("Usage: nano [+LINE] [GNU long option] [option] [file]\n\n"));
    printf(_("Option\t\tLong option\t\tMeaning\n"));
Chris Allegretta's avatar
Chris Allegretta committed
763
#else
Chris Allegretta's avatar
Chris Allegretta committed
764
765
    printf(_("Usage: nano [+LINE] [option] [file]\n\n"));
    printf(_("Option\t\tMeaning\n"));
766
#endif
767

768
769
    print1opt("-h, -?", "--help", N_("Show this message"));
    print1opt(_("+LINE"), "", N_("Start at line number LINE"));
770
#ifndef NANO_SMALL
771
772
773
    print1opt("-A", "--smarthome", N_("Enable smart home key"));
    print1opt("-B", "--backup", N_("Backup existing files on save"));
    print1opt(_("-E [dir]"), _("--backupdir=[dir]"), N_("Directory for writing backup files"));
774
#endif
775
#ifdef ENABLE_MULTIBUFFER
776
    print1opt("-F", "--multibuffer", N_("Enable multiple file buffers"));
Chris Allegretta's avatar
Chris Allegretta committed
777
778
#endif
#ifdef ENABLE_NANORC
779
#ifndef NANO_SMALL
780
    print1opt("-H", "--historylog", N_("Log & read search/replace string history"));
781
#endif
782
    print1opt("-I", "--ignorercfiles", N_("Don't look at nanorc files"));
Chris Allegretta's avatar
Chris Allegretta committed
783
784
#endif
#ifndef NANO_SMALL
785
    print1opt("-N", "--noconvert", N_("Don't convert files from DOS/Mac format"));
786
787
#endif
#ifndef DISABLE_JUSTIFY
788
    print1opt(_("-Q [str]"), _("--quotestr=[str]"), N_("Quoting string, default \"> \""));
789
#endif
790
#ifdef HAVE_REGEX_H
791
    print1opt("-R", "--regexp", N_("Do regular expression searches"));
792
#endif
793
#ifndef NANO_SMALL
794
    print1opt("-S", "--smooth", N_("Smooth scrolling"));
795
#endif
796
797
    print1opt(_("-T [#cols]"), _("--tabsize=[#cols]"), N_("Set width of a tab in cols to #cols"));
    print1opt("-V", "--version", N_("Print version information and exit"));
798
#ifdef ENABLE_COLOR
799
    print1opt(_("-Y [str]"), _("--syntax [str]"), N_("Syntax definition to use"));
800
#endif
801
802
    print1opt("-Z", "--restricted", N_("Restricted mode"));
    print1opt("-c", "--const", N_("Constantly show cursor position"));
803
#ifndef NANO_SMALL
804
805
806
    print1opt("-d", "--rebinddelete", N_("Fix Backspace/Delete confusion problem"));
    print1opt("-i", "--autoindent", N_("Automatically indent new lines"));
    print1opt("-k", "--cut", N_("Cut from cursor to end of line"));
807
#endif
808
    print1opt("-l", "--nofollow", N_("Don't follow symbolic links, overwrite"));
809
#ifndef DISABLE_MOUSE
810
    print1opt("-m", "--mouse", N_("Enable mouse"));
Chris Allegretta's avatar
Chris Allegretta committed
811
#endif
812
#ifndef DISABLE_OPERATINGDIR
813
    print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), N_("Set operating directory"));
Chris Allegretta's avatar
Chris Allegretta committed
814
#endif
815
    print1opt("-p", "--preserve", N_("Preserve XON (^Q) and XOFF (^S) keys"));
816
#ifndef DISABLE_WRAPJUSTIFY
817
    print1opt(_("-r [#cols]"), _("--fill=[#cols]"), N_("Set fill cols to (wrap lines at) #cols"));
818
#endif
819
#ifndef DISABLE_SPELLER
820
    print1opt(_("-s [prog]"), _("--speller=[prog]"), N_("Enable alternate speller"));
821
#endif
822
823
    print1opt("-t", "--tempfile", N_("Auto save on exit, don't prompt"));
    print1opt("-v", "--view", N_("View (read only) mode"));
824
#ifndef DISABLE_WRAPPING
825
    print1opt("-w", "--nowrap", N_("Don't wrap long lines"));
Chris Allegretta's avatar
Chris Allegretta committed
826
#endif
827
828
    print1opt("-x", "--nohelp", N_("Don't show help window"));
    print1opt("-z", "--suspend", N_("Enable suspend"));
Chris Allegretta's avatar
Chris Allegretta committed
829

830
    /* This is a special case. */
Chris Allegretta's avatar
Chris Allegretta committed
831
    printf(" %s\t\t\t%s\n","-a, -b, -e, -f, -g, -j", _("(ignored, for Pico compatibility)"));
832

Chris Allegretta's avatar
Chris Allegretta committed
833
834
835
    exit(0);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
836
void version(void)
Chris Allegretta's avatar
Chris Allegretta committed
837
{
Chris Allegretta's avatar
Chris Allegretta committed
838
    printf(_(" GNU nano version %s (compiled %s, %s)\n"),
Chris Allegretta's avatar
Chris Allegretta committed
839
	   VERSION, __TIME__, __DATE__);
840
    printf(_
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
841
	   (" Email: nano@nano-editor.org	Web: http://www.nano-editor.org/"));
842
    printf(_("\n Compiled options:"));
843

844
845
846
#ifndef ENABLE_NLS
    printf(" --disable-nls");
#endif
847
848
849
#ifdef DEBUG
    printf(" --enable-debug");
#endif
850
851
#ifdef NANO_EXTRA
    printf(" --enable-extra");
852
#endif
853
854
855
#ifdef NANO_SMALL
    printf(" --enable-tiny");
#else
856
#ifdef DISABLE_BROWSER
857
    printf(" --disable-browser");
858
#endif
859
860
#ifdef DISABLE_HELP
    printf(" --disable-help");
861
862
#endif
#ifdef DISABLE_JUSTIFY
863
    printf(" --disable-justify");
864
#endif
865
#ifdef DISABLE_MOUSE
866
    printf(" --disable-mouse");
867
#endif
868
869
870
#ifdef DISABLE_OPERATINGDIR
    printf(" --disable-operatingdir");
#endif
871
872
873
874
875
876
877
878
879
880
#ifdef DISABLE_SPELLER
    printf(" --disable-speller");
#endif
#ifdef DISABLE_TABCOMP
    printf(" --disable-tabcomp");
#endif
#endif /* NANO_SMALL */
#ifdef DISABLE_WRAPPING
    printf(" --disable-wrapping");
#endif
881
882
883
#ifdef DISABLE_ROOTWRAP
    printf(" --disable-wrapping-as-root");
#endif
884
885
886
887
888
889
890
891
892
#ifdef ENABLE_COLOR
    printf(" --enable-color");
#endif
#ifdef ENABLE_MULTIBUFFER
    printf(" --enable-multibuffer");
#endif
#ifdef ENABLE_NANORC
    printf(" --enable-nanorc");
#endif
893
894
895
896
#ifdef USE_SLANG
    printf(" --with-slang");
#endif
    printf("\n");
Chris Allegretta's avatar
Chris Allegretta committed
897
898
899
900
}

int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
901
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
902
903
}

904
void nano_disabled_msg(void)
905
{
Chris Allegretta's avatar
Chris Allegretta committed
906
    statusbar(_("Sorry, support for this function has been disabled"));
907
908
}

Chris Allegretta's avatar
Chris Allegretta committed
909
#ifndef NANO_SMALL
910
RETSIGTYPE cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
911
{
912
913
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
914
915
}

916
917
/* Return TRUE on success. */
bool open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
918
{
919
920
921
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
922
923
			/* Original and temporary handlers for
			 * SIGINT. */
924
925
926
    bool sig_failed = FALSE;
    /* sig_failed means that sigaction() failed without changing the
     * signal handlers.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
927
     *
928
929
     * We use this variable since it is important to put things back
     * when we finish, even if we get errors. */
Chris Allegretta's avatar
Chris Allegretta committed
930

931
    /* Make our pipes. */
932

933
934
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
935
	return FALSE;
936
    }
937

938
    /* Fork a child. */
939

940
941
942
943
944
    if ((pid = fork()) == 0) {
	close(fd[0]);
	dup2(fd[1], fileno(stdout));
	dup2(fd[1], fileno(stderr));
	/* If execl() returns at all, there was an error. */
945

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
946
	execl("/bin/sh", "sh", "-c", command, 0);
947
948
	exit(0);
    }
949

950
951
952
953
954
955
956
    /* Else continue as parent. */

    close(fd[1]);

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

960
961
    /* Before we start reading the forked command's output, we set
     * things up so that ^C will cancel the new process. */
962

963
964
    /* Enable interpretation of the special control keys so that we get
     * SIGINT when Ctrl-C is pressed. */
965
966
    enable_signals();

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
967
    if (sigaction(SIGINT, NULL, &newaction) == -1) {
968
	sig_failed = TRUE;
969
	nperror("sigaction");
Chris Allegretta's avatar
Chris Allegretta committed
970
    } else {
971
	newaction.sa_handler = cancel_fork;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
972
	if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
973
	    sig_failed = TRUE;
974
975
	    nperror("sigaction");
	}
Chris Allegretta's avatar
Chris Allegretta committed
976
    }
977
978
    /* Note that now oldaction is the previous SIGINT signal handler,
     * to be restored later. */
979

980
    f = fdopen(fd[0], "rb");
981
    if (f == NULL)
982
	nperror("fdopen");
983

984
    read_file(f, "stdin");
985
986
    /* If multibuffer mode is on, we could be here in view mode.  If so,
     * don't set the modification flag. */
987
988
989
990
991
992
    if (!ISSET(VIEW_MODE))
	set_modified();

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

993
    if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
994
995
	nperror("sigaction");

996
997
    /* Disable interpretation of the special control keys so that we can
     * use Ctrl-C for other things. */
998
999
    disable_signals();

1000
    return TRUE;
1001
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1002
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1003

1004
/* The user typed a character; add it to the edit buffer. */
1005
1006
1007
1008
void do_char(char ch)
{
    size_t current_len = strlen(current->data);
#if !defined(DISABLE_WRAPPING) || defined(ENABLE_COLOR)
1009
    bool do_refresh = FALSE;
1010
	/* Do we have to call edit_refresh(), or can we get away with
1011
1012
	 * update_line()? */
#endif
1013

1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
    if (ch == '\0')		/* Null to newline, if needed. */
	ch = '\n';
    else if (ch == '\n') {	/* Newline to Enter, if needed. */
	do_enter();
	return;
    }

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

    /* When a character is inserted on the current magicline, it means
     * we need a new one! */
1025
    if (filebot == current)
1026
	new_magicline();
Chris Allegretta's avatar
Chris Allegretta committed
1027

1028
    /* More dangerousness fun =) */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1029
    current->data = charealloc(current->data, current_len + 2);
1030
    assert(current_x <= current_len);
1031
1032
    charmove(&current->data[current_x + 1], &current->data[current_x],
	current_len - current_x + 1);
1033
1034
1035
    current->data[current_x] = ch;
    totsize++;
    set_modified();
1036

Chris Allegretta's avatar
Chris Allegretta committed
1037
#ifndef NANO_SMALL
1038
    /* Note that current_x has not yet been incremented. */
1039
1040
    if (current == mark_beginbuf && current_x < mark_beginx)
	mark_beginx++;
Chris Allegretta's avatar
Chris Allegretta committed
1041
#endif
1042

1043
    do_right(FALSE);
1044

1045
#ifndef DISABLE_WRAPPING
1046
    /* If we're wrapping text, we need to call edit_refresh(). */
1047
    if (!ISSET(NO_WRAP) && ch != '\t')
1048
	do_refresh = do_wrap(current);
1049
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1050

1051
#ifdef ENABLE_COLOR
1052
1053
    /* If color syntaxes are turned on, we need to call
     * edit_refresh(). */
1054
    if (ISSET(COLOR_SYNTAX))
1055
	do_refresh = TRUE;
1056
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1057

1058
#if !defined(DISABLE_WRAPPING) || defined(ENABLE_COLOR)
1059
    if (do_refresh)
1060
	edit_refresh();
1061
    else
1062
#endif
1063
	update_line(current, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
1064
1065
}

1066
void do_verbatim_input(void)
1067
{
1068
1069
    int *v_kbinput = NULL;	/* Used to hold verbatim input. */
    size_t v_len;		/* Length of verbatim input. */
1070
    size_t i;
1071
1072

    statusbar(_("Verbatim input"));
1073

1074
    v_kbinput = get_verbatim_kbinput(edit, ERR, v_kbinput, &v_len, TRUE);
1075
1076
1077
1078
1079

    /* Turn on DISABLE_CURPOS while inserting character(s) and turn it
     * off afterwards, so that if constant cursor position display is
     * on, it will be updated properly. */
    SET(DISABLE_CURPOS);
1080
1081
    for (i = 0; i < v_len; i++)
	do_char((char)v_kbinput[i]);
1082
1083
    UNSET(DISABLE_CURPOS);

1084
    free(v_kbinput);
1085
1086
}

1087
void do_backspace(void)
Chris Allegretta's avatar
Chris Allegretta committed
1088
{
1089
    if (current != fileage || current_x > 0) {
1090
	do_left(FALSE);
1091
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1092
1093
1094
    }
}

1095
void do_delete(void)
Chris Allegretta's avatar
Chris Allegretta committed
1096
{
1097
    bool do_refresh = FALSE;
1098
1099
1100
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

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

1104
    placewewant = xplustabs();
1105

1106
1107
    if (current->data[current_x] != '\0') {
	size_t linelen = strlen(current->data + current_x);
1108

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

1111
	/* Let's get dangerous. */
1112
	charmove(&current->data[current_x], &current->data[current_x + 1],
1113
		linelen);
Chris Allegretta's avatar
Chris Allegretta committed
1114

1115
1116
1117
1118
	null_at(&current->data, linelen + current_x - 1);
#ifndef NANO_SMALL
	if (current_x < mark_beginx && mark_beginbuf == current)
	    mark_beginx--;
Chris Allegretta's avatar
Chris Allegretta committed
1119
#endif
1120
1121
    } else if (current != filebot && (current->next != filebot ||
	current->data[0] == '\0')) {
1122
	/* We can delete the line before filebot only if it is blank: it
1123
	 * becomes the new magicline then. */
1124
	filestruct *foo = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1125

1126
	assert(current_x == strlen(current->data));
1127
1128
1129
1130
1131
1132

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

1133
1134
1135
1136
1137
1138
1139
1140
1141
	current->data = charealloc(current->data, current_x +
		strlen(foo->data) + 1);
	strcpy(current->data + current_x, foo->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == current->next) {
	    mark_beginx += current_x;
	    mark_beginbuf = current;
	}
#endif
1142
	if (filebot == foo)
Chris Allegretta's avatar
Chris Allegretta committed
1143
1144
1145
1146
1147
1148
	    filebot = current;

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
1149
#ifndef DISABLE_WRAPPING
1150
	wrap_reset();
1151
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1152
    } else
1153
	return;
Chris Allegretta's avatar
Chris Allegretta committed
1154
1155
1156

    totsize--;
    set_modified();
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168

#ifdef ENABLE_COLOR
    /* If color syntaxes are turned on, we need to call
     * edit_refresh(). */
    if (ISSET(COLOR_SYNTAX))
	do_refresh = TRUE;
#endif

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

1171
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1172
{
1173
    do_char('\t');
Chris Allegretta's avatar
Chris Allegretta committed
1174
1175
}

1176
/* Someone hits return *gasp!* */
1177
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1178
{
1179
1180
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1181

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1184
#ifndef NANO_SMALL
1185
1186
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1187
1188
1189
1190
1191
	/* 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)
1192
1193
	    extra = current_x;
	totsize += extra;
1194
1195
1196
1197
1198
1199
1200
    }
#endif
    newnode->data = charalloc(strlen(current->data + current_x) +
	extra + 1);
    strcpy(&newnode->data[extra], current->data + current_x);
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT))
1201
1202
	strncpy(newnode->data, current->data, extra);
#endif
1203
1204
1205
1206
1207
    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;
1208
    }
1209
1210
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1211

1212
    if (current == filebot)
1213
1214
	filebot = newnode;
    splice_node(current, newnode, current->next);
1215

1216
1217
1218
    totsize++;
    renumber(current);
    current = newnode;
1219
1220

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

1222
1223
1224
    totlines++;
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1225
1226
}

1227
#ifndef NANO_SMALL
1228
void do_next_word(void)
Chris Allegretta's avatar
Chris Allegretta committed
1229
{
1230
    size_t old_pww = placewewant;
1231
    const filestruct *old_current = current;
1232
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1233

1234
1235
    /* Skip letters in this word first. */
    while (current->data[current_x] != '\0' &&
1236
	isalnum(current->data[current_x]))
1237
	current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1238

1239
1240
    for (; current != NULL; current = current->next) {
	while (current->data[current_x] != '\0' &&
1241
		!isalnum(current->data[current_x]))
1242
	    current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1243

1244
1245
	if (current->data[current_x] != '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1246

1247
1248
1249
1250
	current_x = 0;
    }
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1251

1252
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1253

1254
1255
    /* Update the screen. */
    edit_redraw(old_current, old_pww);
1256
}
Chris Allegretta's avatar
Chris Allegretta committed
1257

1258
/* The same thing for backwards. */
1259
void do_prev_word(void)
1260
{
1261
    size_t old_pww = placewewant;
1262
    const filestruct *old_current = current;
1263
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1264

1265
1266
    current_x++;

1267
    /* Skip letters in this word first. */
1268
    while (current_x > 0 && isalnum(current->data[current_x - 1]))
1269
	current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1270

1271
    for (; current != NULL; current = current->prev) {
1272
	while (current_x > 0 && !isalnum(current->data[current_x - 1]))
1273
	    current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1274

1275
	if (current_x > 0)
1276
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1277

1278
	if (current->prev != NULL)
1279
	    current_x = strlen(current->prev->data) + 1;
Chris Allegretta's avatar
Chris Allegretta committed
1280
1281
    }

1282
1283
    current_x--;

1284
    if (current == NULL) {
1285
1286
	current = fileage;
	current_x = 0;
1287
    } else {
1288
	while (current_x > 0 && isalnum(current->data[current_x - 1]))
1289
	    current_x--;
1290
    }
Chris Allegretta's avatar
Chris Allegretta committed
1291

1292
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1293

1294
1295
    /* Update the screen. */
    edit_redraw(old_current, old_pww);
1296
}
Chris Allegretta's avatar
Chris Allegretta committed
1297

1298
void do_mark(void)
1299
{
1300
1301
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1302
1303
1304
1305
1306
1307
1308
1309
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1310
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1311

1312
#ifndef DISABLE_WRAPPING
1313
1314
void wrap_reset(void)
{
1315
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1316
}
1317
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1318

1319
#ifndef DISABLE_WRAPPING
1320
1321
1322
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1323
bool do_wrap(filestruct *inptr)
Chris Allegretta's avatar
Chris Allegretta committed
1324
{
1325
1326
1327
1328
1329
1330
    size_t len = strlen(inptr->data);
	/* Length of the line we wrap. */
    size_t i = 0;
	/* Generic loop variable. */
    int wrap_loc = -1;
	/* Index of inptr->data where we wrap. */
1331
    int word_back = -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1332
#ifndef NANO_SMALL
1333
    const char *indentation = NULL;
1334
	/* Indentation to prepend to the new line. */
1335
    size_t indent_len = 0;	/* strlen(indentation) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1336
#endif
1337
1338
    const char *after_break;	/* Text after the wrap point. */
    size_t after_break_len;	/* strlen(after_break) */
1339
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1340
    const char *wrap_line = NULL;
1341
	/* The next line, minus indentation. */
1342
1343
1344
    size_t wrap_line_len = 0;	/* strlen(wrap_line) */
    char *newline = NULL;	/* The line we create. */
    size_t new_line_len = 0;	/* Eventual length of newline. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1345

1346
1347
/* There are three steps.  First, we decide where to wrap.  Then, we
 * create the new wrap line.  Finally, we clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
1348

1349
/* Step 1, finding where to wrap.  We are going to add a new line
1350
 * after a whitespace character.  In this step, we set wrap_loc as the
1351
1352
1353
1354
1355
1356
 * location of this replacement.
 *
 * Where should we break the line?  We need the last "legal wrap point"
 * such that the last word before it ended at or before fill.  If there
 * is no such point, we settle for the first legal wrap point.
 *
1357
1358
 * A "legal wrap point" is a whitespace character that is not followed
 * by whitespace.
1359
1360
1361
1362
1363
1364
1365
 *
 * If there is no legal wrap point or we found the last character of the
 * line, we should return without wrapping.
 *
 * Note that the initial indentation does not count as a legal wrap
 * point if we are going to auto-indent!
 *
1366
1367
 * Note that the code below could be optimized, by not calling
 * strnlenpt() so often. */
1368

1369
1370
1371
1372
1373
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT))
	i = indent_length(inptr->data);
#endif
    wrap_line = inptr->data + i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1374
    for (; i < len; i++, wrap_line++) {
1375
	/* Record where the last word ended. */
1376
	if (!isblank(*wrap_line))
1377
	    word_back = i;
1378
1379
	/* If we have found a "legal wrap point" and the current word
	 * extends too far, then we stop. */
1380
1381
	if (wrap_loc != -1 && strnlenpt(inptr->data, word_back + 1) > fill)
	    break;
1382
	/* We record the latest "legal wrap point". */
1383
	if (word_back != i && !isblank(wrap_line[1]))
1384
	    wrap_loc = i;
1385
    }
1386
1387
    if (i == len)
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1388

1389
1390
1391
    /* Step 2, making the new wrap line.  It will consist of indentation
     * + after_break + " " + wrap_line (although indentation and
     * wrap_line are conditional on flags and #defines). */
Chris Allegretta's avatar
Chris Allegretta committed
1392

1393
1394
1395
1396
    /* after_break is the text that will be moved to the next line. */
    after_break = inptr->data + wrap_loc + 1;
    after_break_len = len - wrap_loc - 1;
    assert(after_break_len == strlen(after_break));
Chris Allegretta's avatar
Chris Allegretta committed
1397

1398
1399
1400
    /* new_line_len will later be increased by the lengths of indentation
     * and wrap_line. */
    new_line_len = after_break_len;
Chris Allegretta's avatar
Chris Allegretta committed
1401

1402
1403
1404
    /* We prepend the wrapped text to the next line, if the flag is set,
     * and there is a next line, and prepending would not make the line
     * too long. */
1405
    if (same_line_wrap && inptr->next) {
1406
1407
	wrap_line = inptr->next->data;
	wrap_line_len = strlen(wrap_line);
Chris Allegretta's avatar
Chris Allegretta committed
1408

1409
	/* +1 for the space between after_break and wrap_line. */
1410
	if ((new_line_len + 1 + wrap_line_len) <= fill) {
1411
	    wrapping = TRUE;
1412
1413
1414
	    new_line_len += (1 + wrap_line_len);
	}
    }
1415

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1416
#ifndef NANO_SMALL
1417
    if (ISSET(AUTOINDENT)) {
1418
1419
	/* Indentation comes from the next line if wrapping, else from
	 * this line. */
1420
1421
1422
	indentation = (wrapping ? wrap_line : inptr->data);
	indent_len = indent_length(indentation);
	if (wrapping)
1423
1424
	    /* The wrap_line text should not duplicate indentation.
	     * Note in this case we need not increase new_line_len. */
1425
1426
1427
	    wrap_line += indent_len;
	else
	    new_line_len += indent_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1428
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1429
1430
#endif

1431
1432
    /* Now we allocate the new line and copy into it. */
    newline = charalloc(new_line_len + 1);  /* +1 for \0 */
1433
    new_line_len = 0;
1434
    *newline = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1435

1436
1437
1438
1439
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
	strncpy(newline, indentation, indent_len);
	newline[indent_len] = '\0';
1440
	new_line_len = indent_len;
1441
    }
1442
#endif
1443
    strcat(newline, after_break);
1444
1445
1446
    new_line_len += after_break_len;
    /* We end the old line after wrap_loc.  Note that this does not eat
     * the space. */
1447
1448
    null_at(&inptr->data, wrap_loc + 1);
    totsize++;
1449
    if (wrapping) {
1450
	/* In this case, totsize increases by 1 since we add a space
1451
	 * between after_break and wrap_line.  If the line already ends
1452
1453
	 * in a tab or a space, we don't add a space and decrement
	 * totsize to account for that. */
1454
	if (!isblank(newline[new_line_len - 1]))
1455
1456
1457
	    strcat(newline, " ");
	else
	    totsize--;
1458
1459
1460
1461
1462
	strcat(newline, wrap_line);
	free(inptr->next->data);
	inptr->next->data = newline;
    } else {
	filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct));
1463

1464
1465
	/* In this case, the file size changes by +1 for the new line,
	 * and +indent_len for the new indentation. */
1466
1467
1468
1469
1470
1471
1472
1473
#ifndef NANO_SMALL
	totsize += indent_len;
#endif
	totlines++;
	temp->data = newline;
	temp->prev = inptr;
	temp->next = inptr->next;
	temp->prev->next = temp;
1474
1475
1476
	/* If temp->next is NULL, then temp is the last line of the
	 * file, so we must set filebot. */
	if (temp->next != NULL)
1477
1478
1479
1480
	    temp->next->prev = temp;
	else
	    filebot = temp;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1481

1482
1483
    /* Step 3, clean up.  Here we reposition the cursor and mark, and do
     * some other sundry things. */
Chris Allegretta's avatar
Chris Allegretta committed
1484

1485
    /* Later wraps of this line will be prepended to the next line. */
1486
    same_line_wrap = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
1487

1488
1489
1490
1491
    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
    if (!wrapping)
	renumber(inptr);
Chris Allegretta's avatar
Chris Allegretta committed
1492

1493
1494
1495
1496
1497
1498
    /* If the cursor was after the break point, we must move it. */
    if (current_x > wrap_loc) {
	current = current->next;
	current_x -=
#ifndef NANO_SMALL
		-indent_len +
1499
#endif
1500
1501
1502
1503
		wrap_loc + 1;
	wrap_reset();
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1504

1505
#ifndef NANO_SMALL
1506
1507
    /* If the mark was on this line after the wrap point, we move it
     * down.  If it was on the next line and we wrapped, we move it
1508
1509
1510
1511
1512
1513
1514
     * right. */
    if (mark_beginbuf == inptr && mark_beginx > wrap_loc) {
	mark_beginbuf = inptr->next;
	mark_beginx -= wrap_loc - indent_len + 1;
    } else if (wrapping && mark_beginbuf == inptr->next)
	mark_beginx += after_break_len;
#endif /* !NANO_SMALL */
1515

1516
    return TRUE;
1517
}
1518
#endif /* !DISABLE_WRAPPING */
1519

1520
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1521
/* A word is misspelled in the file.  Let the user replace it.  We
1522
1523
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1524
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1525
    char *save_search, *save_replace;
1526
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1527
    filestruct *edittop_save = edittop, *current_save = current;
1528
	/* Save where we are. */
1529
    bool canceled = FALSE;
1530
	/* The return value. */
1531
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1532
#ifndef NANO_SMALL
1533
1534
    bool reverse_search_set = ISSET(REVERSE_SEARCH);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1535
#ifdef HAVE_REGEX_H
1536
1537
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1538
1539
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1540
1541
    filestruct *top, *bot;
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1542
#endif
1543

1544
    /* Make sure spell-check is case sensitive. */
1545
    SET(CASE_SENSITIVE);
1546

1547
#ifndef NANO_SMALL
1548
1549
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1550
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1551
#ifdef HAVE_REGEX_H
1552
1553
1554
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1555

1556
    /* Save the current search/replace strings. */
1557
1558
1559
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1560

1561
    /* Set search/replace strings to misspelled word. */
1562
1563
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1564

1565
1566
#ifndef NANO_SMALL
    if (old_mark_set) {
1567
	/* If the mark is on, partition the filestruct so that it
1568
	 * contains only the marked text, and turn the mark off. */
1569
1570
1571
1572
1573
1574
1575
	mark_order((const filestruct **)&top, &top_x,
	    (const filestruct **)&bot, &bot_x);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	UNSET(MARK_ISSET);
    }
#endif

1576
    /* Start from the top of the file. */
1577
    edittop = fileage;
1578
    current = fileage;
1579
    current_x = (size_t)-1;
1580
    placewewant = 0;
1581

1582
    /* Find the first whole-word occurrence of word. */
1583
    findnextstr_wrap_reset();
1584
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) {
1585
1586
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
1587

1588
	    do_replace_highlight(TRUE, word);
1589

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1590
	    /* Allow all instances of the word to be corrected. */
1591
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
1592
#ifndef NANO_SMALL
1593
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
1594
#endif
1595
			 _("Edit a replacement")) == -1);
1596

1597
	    do_replace_highlight(FALSE, word);
1598

1599
	    if (!canceled && strcmp(word, answer) != 0) {
1600
1601
1602
1603
		bool added_magicline = (filebot->data[0] != '\0');
			/* Whether we added a magicline after
			 * filebot. */

1604
		current_x--;
1605
1606
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
1607
1608
1609
1610

		/* If we added a magicline, remove it now. */
		if (added_magicline)
		    remove_magicline();
1611
	    }
1612
1613

	    break;
1614
	}
1615
    }
Chris Allegretta's avatar
Chris Allegretta committed
1616

1617
1618
1619
1620
1621
    /* 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
1622

1623
1624
#ifndef NANO_SMALL
    if (old_mark_set) {
1625
1626
	/* If the mark was on, unpartition the filestruct so that it
	 * contains all the text again, and turn the mark back on. */
1627
1628
1629
1630
1631
	unpartition_filestruct(filepart);
	SET(MARK_ISSET);
    }
#endif

1632
    /* Restore where we were. */
1633
    edittop = edittop_save;
1634
1635
    current = current_save;
    current_x = current_x_save;
1636
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
1637

1638
    /* Restore case sensitivity setting. */
1639
1640
1641
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

1642
#ifndef NANO_SMALL
1643
1644
1645
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
1646
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1647
#ifdef HAVE_REGEX_H
1648
1649
1650
1651
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
1652

1653
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
1654
1655
}

1656
1657
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
1658
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
1659
{
1660
1661
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1662
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1663
1664
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
1665

1666
    /* Create all three pipes up front. */
1667
1668
    if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1)
	return _("Could not create pipe");
Chris Allegretta's avatar
Chris Allegretta committed
1669

1670
    statusbar(_("Creating misspelled word list, please wait..."));
1671

1672
    /* A new process to run spell in. */
1673
    if ((pid_spell = fork()) == 0) {
1674

1675
	/* Child continues (i.e, future spell process). */
1676

1677
	close(spell_fd[0]);
1678

1679
	/* Replace the standard input with the temp file. */
1680
1681
1682
1683
1684
1685
	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;

1686
	close(tempfile_fd);
1687

1688
	/* Send spell's standard output to the pipe. */
1689
1690
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1691

1692
	close(spell_fd[1]);
1693

1694
	/* Start spell program; we are using PATH. */
1695
	execlp("spell", "spell", NULL);
1696

1697
	/* Should not be reached, if spell is found. */
1698
	exit(1);
1699
    }
Chris Allegretta's avatar
Chris Allegretta committed
1700

1701
    /* Parent continues here. */
1702
1703
    close(spell_fd[1]);

1704
    /* A new process to run sort in. */
1705
1706
    if ((pid_sort = fork()) == 0) {

1707
1708
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
1709
1710
1711
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1712
1713
	close(spell_fd[0]);

1714
	/* Send sort's standard output to the new pipe. */
1715
1716
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1717
1718
1719

	close(sort_fd[1]);

1720
1721
1722
	/* Start sort program.  Use -f to remove mixed case without
	 * having to have ANOTHER pipe for tr.  If this isn't portable,
	 * let me know. */
1723
1724
	execlp("sort", "sort", "-f", NULL);

1725
	/* Should not be reached, if sort is found. */
1726
1727
1728
	exit(1);
    }

1729
    close(spell_fd[0]);
1730
1731
    close(sort_fd[1]);

1732
    /* A new process to run uniq in. */
1733
1734
    if ((pid_uniq = fork()) == 0) {

1735
1736
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
1737
1738
1739
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1740
1741
	close(sort_fd[0]);

1742
	/* Send uniq's standard output to the new pipe. */
1743
1744
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1745
1746
1747

	close(uniq_fd[1]);

1748
	/* Start uniq program; we are using PATH. */
1749
1750
	execlp("uniq", "uniq", NULL);

1751
	/* Should not be reached, if uniq is found. */
1752
1753
1754
	exit(1);
    }

1755
    close(sort_fd[0]);
1756
    close(uniq_fd[1]);
1757

1758
    /* Child process was not forked successfully. */
1759
1760
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
1761
	return _("Could not fork");
1762
    }
1763

1764
    /* Get system pipe buffer size. */
1765
1766
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
1767
	return _("Could not get size of pipe buffer");
1768
    }
Chris Allegretta's avatar
Chris Allegretta committed
1769

1770
    /* Read in the returned spelling errors. */
1771
1772
1773
    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
1774

1775
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
1776
1777
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1778
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
1779
	read_buff_ptr += read_buff_read;
1780

1781
    }
Chris Allegretta's avatar
Chris Allegretta committed
1782

1783
    *read_buff_ptr = (char)NULL;
1784
    close(uniq_fd[0]);
1785

1786
    /* Process the spelling errors. */
1787
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
1788

1789
    while (*read_buff_ptr != '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1790

1791
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
1792
	    *read_buff_ptr = (char)NULL;
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
	    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
1803

1804
    /* Special case where last word doesn't end with \n or \r. */
1805
1806
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
1807

1808
1809
    free(read_buff);
    replace_abort();
1810
    edit_refresh();
1811

1812
    /* Process end of spell process. */
1813
1814
1815
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
1816

1817
1818
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
1819

1820
1821
1822
1823
1824
1825
1826
1827
    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;
1828

1829
  close_pipes_and_exit:
1830

1831
    /* Don't leak any handles. */
1832
1833
1834
1835
1836
1837
1838
1839
    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
1840
1841
}

1842
1843
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
1844
const char *do_alt_speller(char *tempfile_name)
1845
{
1846
    int alt_spell_status, lineno_cur = current->lineno;
1847
1848
    size_t x_cur = current_x, pww_cur = placewewant;
    int y_cur = current_y;
1849
1850
1851
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
1852
1853
    static char **spellargs = NULL;
    FILE *f;
1854
#ifndef NANO_SMALL
1855
    bool old_mark_set = ISSET(MARK_ISSET);
1856
1857
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1858
1859
    int mbb_lineno_cur = 0;
	/* We're going to close the current file, and open the output of
1860
1861
1862
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
1863
1864
1865
1866
1867
1868
    int old_totlines = totlines;
	/* Our saved value of totlines, used when we spell-check a
	 * marked selection. */
    long old_totsize = totsize;
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
1869

1870
    if (old_mark_set) {
1871
1872
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
1873
1874
1875
	mbb_lineno_cur = mark_beginbuf->lineno;
	UNSET(MARK_ISSET);
    }
1876
#endif
1877

1878
    endwin();
1879

1880
    /* Set up an argument list to pass execvp(). */
1881
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1882
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
1883

1884
1885
1886
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1887
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
1888
1889
1890
1891
1892
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
1893

1894
    /* Start a new process for the alternate speller. */
1895
    if ((pid_spell = fork()) == 0) {
1896
	/* Start alternate spell program; we are using PATH. */
1897
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
1898

1899
1900
1901
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
1902

1903
1904
    /* Could not fork?? */
    if (pid_spell < 0)
1905
	return _("Could not fork");
1906

1907
    /* Wait for alternate speller to complete. */
1908
    wait(&alt_spell_status);
1909

1910
1911
1912
1913
1914
1915
1916
1917
1918
    if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0) {
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
1919
1920

    refresh();
1921

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

1925
#ifndef NANO_SMALL
1926
    if (old_mark_set) {
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
	filestruct *top, *bot;
	size_t top_x, bot_x;
	int part_totlines;
	long part_totsize;

	/* 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,
		(const filestruct **)&bot, &bot_x);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	added_magicline = (filebot->data[0] != '\0');

	/* Get the number of lines and the number of characters in the
	 * marked text, and subtract them from the saved values of
	 * totlines and totsize. */
	get_totals(top, bot, &part_totlines, &part_totsize);
	old_totlines -= part_totlines;
	old_totsize -= part_totsize;
    }
#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
1963
	filestruct *top_save = fileage;
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979

	/* If we added a magicline, remove it now. */
	if (added_magicline)
	    remove_magicline();

	/* If the mark was on, 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. */
	unpartition_filestruct(filepart);

	/* Renumber starting with the beginning line of the old
	 * partition.  Also add the number of lines and characters in
	 * the spell-checked marked text to the saved values of totlines
	 * and totsize, and then make those saved values the actual
	 * values. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1980
	renumber(top_save);
1981
1982
1983
1984
1985
1986
1987
	old_totlines += totlines;
	old_totsize += totsize;
	totlines = old_totlines;
	totsize = old_totsize;

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
1988
1989
	do_gotopos(mbb_lineno_cur, mark_beginx, y_cur, 0);
	mark_beginbuf = current;
1990
1991
1992
1993

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

1996
1997
	/* Turn the mark back on. */
	SET(MARK_ISSET);
1998
1999
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2000

2001
2002
    /* Go back to the old position, mark the file as modified, and make
     * sure that the titlebar is refreshed. */
2003
2004
2005
2006
    do_gotopos(lineno_cur, x_cur, y_cur, pww_cur);
    set_modified();
    clearok(topwin, FALSE);
    titlebar(NULL);
2007

2008
    return NULL;
2009
}
2010

2011
void do_spell(void)
2012
{
2013
    int i;
2014
2015
    char *temp = safe_tempnam(0, "nano.");
    const char *spell_msg;
2016

2017
2018
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2019
	return;
2020
    }
2021

2022
2023
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2024
	i = write_marked(temp, TRUE, FALSE);
2025
2026
    else
#endif
2027
	i = write_file(temp, TRUE, FALSE, FALSE);
2028
2029

    if (i == -1) {
2030
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2031
	free(temp);
2032
	return;
2033
2034
    }

2035
#ifdef ENABLE_MULTIBUFFER
2036
2037
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2038
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2039
#endif
2040

2041
2042
    spell_msg = alt_speller != NULL ? do_alt_speller(temp) :
	do_int_speller(temp);
2043
    unlink(temp);
2044
    free(temp);
2045

2046
    if (spell_msg != NULL)
2047
2048
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2049
    else
2050
	statusbar(_("Finished checking spelling"));
2051
}
2052
#endif /* !DISABLE_SPELLER */
2053

2054
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2055
2056
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2057
2058
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2059
    size_t len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2060

Chris Allegretta's avatar
Chris Allegretta committed
2061
    assert(line != NULL);
2062
    while (isblank(*line)) {
Chris Allegretta's avatar
Chris Allegretta committed
2063
2064
	line++;
	len++;
Chris Allegretta's avatar
Chris Allegretta committed
2065
    }
Chris Allegretta's avatar
Chris Allegretta committed
2066
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2067
}
2068
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2069

Chris Allegretta's avatar
Chris Allegretta committed
2070
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2071
/* justify_format() replaces Tab by Space and multiple spaces by 1
2072
2073
2074
 * (except it maintains 2 after a non-repeated character in punct
 * followed by a character in brackets).  Note that the terminating \0
 * counts as a space.
Chris Allegretta's avatar
Chris Allegretta committed
2075
 *
2076
2077
 * justify_format() might make line->data shorter, and change the actual
 * pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2078
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2079
 * justify_format() will not look at the first skip characters of line.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2080
2081
 * skip should be at most strlen(line->data).  The character at
 * line[skip + 1] must not be whitespace. */
2082
void justify_format(filestruct *line, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2083
{
Chris Allegretta's avatar
Chris Allegretta committed
2084
2085
2086
2087
2088
    char *back, *front;

    /* These four asserts are assumptions about the input data. */
    assert(line != NULL);
    assert(line->data != NULL);
2089
    assert(skip < strlen(line->data));
2090
    assert(!isblank(line->data[skip]));
Chris Allegretta's avatar
Chris Allegretta committed
2091
2092

    back = line->data + skip;
2093
    for (front = back; ; front++) {
2094
	bool remove_space = FALSE;
2095
2096
	    /* Do we want to remove this space? */

2097
	if (*front == '\t')
Chris Allegretta's avatar
Chris Allegretta committed
2098
	    *front = ' ';
2099

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2100
2101
	/* These tests are safe since line->data + skip is not a
	 * space. */
2102
	if ((*front == '\0' || *front == ' ') && *(front - 1) == ' ') {
2103
	    const char *bob = back - 2;
2104

2105
	    remove_space = TRUE;
2106
	    for (; bob >= line->data + skip; bob--) {
2107
		if (strchr(punct, *bob) != NULL) {
2108
2109
2110
2111
2112
		    /* If this character is in punct, don't remove the
		     * space unless this character and the character
		     * before it are the same. */
		    remove_space = (bob > line->data + skip &&
			*bob == *(bob - 1));
2113
2114
2115
2116
2117
2118
2119
2120
		    break;
		}
		if (strchr(brackets, *bob) == NULL)
		    break;
	    }
	}

	if (remove_space) {
Chris Allegretta's avatar
Chris Allegretta committed
2121
	    /* Now *front is a space we want to remove.  We do that by
2122
	     * simply failing to assign it to *back. */
Chris Allegretta's avatar
Chris Allegretta committed
2123
2124
2125
2126
#ifndef NANO_SMALL
	    if (mark_beginbuf == line && back - line->data < mark_beginx)
		mark_beginx--;
#endif
2127
2128
	    if (*front == '\0')
		*(back - 1) = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
2131
2132
	} else {
	    *back = *front;
	    back++;
	}
2133
2134
	if (*front == '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2135
2136
    }

2137
    back--;
2138
    assert(*back == '\0' && *front == '\0');
Chris Allegretta's avatar
Chris Allegretta committed
2139

Chris Allegretta's avatar
Chris Allegretta committed
2140
2141
    /* Now back is the new end of line->data. */
    if (back != front) {
2142
	totsize -= front - back;
Chris Allegretta's avatar
Chris Allegretta committed
2143
2144
2145
2146
2147
	null_at(&line->data, back - line->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == line && back - line->data < mark_beginx)
	    mark_beginx = back - line->data;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2148
    }
Chris Allegretta's avatar
Chris Allegretta committed
2149
}
Chris Allegretta's avatar
Chris Allegretta committed
2150

Chris Allegretta's avatar
Chris Allegretta committed
2151
2152
2153
2154
2155
2156
/* 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. */
2157
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2158
{
2159
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2160
    regmatch_t matches;
2161
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2162

2163
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2164
	return 0;
2165
2166
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2167
2168
2169
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2170

2171
    /* Compute quote depth level. */
2172
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2173
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2174
2175
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2176
}
Chris Allegretta's avatar
Chris Allegretta committed
2177

Chris Allegretta's avatar
Chris Allegretta committed
2178
2179
2180
/* 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. */
2181
2182
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2183
{
Chris Allegretta's avatar
Chris Allegretta committed
2184
    /* Here is the assumption about a_quote: */
2185
2186
    assert(a_quote == quote_length(a_line));
    return a_quote == quote_length(b_line) &&
2187
	strncmp(a_line, b_line, a_quote) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2188
}
2189

2190
2191
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2192
bool indents_match(const char *a_line, size_t a_indent, const char
2193
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2194
{
Chris Allegretta's avatar
Chris Allegretta committed
2195
2196
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2197

2198
2199
    return b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2200
}
Chris Allegretta's avatar
Chris Allegretta committed
2201

2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
/* 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
 *	   AUTOINDENT is not set.
 *   The reason for number 5) is that if AUTOINDENT is not set, then an
 *   indented line is expected to start a paragraph, like in books.
 *   Thus, nano can justify an indented paragraph only if AUTOINDENT is
 *   turned on. */
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. */
void do_para_begin(void)
{
    const filestruct *old_current = current;
    const size_t old_pww = placewewant;

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2274
	    current_y--;
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
	} while (!begpar(current));
    }

    edit_redraw(old_current, old_pww);
}

bool inpar(const char *str)
{
    size_t quote_len = quote_length(str);

    return str[quote_len + indent_length(str + quote_len)] != '\0';
}

/* 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. */
void do_para_end(void)
{
    const filestruct *const old_current = current;
    const size_t old_pww = placewewant;

    current_x = 0;
    placewewant = 0;

    while (current->next != NULL && !inpar(current->data))
	current = current->next;

    while (current->next != NULL && inpar(current->next->data) &&
2303
	    !begpar(current->next)) {
2304
	current = current->next;
2305
2306
	current_y++;
    }
2307
2308
2309
2310
2311
2312
2313

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

    edit_redraw(old_current, old_pww);
}

Chris Allegretta's avatar
Chris Allegretta committed
2314
/* Put the next par_len lines, starting with first_line, in the cut
2315
2316
2317
 * buffer, not allowing them to be concatenated.  We assume there are
 * enough lines after first_line.  We leave copies of the lines in
 * place, too.  We return the new copy of first_line. */
2318
2319
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2320
{
2321
2322
2323
    /* We put the original lines, not copies, into the cutbuffer, just
     * out of a misguided sense of consistency, so if you uncut, you get
     * the actual same paragraph back, not a copy. */
2324
    filestruct *alice = first_line;
Chris Allegretta's avatar
Chris Allegretta committed
2325
2326

    set_modified();
2327
    cutbuffer = NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2328
    for (; par_len > 0; par_len--) {
2329
	filestruct *bob = copy_node(alice);
Chris Allegretta's avatar
Chris Allegretta committed
2330

2331
	if (alice == first_line)
Chris Allegretta's avatar
Chris Allegretta committed
2332
	    first_line = bob;
2333
	if (alice == current)
Chris Allegretta's avatar
Chris Allegretta committed
2334
	    current = bob;
2335
	if (alice == edittop)
Chris Allegretta's avatar
Chris Allegretta committed
2336
2337
	    edittop = bob;
#ifndef NANO_SMALL
2338
	if (alice == mark_beginbuf)
Chris Allegretta's avatar
Chris Allegretta committed
2339
2340
2341
	    mark_beginbuf = bob;
#endif

2342
	assert(alice != NULL && bob != NULL);
2343
	add_to_cutbuffer(alice, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
2344
	splice_node(bob->prev, bob, bob->next);
2345
	alice = bob->next;
Chris Allegretta's avatar
Chris Allegretta committed
2346
2347
2348
2349
    }
    return first_line;
}

Chris Allegretta's avatar
Chris Allegretta committed
2350
/* Is it possible to break line at or before goal? */
2351
bool breakable(const char *line, ssize_t goal)
Chris Allegretta's avatar
Chris Allegretta committed
2352
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2353
    for (; *line != '\0' && goal >= 0; line++) {
2354
	if (isblank(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2355
2356
	    return TRUE;

2357
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2358
2359
2360
2361
	    goal -= 2;
	else
	    goal -= 1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2362
2363
2364
    /* If goal is not negative, the whole line (one word) was short
     * enough. */
    return goal >= 0;
Chris Allegretta's avatar
Chris Allegretta committed
2365
2366
}

Chris Allegretta's avatar
Chris Allegretta committed
2367
/* We are trying to break a chunk off line.  We find the last space such
2368
 * that the display length to there is at most goal + 1.  If there is no
2369
2370
2371
 * such space, and force is TRUE, then we find the first space.  Anyway,
 * we then take the last space in that group of spaces.  The terminating
 * '\0' counts as a space. */
2372
int break_line(const char *line, ssize_t goal, bool force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2373
{
2374
    ssize_t space_loc = -1;
Chris Allegretta's avatar
Chris Allegretta committed
2375
2376
	/* Current tentative return value.  Index of the last space we
	 * found with short enough display width.  */
2377
    ssize_t cur_loc = 0;
2378
	/* Current index in line. */
Chris Allegretta's avatar
Chris Allegretta committed
2379
2380

    assert(line != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2381
    for (; *line != '\0' && goal >= 0; line++, cur_loc++) {
Chris Allegretta's avatar
Chris Allegretta committed
2382
2383
2384
2385
	if (*line == ' ')
	    space_loc = cur_loc;
	assert(*line != '\t');

Chris Allegretta's avatar
Chris Allegretta committed
2386
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
	    goal -= 2;
	else
	    goal--;
    }
    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;
    if (space_loc == -1) {
	/* No space found short enough. */
	if (force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2397
	    for (; *line != '\0'; line++, cur_loc++)
2398
		if (*line == ' ' && *(line + 1) != ' ' && *(line + 1) != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
2399
2400
2401
2402
		    return cur_loc;
	return -1;
    }
    /* Perhaps the character after space_loc is a space.  But because
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2403
     * of justify_format(), there can be only two adjacent. */
Chris Allegretta's avatar
Chris Allegretta committed
2404
2405
2406
2407
2408
2409
    if (*(line - cur_loc + space_loc + 1) == ' ' ||
	*(line - cur_loc + space_loc + 1) == '\0')
	space_loc++;
    return space_loc;
}

2410
2411
2412
2413
2414
/* 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
 * quote length and paragraph length in *quote and *par.  Return FALSE
 * if we found a paragraph, or TRUE if there was an error or we didn't
 * find a paragraph.
Chris Allegretta's avatar
Chris Allegretta committed
2415
 *
2416
2417
2418
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
bool do_para_search(size_t *const quote, size_t *const par)
2419
{
Chris Allegretta's avatar
Chris Allegretta committed
2420
    size_t quote_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2421
	/* Length of the initial quotation of the paragraph we
2422
	 * search. */
Chris Allegretta's avatar
Chris Allegretta committed
2423
2424
    size_t par_len;
	/* Number of lines in that paragraph. */
2425
2426
2427
2428
    size_t indent_len;
	/* Generic indentation length. */
    filestruct *line;
	/* Generic line of text. */
2429

Chris Allegretta's avatar
Chris Allegretta committed
2430
#ifdef HAVE_REGEX_H
2431
2432
2433
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
	return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
2434
    }
Chris Allegretta's avatar
Chris Allegretta committed
2435
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2436

Chris Allegretta's avatar
Chris Allegretta committed
2437
2438
    /* Here is an assumption that is always true anyway. */
    assert(current != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2439

2440
2441
    current_x = 0;

2442
    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2443
    indent_len = indent_length(current->data + quote_len);
Robert Siemborski's avatar
Robert Siemborski committed
2444

2445
2446
2447
2448
    /* Here we find the first line of the paragraph to search.  If the
     * current line is in a paragraph, then we move back to the first
     * line of the paragraph.  Otherwise, we move to the first line that
     * is in a paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2449
2450
    if (current->data[quote_len + indent_len] != '\0') {
	/* This line is part of a paragraph.  So we must search back to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2451
2452
	 * the first line of this paragraph.  First we check items 1)
	 * and 3) above. */
2453
2454
	while (current->prev != NULL &&	quotes_match(current->data,
		quote_len, current->prev->data)) {
2455
	    size_t temp_id_len =
2456
		indent_length(current->prev->data + quote_len);
2457
		/* The indentation length of the previous line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2458

2459
	    /* Is this line the beginning of a paragraph, according to
2460
	     * items 2), 5), or 4) above?  If so, stop. */
2461
	    if (current->prev->data[quote_len + temp_id_len] == '\0' ||
2462
		(quote_len == 0 && indent_len > 0
2463
#ifndef NANO_SMALL
2464
		&& !ISSET(AUTOINDENT)
2465
#endif
2466
2467
		) || !indents_match(current->prev->data + quote_len,
		temp_id_len, current->data + quote_len, indent_len))
2468
2469
2470
2471
		break;
	    indent_len = temp_id_len;
	    current = current->prev;
	    current_y--;
Chris Allegretta's avatar
Chris Allegretta committed
2472
	}
Chris Allegretta's avatar
Chris Allegretta committed
2473
    } else {
Chris Allegretta's avatar
Chris Allegretta committed
2474
2475
	/* This line is not part of a paragraph.  Move down until we get
	 * to a non "blank" line. */
Chris Allegretta's avatar
Chris Allegretta committed
2476
	do {
2477
	    /* There is no next paragraph, so nothing to move to. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2478
2479
	    if (current->next == NULL) {
		placewewant = 0;
2480
		return TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2481
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2482
	    current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2483
	    current_y++;
2484
	    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2485
2486
2487
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
    }
2488

2489
2490
    /* Now current is the first line of the paragraph, and quote_len is
     * the quotation length of that line. */
Chris Allegretta's avatar
Chris Allegretta committed
2491

2492
2493
    /* Next step, compute par_len, the number of lines in this
     * paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2494
2495
2496
2497
    line = current;
    par_len = 1;
    indent_len = indent_length(line->data + quote_len);

2498
2499
2500
    while (line->next != NULL &&
	    quotes_match(current->data, quote_len, line->next->data)) {
	size_t temp_id_len = indent_length(line->next->data + quote_len);
2501

2502
	if (!indents_match(line->data + quote_len, indent_len,
2503
		line->next->data + quote_len, temp_id_len) ||
Chris Allegretta's avatar
Chris Allegretta committed
2504
2505
2506
		line->next->data[quote_len + temp_id_len] == '\0' ||
		(quote_len == 0 && temp_id_len > 0
#ifndef NANO_SMALL
2507
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
2508
2509
#endif
		))
2510
2511
2512
2513
	    break;
	indent_len = temp_id_len;
	line = line->next;
	par_len++;
2514
2515
    }

2516
2517
    /* Now par_len is the number of lines in this paragraph.  We should
     * never call quotes_match() or quote_length() again. */
2518

2519
2520
2521
2522
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
    *quote = quote_len;
    *par = par_len;
2523

2524
    return FALSE;
2525
2526
}

2527
2528
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
2529
void do_justify(bool full_justify)
2530
{
2531
2532
2533
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
	 * For restoring after uncut. */
2534
    filestruct *last_par_line;
2535
2536
2537
	/* Will be the last line of the result, also for uncut. */
    filestruct *cutbuffer_save = cutbuffer;
	/* When the paragraph gets modified, all lines from the changed
2538
	 * one down are stored in the cutbuffer.  We back up the
2539
	 * original to restore it later. */
2540
    bool allow_respacing;
2541
	/* Whether we should change the spacing at the end of a line
2542
	 * after justifying it.  This should be TRUE whenever we move
2543
	 * to the next line after justifying the current line. */
2544
2545

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2546
     * unjustifies.  Note that we don't need to save totlines. */
2547
2548
    size_t current_x_save = current_x;
    int current_y_save = current_y;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2549
2550
    long flags_save = flags, totsize_save = totsize;
    filestruct *edittop_save = edittop, *current_save = current;
2551
2552
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
2553
    size_t mark_beginx_save = mark_beginx;
2554
#endif
2555
    int kbinput;
2556
    bool meta_key, func_key;
2557

2558
2559
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
2560
	current = fileage;
2561
2562

    last_par_line = current;
2563
2564

    while (TRUE) {
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
	size_t quote_len;
	    /* Length of the initial quotation of the paragraph we
	     * justify. */
	size_t par_len;
	    /* Number of lines in that paragraph. */

	/* 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
	 * length (number of lines).  Don't refresh the screen yet
	 * (since we'll do that after we justify).  If the search failed
	 * and we're justifying the whole file, move the last line of
	 * the text we're justifying to just before the magicline, which
	 * is where it'll be anyway if we've searched the entire file,
	 * and break out of the loop; otherwise, refresh the screen and
	 * get out. */
	if (do_para_search(&quote_len, &par_len)) {
2582
2583
2584
2585
2586
2587
2588
2589
	    if (full_justify) {
		/* This should be safe in the event of filebot->prev's
		 * being NULL, since only last_par_line->next is used if
		 * we eventually unjustify. */
		last_par_line = filebot->prev;
		break;
	    } else {
		edit_refresh();
2590
		return;
2591
2592
	    }
	}
2593

2594
2595
2596
	/* Next step, we loop through the lines of this paragraph,
	 * justifying each one individually. */
	for (; par_len > 0; current_y++, par_len--) {
2597
	    size_t indent_len;	/* Generic indentation length. */
2598
2599
2600
	    size_t line_len;
	    size_t display_len;
		/* The width of current in screen columns. */
2601
	    ssize_t break_pos;
2602
2603
		/* Where we will break the line. */

2604
2605
2606
2607
2608
	    /* We'll be moving to the next line after justifying the
	     * current line in almost all cases, so allow changing the
	     * spacing at the ends of justified lines by default. */
	    allow_respacing = TRUE;

2609
2610
	    indent_len = quote_len + indent_length(current->data +
		quote_len);
2611

2612
2613
	    /* If we haven't already done it, copy the original
	     * paragraph to the cutbuffer for unjustification. */
2614
2615
	    if (first_par_line == NULL)
		first_par_line = backup_lines(current, full_justify ?
2616
2617
			filebot->lineno - current->lineno : par_len, quote_len);

2618
2619
2620
2621
2622
2623
	    /* Now we call justify_format() on the current line of the
	     * paragraph, which will remove excess spaces from it and
	     * change tabs to spaces. */
	    justify_format(current, quote_len +
		indent_length(current->data + quote_len));

2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
	    line_len = strlen(current->data);
	    display_len = strlenpt(current->data);

	    if (display_len > fill) {
		/* The line is too long.  Try to wrap it to the next. */
	        break_pos = break_line(current->data + indent_len,
			fill - strnlenpt(current->data, indent_len), TRUE);
		if (break_pos == -1 || break_pos + indent_len == line_len)
		    /* We can't break the line, or don't need to, so
		     * just go on to the next. */
		    goto continue_loc;
		break_pos += indent_len;
		assert(break_pos < line_len);
		if (par_len == 1) {
		    /* There is no next line in this paragraph.  We make
		     * a new line and copy text after break_pos into
		     * it. */
		    splice_node(current, make_new_node(current), current->next);
		    /* In a non-quoted paragraph, we copy the indent
		     * only if AUTOINDENT is turned on. */
2644
		    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
2645
#ifndef NANO_SMALL
2646
			&& !ISSET(AUTOINDENT)
2647
#endif
2648
			)
2649
2650
2651
2652
2653
			    indent_len = 0;
		    current->next->data = charalloc(indent_len + line_len -
			break_pos);
		    strncpy(current->next->data, current->data, indent_len);
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2654
			current->data + break_pos + 1);
2655
		    assert(strlen(current->next->data) ==
Chris Allegretta's avatar
Chris Allegretta committed
2656
			indent_len + line_len - break_pos - 1);
2657
2658
2659
2660
2661
		    totlines++;
		    totsize += indent_len;
		    par_len++;
		} else {
		    size_t next_line_len = strlen(current->next->data);
Chris Allegretta's avatar
Chris Allegretta committed
2662

2663
		    indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2664
			indent_length(current->next->data + quote_len);
2665
		    current->next->data = charealloc(current->next->data,
Chris Allegretta's avatar
Chris Allegretta committed
2666
			next_line_len + line_len - break_pos + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2667

2668
2669
		    charmove(current->next->data + indent_len + line_len -
			break_pos, current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2670
			next_line_len - indent_len + 1);
2671
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2672
			current->data + break_pos + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2673
2674
		    current->next->data[indent_len + line_len -
			break_pos - 1] = ' ';
Chris Allegretta's avatar
Chris Allegretta committed
2675
#ifndef NANO_SMALL
2676
2677
2678
2679
2680
		    if (mark_beginbuf == current->next) {
			if (mark_beginx < indent_len)
			    mark_beginx = indent_len;
			mark_beginx += line_len - break_pos;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2681
#endif
2682
		}
Chris Allegretta's avatar
Chris Allegretta committed
2683
#ifndef NANO_SMALL
2684
2685
2686
2687
		if (mark_beginbuf == current && mark_beginx > break_pos) {
		    mark_beginbuf = current->next;
		    mark_beginx -= break_pos + 1 - indent_len;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2688
#endif
2689
		null_at(&current->data, break_pos);
2690
2691

		/* Go to the next line. */
2692
2693
2694
		current = current->next;
	    } else if (display_len < fill && par_len > 1) {
		size_t next_line_len;
Chris Allegretta's avatar
Chris Allegretta committed
2695

2696
		indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2697
			indent_length(current->next->data + quote_len);
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
		/* If we can't pull a word from the next line up to this
		 * one, just go on. */
		if (!breakable(current->next->data + indent_len,
			fill - display_len - 1))
		    goto continue_loc;

		break_pos = break_line(current->next->data + indent_len,
			fill - display_len - 1, FALSE);
		assert(break_pos != -1);

		current->data = charealloc(current->data,
			line_len + break_pos + 2);
		current->data[line_len] = ' ';
		strncpy(current->data + line_len + 1,
Chris Allegretta's avatar
Chris Allegretta committed
2712
			current->next->data + indent_len, break_pos);
2713
		current->data[line_len + break_pos + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2714
#ifndef NANO_SMALL
2715
2716
		if (mark_beginbuf == current->next) {
		    if (mark_beginx < indent_len + break_pos) {
2717
			mark_beginbuf = current;
2718
2719
2720
			if (mark_beginx <= indent_len)
			    mark_beginx = line_len + 1;
			else
2721
2722
			    mark_beginx = line_len + 1 + mark_beginx -
				indent_len;
2723
2724
2725
		    } else
			mark_beginx -= break_pos + 1;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2726
#endif
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
		next_line_len = strlen(current->next->data);
		if (indent_len + break_pos == next_line_len) {
		    filestruct *line = current->next;

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

		    unlink_node(line);
		    delete_node(line);
		    totlines--;
		    totsize -= indent_len;
		    current_y--;
2740

2741
2742
		    /* Don't go to the next line.  Accordingly, don't
		     * allow changing the spacing at the end of the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2743
2744
		     * previous justified line, so that we don't end up
		     * doing it more than once on the same line. */
2745
		    allow_respacing = FALSE;
2746
2747
		} else {
		    charmove(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2748
2749
			current->next->data + indent_len + break_pos + 1,
			next_line_len - break_pos - indent_len);
2750
		    null_at(&current->next->data, next_line_len - break_pos);
2751
2752

		    /* Go to the next line. */
2753
2754
2755
		    current = current->next;
		}
	    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2756
  continue_loc:
2757
		/* Go to the next line. */
2758
		current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
2759

2760
	    /* We've moved to the next line after justifying the
2761
2762
2763
2764
2765
2766
2767
	     * current line.  If the justified line was not the last
	     * line of the paragraph, add a space to the end of it to
	     * replace the one removed or left out by justify_format().
	     * If it was the last line of the paragraph, and
	     * justify_format() left a space on the end of it, remove
	     * the space. */
	    if (allow_respacing) {
2768
		size_t prev_line_len = strlen(current->prev->data);
2769
2770
2771

		if (par_len > 1) {
		    current->prev->data = charealloc(current->prev->data,
2772
			prev_line_len + 2);
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
		    current->prev->data[prev_line_len] = ' ';
		    current->prev->data[prev_line_len + 1] = '\0';
		    totsize++;
		} else if (par_len == 1 &&
			current->prev->data[prev_line_len - 1] == ' ') {
		    current->prev->data = charealloc(current->prev->data,
			prev_line_len);
		    current->prev->data[prev_line_len - 1] = '\0';
		    totsize--;
		}
2783
	    }
2784
2785
	}

2786
2787
2788
	/* 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. */
2789
2790
2791
2792
	if (!full_justify)
	    break;

    } /* while (TRUE) */
2793

2794
2795
2796
    /* 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
2797
2798
2799
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
2800
    last_par_line = current->prev;
2801
2802
2803
2804
2805
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
2806

2807
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
2808

2809
    statusbar(_("Can now UnJustify!"));
2810

2811
    /* Display the shortcut list with UnJustify. */
2812
    shortcut_init(TRUE);
2813
    display_main_list();
2814

Chris Allegretta's avatar
Chris Allegretta committed
2815
    /* Now get a keystroke and see if it's unjustify; if not, unget the
2816
     * keystroke and return. */
2817
    kbinput = get_edit_input(&meta_key, &func_key, FALSE);
2818

2819
    if (!meta_key && !func_key && kbinput == NANO_UNJUSTIFY_KEY) {
2820
	/* Restore the justify we just did (ungrateful user!). */
2821
2822
	filestruct *cutbottom = get_cutbottom();

Chris Allegretta's avatar
Chris Allegretta committed
2823
2824
2825
2826
2827
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

2828
2829
2830
2831
2832
	/* Splice the cutbuffer back into the file, but only if we
	 * actually justified something. */
	if (first_par_line != NULL) {
	    cutbottom->next = last_par_line->next;
	    cutbottom->next->prev = cutbottom;
2833
2834
	    /* The line numbers after the end of the paragraph have been
	     * changed, so we change them back. */
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
	    renumber(cutbottom->next);
	    if (first_par_line->prev != NULL) {
		cutbuffer->prev = first_par_line->prev;
		cutbuffer->prev->next = cutbuffer;
	    } else
		fileage = cutbuffer;

	    last_par_line->next = NULL;
	    free_filestruct(first_par_line);
	}
2845
2846
2847
2848

	/* Restore global variables from before the justify. */
	totsize = totsize_save;
	totlines = filebot->lineno;
Chris Allegretta's avatar
Chris Allegretta committed
2849
#ifndef NANO_SMALL
2850
2851
	mark_beginbuf = mark_beginbuf_save;
	mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
2852
#endif
2853
	flags = flags_save;
2854
	if (!ISSET(MODIFIED))
2855
	    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2856
	edit_refresh();
2857
2858
    } else {
	placewewant = 0;
2859
	unget_kbinput(kbinput, meta_key, func_key);
2860
    }
2861

Chris Allegretta's avatar
Chris Allegretta committed
2862
    cutbuffer = cutbuffer_save;
2863
2864
    /* Note that now cutbottom is invalid, but that's okay. */
    blank_statusbar();
2865

2866
    /* Display the shortcut list with UnCut. */
2867
    shortcut_init(FALSE);
2868
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2869
}
2870

2871
void do_justify_void(void)
2872
{
2873
    do_justify(FALSE);
2874
2875
}

2876
void do_full_justify(void)
2877
{
2878
    do_justify(TRUE);
2879
}
2880
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2881

2882
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
2883
{
2884
2885
    int i;

2886
2887
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
2888
    else if (ISSET(TEMP_FILE))
2889
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2890
    else
2891
2892
2893
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

2894
2895
#ifdef DEBUG
    dump_buffer(fileage);
2896
#endif
2897

2898
    if (i == 0 || (i == 1 && do_writeout(TRUE) > 0)) {
2899
#ifdef ENABLE_MULTIBUFFER
2900
	/* Exit only if there are no more open buffers. */
2901
	if (!close_open_file())
2902
#endif
2903
	    finish();
2904
    } else if (i != 1)
2905
2906
2907
2908
2909
2910
2911
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
2912
2913
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
2914
2915
2916
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
2917
    sigaction(SIGQUIT, &act, NULL);
2918

2919
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
2920
    act.sa_handler = handle_hupterm;
2921
    sigaction(SIGHUP, &act, NULL);
2922
    sigaction(SIGTERM, &act, NULL);
2923

2924
#ifndef NANO_SMALL
2925
    /* Trap SIGWINCH because we want to handle window resizes. */
2926
2927
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
2928
    allow_pending_sigwinch(FALSE);
2929
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2930

2931
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
2932
2933
2934
2935
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
2936
2937
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
2938
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
2939

2940
2941
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
2942

2943
2944
2945
2946
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
2947

2948
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
2949
RETSIGTYPE handle_hupterm(int signal)
2950
{
2951
    die(_("Received SIGHUP or SIGTERM\n"));
2952
}
2953

2954
/* Handler for SIGTSTP (suspend). */
2955
2956
2957
RETSIGTYPE do_suspend(int signal)
{
    endwin();
2958
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
2959
    fflush(stdout);
2960

2961
    /* Restore the old terminal settings. */
2962
    tcsetattr(0, TCSANOW, &oldterm);
2963

2964
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
2965
     * suspended. */
2966
2967
2968
2969
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

2970
    /* Do what mutt does: send ourselves a SIGSTOP. */
2971
2972
    kill(0, SIGSTOP);
}
2973

2974
/* Handler for SIGCONT (continue after suspend). */
2975
2976
RETSIGTYPE do_cont(int signal)
{
2977
#ifndef NANO_SMALL
2978
2979
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
2980
    handle_sigwinch(0);
2981
#else
2982
2983
    /* Just update the screen. */
    doupdate();
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
#endif
}

#ifndef NANO_SMALL
void handle_sigwinch(int s)
{
    const char *tty = ttyname(0);
    int fd;
    int result = 0;
    struct winsize win;

2995
    if (tty == NULL)
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
	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;
3011
3012
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
3013
3014
3015
3016
3017
3018
	die_too_small();

#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
3019
3020
    if (fill < 0)
	fill = 0;
3021
3022
#endif

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3023
    hblank = charealloc(hblank, COLS + 1);
3024
3025
3026
    memset(hblank, ' ', COLS);
    hblank[COLS] = '\0';

3027
3028
3029
3030
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
	unpartition_filestruct(filepart);

3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
#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
3044

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

3048
3049
3050
3051
3052
3053
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

    /* Redraw the contents of the windows that need it. */
3054
    blank_statusbar();
3055
    display_main_list();
3056
3057
    total_refresh();

3058
    /* Turn cursor back on for sure. */
3059
3060
    curs_set(1);

3061
3062
3063
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3064
    /* Jump back to the main loop. */
3065
3066
    siglongjmp(jmpbuf, 1);
}
3067

3068
void allow_pending_sigwinch(bool allow)
3069
3070
3071
3072
3073
3074
3075
3076
3077
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3078
#endif /* !NANO_SMALL */
3079

3080
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3081
void do_toggle(const toggle *which)
3082
{
3083
    bool enabled;
3084

3085
    /* Even easier! */
3086
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3087

3088
    switch (which->val) {
3089
3090
3091
    case TOGGLE_SUSPEND_KEY:
	signal_init();
	break;
3092
#ifndef DISABLE_MOUSE
3093
3094
3095
    case TOGGLE_MOUSE_KEY:
	mouse_init();
	break;
3096
#endif
3097
    case TOGGLE_NOHELP_KEY:
3098
3099
	blank_statusbar();
	blank_bottombars();
Chris Allegretta's avatar
Chris Allegretta committed
3100
3101
3102
3103
	wrefresh(bottomwin);
	window_init();
	edit_refresh();
	display_main_list();
3104
	break;
3105
#ifdef ENABLE_COLOR
3106
3107
    case TOGGLE_SYNTAX_KEY:
	edit_refresh();
3108
	break;
3109
3110
3111
3112
3113
#endif
#ifdef ENABLE_NANORC
    case TOGGLE_WHITESPACE_KEY:
	edit_refresh();
	break;
3114
#endif
3115
    }
Chris Allegretta's avatar
Chris Allegretta committed
3116

Chris Allegretta's avatar
Chris Allegretta committed
3117
3118
3119
3120
3121
3122
3123
    /* We are assuming here that shortcut_init() above didn't free and
     * reallocate the toggles. */
    enabled = ISSET(which->flag);
    if (which->val == TOGGLE_NOHELP_KEY || which->val == TOGGLE_WRAP_KEY)
	enabled = !enabled;
    statusbar("%s %s", which->desc,
		enabled ? _("enabled") : _("disabled"));
Chris Allegretta's avatar
Chris Allegretta committed
3124
}
3125
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3126

3127
3128
3129
3130
3131
3132
3133
3134
3135
void disable_extended_input(void)
{
    struct termios term;

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

3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
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);
    term.c_iflag &= ~(IXON|IXOFF);
    tcsetattr(0, TCSANOW, &term);
}

void enable_flow_control(void)
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_iflag |= (IXON|IXOFF);
    tcsetattr(0, TCSANOW, &term);
}

3174
3175
3176
3177
/* 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
3178
3179
3180
3181
 * echoing of characters as they're typed.  Finally, disable extended
 * input processing, disable interpretation of the special control keys,
 * and if we're not in preserve mode, disable interpretation of the flow
 * control characters too. */
3182
3183
3184
3185
3186
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3187
    disable_extended_input();
3188
3189
3190
3191
3192
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3193
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
3194
3195
{
    int optchr;
3196
3197
    int startline = 0;
	/* Line to try and start at. */
3198
#ifndef DISABLE_WRAPJUSTIFY
3199
3200
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
3201
#endif
3202
3203
3204
3205
3206
3207
3208
#ifdef ENABLE_MULTIBUFFER
    bool old_multibuffer;
	/* The old value of the multibuffer option, restored after we
	 * load all files on the command line. */
#endif
    int kbinput;
	/* Input from keyboard. */
3209
    bool meta_key, func_key;
Chris Allegretta's avatar
Chris Allegretta committed
3210
#ifdef HAVE_GETOPT_LONG
3211
    const struct option long_options[] = {
3212
3213
3214
	{"help", 0, 0, 'h'},
#ifdef ENABLE_MULTIBUFFER
	{"multibuffer", 0, 0, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
3215
3216
#endif
#ifdef ENABLE_NANORC
3217
#ifndef NANO_SMALL
3218
	{"historylog", 0, 0, 'H'},
3219
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3220
	{"ignorercfiles", 0, 0, 'I'},
3221
3222
3223
3224
#endif
#ifndef DISABLE_JUSTIFY
	{"quotestr", 1, 0, 'Q'},
#endif
3225
#ifdef HAVE_REGEX_H
3226
	{"regexp", 0, 0, 'R'},
3227
#endif
3228
	{"tabsize", 1, 0, 'T'},
Chris Allegretta's avatar
Chris Allegretta committed
3229
	{"version", 0, 0, 'V'},
3230
3231
#ifdef ENABLE_COLOR
	{"syntax", 1, 0, 'Y'},
3232
#endif
3233
	{"const", 0, 0, 'c'},
3234
	{"rebinddelete", 0, 0, 'd'},
3235
	{"nofollow", 0, 0, 'l'},
3236
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3237
	{"mouse", 0, 0, 'm'},
3238
#endif
3239
3240
3241
#ifndef DISABLE_OPERATINGDIR
	{"operatingdir", 1, 0, 'o'},
#endif
3242
	{"preserve", 0, 0, 'p'},
3243
3244
3245
3246
3247
#ifndef DISABLE_WRAPJUSTIFY
	{"fill", 1, 0, 'r'},
#endif
#ifndef DISABLE_SPELLER
	{"speller", 1, 0, 's'},
3248
#endif
3249
3250
	{"tempfile", 0, 0, 't'},
	{"view", 0, 0, 'v'},
3251
#ifndef DISABLE_WRAPPING
3252
	{"nowrap", 0, 0, 'w'},
3253
#endif
3254
3255
	{"nohelp", 0, 0, 'x'},
	{"suspend", 0, 0, 'z'},
3256
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3257
	{"smarthome", 0, 0, 'A'},
3258
	{"backup", 0, 0, 'B'},
3259
	{"backupdir", 1, 0, 'E'},
3260
	{"noconvert", 0, 0, 'N'},
3261
	{"smooth", 0, 0, 'S'},
3262
	{"restricted", 0, 0, 'Z'},
3263
3264
	{"autoindent", 0, 0, 'i'},
	{"cut", 0, 0, 'k'},
3265
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3266
3267
3268
3269
3270
	{0, 0, 0, 0}
    };
#endif

    setlocale(LC_ALL, "");
3271
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
3272
3273
3274
3275
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3276
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3277
3278
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
3279
    if (geteuid() == NANO_ROOT_UID)
3280
3281
	SET(NO_WRAP);
#endif
3282

3283
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
3284
#ifdef HAVE_GETOPT_LONG
3285
	getopt_long(argc, argv, "h?ABE:FHINQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz", long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
3286
#else
3287
	getopt(argc, argv, "h?ABE:FHINQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
3288
#endif
3289
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3290
3291

	switch (optchr) {
3292
3293
3294
3295
3296
3297
3298
3299
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
3300
#ifndef NANO_SMALL
3301
3302
3303
3304
3305
3306
3307
3308
3309
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
	    case 'E':
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
3310
#endif
3311
#ifdef ENABLE_MULTIBUFFER
3312
3313
3314
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
3315
3316
#endif
#ifdef ENABLE_NANORC
3317
#ifndef NANO_SMALL
3318
3319
3320
	    case 'H':
		SET(HISTORYLOG);
		break;
3321
#endif
3322
3323
3324
	    case 'I':
		SET(NO_RCFILE);
		break;
3325
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3326
#ifndef NANO_SMALL
3327
3328
3329
	    case 'N':
		SET(NO_CONVERT);
		break;
3330
3331
#endif
#ifndef DISABLE_JUSTIFY
3332
3333
3334
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
3335
#endif
3336
#ifdef HAVE_REGEX_H
3337
3338
3339
	    case 'R':
		SET(USE_REGEXP);
		break;
3340
3341
#endif
#ifndef NANO_SMALL
3342
3343
3344
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
3345
#endif
3346
3347
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
3348
3349
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3350
3351
3352
3353
3354
3355
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
3356
#ifdef ENABLE_COLOR
3357
3358
3359
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
3360
#endif
3361
3362
3363
3364
3365
3366
3367
3368
3369
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
3370
#ifndef NANO_SMALL
3371
3372
3373
3374
3375
3376
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
3377
#endif
3378
3379
3380
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
3381
#ifndef DISABLE_MOUSE
3382
3383
3384
	    case 'm':
		SET(USE_MOUSE);
		break;
3385
#endif
3386
#ifndef DISABLE_OPERATINGDIR
3387
3388
3389
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
3390
#endif
3391
3392
3393
	    case 'p':
		SET(PRESERVE);
		break;
3394
#ifndef DISABLE_WRAPJUSTIFY
3395
3396
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
3397
3398
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3399
3400
3401
3402
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
3403
#endif
3404
#ifndef DISABLE_SPELLER
3405
3406
3407
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
3408
#endif
3409
3410
3411
3412
3413
3414
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
3415
#ifndef DISABLE_WRAPPING
3416
3417
3418
	    case 'w':
		SET(NO_WRAP);
		break;
3419
#endif
3420
3421
3422
3423
3424
3425
3426
3427
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
3428
3429
3430
	}
    }

3431
3432
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
3433
3434
3435
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

3436
3437
3438
    /* 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. */
3439
3440
3441
3442
3443
3444
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
3445
/* We've read through the command line options.  Now back up the flags
3446
3447
 * 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
3448
3449
3450
3451
3452
#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
3453
#ifndef DISABLE_WRAPJUSTIFY
3454
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
3455
#endif
3456
3457
3458
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3459
3460
3461
3462
3463
3464
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
3465
	ssize_t tabsize_cpy = tabsize;
Chris Allegretta's avatar
Chris Allegretta committed
3466
3467
	long flags_cpy = flags;

3468
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3469
	operating_dir = NULL;
3470
#endif
3471
3472
3473
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
3474
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3475
	quotestr = NULL;
3476
3477
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
3478
	alt_speller = NULL;
3479
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3480
3481
3482
3483
3484
3485
3486
3487
3488

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
3489
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3490
3491
3492
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
3493
3494
3495
3496
3497
3498
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
#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
3511
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
3512
3513
3514
3515
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3516
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
3517
3518
3519
3520
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

3521
3522
3523
3524
3525
3526
3527
3528
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

3529
#ifndef NANO_SMALL
3530
    /* Set up the backup directory (unless we're using restricted mode,
3531
3532
3533
3534
     * 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. */
3535
3536
    if (!ISSET(RESTRICTED))
	init_backup_dir();
3537
3538
#endif

3539
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3540
    /* Set up the operating directory.  This entails chdir()ing there,
3541
     * so that file reads and writes will be based there. */
3542
3543
3544
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3545
#ifndef DISABLE_JUSTIFY
3546
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3547
	punct = mallocstrcpy(punct, ".?!");
3548
3549

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

Chris Allegretta's avatar
Chris Allegretta committed
3552
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3553
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
3554
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3555
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
3556
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3557
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
3558
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3559
		);
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
#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
3576
#endif /* !DISABLE_JUSTIFY */
3577

3578
3579
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
3580
     * command line and/or rcfile(s), check $SPELL for one, as Pico
3581
     * does (unless we're using restricted mode, in which case spell
3582
3583
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
3584
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
3585
3586
3587
3588
3589
3590
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

3591
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
3592
    /* If whitespace wasn't specified, set its default value. */
3593
3594
3595
3596
    if (whitespace == NULL)
	whitespace = mallocstrcpy(NULL, "  ");
#endif

3597
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
3598
    if (tabsize == -1)
3599
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3600

3601
    /* Back up the old terminal settings so that they can be restored. */
3602
    tcgetattr(0, &oldterm);
3603

3604
3605
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
3606
    initscr();
3607
    terminal_init();
3608

3609
    /* Set up the global variables and the shortcuts. */
3610
3611
    global_init(FALSE);
    shortcut_init(FALSE);
3612
3613

    /* Set up the signal handlers. */
3614
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
3615
3616

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

3620
    window_init();
3621
#ifndef DISABLE_MOUSE
3622
    mouse_init();
3623
#endif
3624

Chris Allegretta's avatar
Chris Allegretta committed
3625
#ifdef DEBUG
3626
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
3627
#endif
3628

3629
3630
3631
3632
3633
3634
3635
3636
    /* If there's a +LINE flag here, it is the first non-option
     * argument, and it is followed by at least one other argument, the
     * filename it applies to. */
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
	startline = atoi(&argv[optind][1]);
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
3637
#ifdef ENABLE_MULTIBUFFER
3638
3639
3640
3641
3642
3643
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

    /* Read all the files after the first one on the command line into
     * new buffers. */
    {
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
	int i = optind + 1, iline = 0;
	for (; i < argc; i++) {
	    /* If there's a +LINE flag here, it is followed by at least
	     * one other argument, the filename it applies to. */
	    if (i < argc - 1 && argv[i][0] == '+' && iline == 0) {
		iline = atoi(&argv[i][1]);
	    } else {
		load_buffer(argv[i]);
		if (iline > 0) {
		    do_gotoline(iline, FALSE);
		    iline = 0;
		}
	    }
	}
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
    }
#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
        * multibuffer support, or to the main filestruct if we don't. */
	load_file();
Chris Allegretta's avatar
Chris Allegretta committed
3679
    }
3680
3681
3682
3683

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

3686
3687
3688
3689
3690
3691
3692
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

Chris Allegretta's avatar
Chris Allegretta committed
3693
    if (startline > 0)
3694
	do_gotoline(startline, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
3695

3696
3697
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
3698
    sigsetjmp(jmpbuf, 1);
3699
#endif
3700

Robert Siemborski's avatar
Robert Siemborski committed
3701
3702
    edit_refresh();

3703
    while (TRUE) {
3704
	reset_cursor();
3705
	if (ISSET(CONSTUPDATE))
3706
	    do_cursorpos(TRUE);
3707

3708
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
3709
	currshortcut = main_list;
3710
#endif
3711

3712
	kbinput = get_edit_input(&meta_key, &func_key, TRUE);
3713

3714
	/* Last gasp, stuff that's not in the main lists. */
3715
	if (kbinput != ERR && !is_cntrl_char(kbinput)) {
3716
3717
	    /* Don't stop unhandled printable sequences, so that people
	     * with odd character sets can type. */
3718
3719
3720
3721
	    if (ISSET(VIEW_MODE))
		print_view_warning();
	    else
		do_char(kbinput);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3722
	}
Chris Allegretta's avatar
Chris Allegretta committed
3723
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3724
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
3725
}