"src/nano.c" did not exist on "8e207bf03f63d7cc858cd33e228ee92b08d7ab65"
nano.c 103 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
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1542
1543
1544
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1545
    filestruct *top, *bot;
1546
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547
#endif
1548

1549
    /* Make sure spell-check is case sensitive. */
1550
    SET(CASE_SENSITIVE);
1551

1552
#ifndef NANO_SMALL
1553
1554
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1555
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1556
#ifdef HAVE_REGEX_H
1557
1558
1559
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1560

1561
    /* Save the current search/replace strings. */
1562
1563
1564
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1565

1566
    /* Set search/replace strings to misspelled word. */
1567
1568
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1569

1570
1571
#ifndef NANO_SMALL
    if (old_mark_set) {
1572
	/* If the mark is on, partition the filestruct so that it
1573
1574
1575
	 * contains only the marked text, keep track of whether the text
	 * will have a magicline added when we're done correcting
	 * misspelled words, and turn the mark off. */
1576
	mark_order((const filestruct **)&top, &top_x,
1577
	    (const filestruct **)&bot, &bot_x, &right_side_up);
1578
	filepart = partition_filestruct(top, top_x, bot, bot_x);
1579
	added_magicline = (filebot->data[0] != '\0');
1580
1581
1582
1583
	UNSET(MARK_ISSET);
    }
#endif

1584
    /* Start from the top of the file. */
1585
    edittop = fileage;
1586
    current = fileage;
1587
    current_x = (size_t)-1;
1588
    placewewant = 0;
1589

1590
    /* Find the first whole-word occurrence of word. */
1591
    findnextstr_wrap_reset();
1592
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) {
1593
1594
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
1595

1596
	    do_replace_highlight(TRUE, word);
1597

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1598
	    /* Allow all instances of the word to be corrected. */
1599
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
1600
#ifndef NANO_SMALL
1601
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
1602
#endif
1603
			 _("Edit a replacement")) == -1);
1604

1605
	    do_replace_highlight(FALSE, word);
1606

1607
	    if (!canceled && strcmp(word, answer) != 0) {
1608
		current_x--;
1609
1610
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
1611
	    }
1612
1613

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

1617
1618
#ifndef NANO_SMALL
    if (old_mark_set) {
1619
	size_t top_data_len, bot_data_len;
1620
1621
1622
1623

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

1625
1626
1627
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	top_data_len = strlen(filepart->top_data);
1628
	bot_data_len = strlen(filebot->data);
1629
1630
1631
1632
	if (fileage == filebot)
	    bot_data_len += top_data_len;
	if (right_side_up) {
	    mark_beginx = top_data_len;
1633
	    current_x_save = bot_data_len;
1634
1635
	} else {
	    current_x_save = top_data_len;
1636
	    mark_beginx = bot_data_len;
1637
	}
1638
1639
1640
1641
1642

	/* If the mark was on, unpartition the filestruct so that it
	 * contains all the text again, and turn the mark back on. */
	unpartition_filestruct(filepart);
	SET(MARK_ISSET);
1643
1644
    }
#endif
1645

1646
1647
1648
1649
1650
    /* 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
1651

1652
    /* Restore where we were. */
1653
    edittop = edittop_save;
1654
1655
    current = current_save;
    current_x = current_x_save;
1656
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
1657

1658
    /* Restore case sensitivity setting. */
1659
1660
1661
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

1662
#ifndef NANO_SMALL
1663
1664
1665
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
1666
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1667
#ifdef HAVE_REGEX_H
1668
1669
1670
1671
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
1672

1673
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
1674
1675
}

1676
1677
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
1678
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
1679
{
1680
1681
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1682
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1683
1684
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
1685

1686
    /* Create all three pipes up front. */
1687
1688
    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
1689

1690
    statusbar(_("Creating misspelled word list, please wait..."));
1691

1692
    /* A new process to run spell in. */
1693
    if ((pid_spell = fork()) == 0) {
1694

1695
	/* Child continues (i.e, future spell process). */
1696

1697
	close(spell_fd[0]);
1698

1699
	/* Replace the standard input with the temp file. */
1700
1701
1702
1703
1704
1705
	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;

1706
	close(tempfile_fd);
1707

1708
	/* Send spell's standard output to the pipe. */
1709
1710
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1711

1712
	close(spell_fd[1]);
1713

1714
	/* Start spell program; we are using PATH. */
1715
	execlp("spell", "spell", NULL);
1716

1717
	/* Should not be reached, if spell is found. */
1718
	exit(1);
1719
    }
Chris Allegretta's avatar
Chris Allegretta committed
1720

1721
    /* Parent continues here. */
1722
1723
    close(spell_fd[1]);

1724
    /* A new process to run sort in. */
1725
1726
    if ((pid_sort = fork()) == 0) {

1727
1728
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
1729
1730
1731
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1732
1733
	close(spell_fd[0]);

1734
	/* Send sort's standard output to the new pipe. */
1735
1736
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1737
1738
1739

	close(sort_fd[1]);

1740
1741
1742
	/* 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. */
1743
1744
	execlp("sort", "sort", "-f", NULL);

1745
	/* Should not be reached, if sort is found. */
1746
1747
1748
	exit(1);
    }

1749
    close(spell_fd[0]);
1750
1751
    close(sort_fd[1]);

1752
    /* A new process to run uniq in. */
1753
1754
    if ((pid_uniq = fork()) == 0) {

1755
1756
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
1757
1758
1759
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1760
1761
	close(sort_fd[0]);

1762
	/* Send uniq's standard output to the new pipe. */
1763
1764
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1765
1766
1767

	close(uniq_fd[1]);

1768
	/* Start uniq program; we are using PATH. */
1769
1770
	execlp("uniq", "uniq", NULL);

1771
	/* Should not be reached, if uniq is found. */
1772
1773
1774
	exit(1);
    }

1775
    close(sort_fd[0]);
1776
    close(uniq_fd[1]);
1777

1778
    /* Child process was not forked successfully. */
1779
1780
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
1781
	return _("Could not fork");
1782
    }
1783

1784
    /* Get system pipe buffer size. */
1785
1786
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
1787
	return _("Could not get size of pipe buffer");
1788
    }
Chris Allegretta's avatar
Chris Allegretta committed
1789

1790
    /* Read in the returned spelling errors. */
1791
1792
1793
    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
1794

1795
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
1796
1797
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1798
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
1799
	read_buff_ptr += read_buff_read;
1800

1801
    }
Chris Allegretta's avatar
Chris Allegretta committed
1802

1803
    *read_buff_ptr = (char)NULL;
1804
    close(uniq_fd[0]);
1805

1806
    /* Process the spelling errors. */
1807
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
1808

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

1811
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
1812
	    *read_buff_ptr = (char)NULL;
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
	    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
1823

1824
    /* Special case where last word doesn't end with \n or \r. */
1825
1826
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
1827

1828
1829
    free(read_buff);
    replace_abort();
1830
    edit_refresh();
1831

1832
    /* Process end of spell process. */
1833
1834
1835
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
1836

1837
1838
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
1839

1840
1841
1842
1843
1844
1845
1846
1847
    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;
1848

1849
  close_pipes_and_exit:
1850

1851
    /* Don't leak any handles. */
1852
1853
1854
1855
1856
1857
1858
1859
    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
1860
1861
}

1862
1863
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
1864
const char *do_alt_speller(char *tempfile_name)
1865
{
1866
1867
1868
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
    int current_y_save = current_y;
1869
1870
1871
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
1872
1873
    static char **spellargs = NULL;
    FILE *f;
1874
#ifndef NANO_SMALL
1875
    bool old_mark_set = ISSET(MARK_ISSET);
1876
1877
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1878
1879
1880
1881
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
    int mbb_lineno_save = 0;
1882
	/* We're going to close the current file, and open the output of
1883
1884
1885
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
1886
1887
1888
1889
1890
1891
    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. */
1892

1893
    if (old_mark_set) {
1894
1895
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
1896
	mbb_lineno_save = mark_beginbuf->lineno;
1897
1898
	UNSET(MARK_ISSET);
    }
1899
#endif
1900

1901
    endwin();
1902

1903
    /* Set up an argument list to pass execvp(). */
1904
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1905
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
1906

1907
1908
1909
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1910
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
1911
1912
1913
1914
1915
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
1916

1917
    /* Start a new process for the alternate speller. */
1918
    if ((pid_spell = fork()) == 0) {
1919
	/* Start alternate spell program; we are using PATH. */
1920
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
1921

1922
1923
1924
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
1925

1926
1927
    /* Could not fork?? */
    if (pid_spell < 0)
1928
	return _("Could not fork");
1929

1930
    /* Wait for alternate speller to complete. */
1931
    wait(&alt_spell_status);
1932

1933
1934
1935
1936
1937
1938
1939
1940
1941
    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;
    }
1942
1943

    refresh();
1944

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

1948
#ifndef NANO_SMALL
1949
    if (old_mark_set) {
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
	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,
1960
		(const filestruct **)&bot, &bot_x, &right_side_up);
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
	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
1986
	filestruct *top_save = fileage;
1987
	size_t top_data_len, bot_data_len;
1988
1989
1990
1991
1992

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

1993
1994
1995
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	top_data_len = strlen(filepart->top_data);
1996
	bot_data_len = strlen(filebot->data);
1997
1998
1999
2000
	if (fileage == filebot)
	    bot_data_len += top_data_len;
	if (right_side_up) {
	    mark_beginx = top_data_len;
2001
	    current_x_save = bot_data_len;
2002
2003
	} else {
	    current_x_save = top_data_len;
2004
	    mark_beginx = bot_data_len;
2005
	}
2006

2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
	/* 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
2018
	renumber(top_save);
2019
2020
2021
2022
2023
2024
2025
	old_totlines += totlines;
	old_totsize += totsize;
	totlines = old_totlines;
	totsize = old_totsize;

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2026
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2027
	mark_beginbuf = current;
2028
2029
2030
2031

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

2034
2035
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2036
2037
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2038

2039
2040
    /* Go back to the old position, mark the file as modified, and make
     * sure that the titlebar is refreshed. */
2041
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2042
2043
2044
    set_modified();
    clearok(topwin, FALSE);
    titlebar(NULL);
2045

2046
    return NULL;
2047
}
2048

2049
void do_spell(void)
2050
{
2051
    int i;
2052
2053
    char *temp = safe_tempnam(0, "nano.");
    const char *spell_msg;
2054

2055
2056
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2057
	return;
2058
    }
2059

2060
2061
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2062
	i = write_marked(temp, TRUE, FALSE);
2063
2064
    else
#endif
2065
	i = write_file(temp, TRUE, FALSE, FALSE);
2066
2067

    if (i == -1) {
2068
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2069
	free(temp);
2070
	return;
2071
2072
    }

2073
#ifdef ENABLE_MULTIBUFFER
2074
2075
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2076
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2077
#endif
2078

2079
2080
    spell_msg = alt_speller != NULL ? do_alt_speller(temp) :
	do_int_speller(temp);
2081
    unlink(temp);
2082
    free(temp);
2083

2084
    if (spell_msg != NULL)
2085
2086
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2087
    else
2088
	statusbar(_("Finished checking spelling"));
2089
}
2090
#endif /* !DISABLE_SPELLER */
2091

2092
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2093
2094
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2095
2096
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2097
    size_t len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2098

Chris Allegretta's avatar
Chris Allegretta committed
2099
    assert(line != NULL);
2100
    while (isblank(*line)) {
Chris Allegretta's avatar
Chris Allegretta committed
2101
2102
	line++;
	len++;
Chris Allegretta's avatar
Chris Allegretta committed
2103
    }
Chris Allegretta's avatar
Chris Allegretta committed
2104
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2105
}
2106
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2107

Chris Allegretta's avatar
Chris Allegretta committed
2108
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2109
/* justify_format() replaces Tab by Space and multiple spaces by 1
2110
2111
2112
 * (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
2113
 *
2114
2115
 * justify_format() might make line->data shorter, and change the actual
 * pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2116
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2117
 * justify_format() will not look at the first skip characters of line.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2118
2119
 * skip should be at most strlen(line->data).  The character at
 * line[skip + 1] must not be whitespace. */
2120
void justify_format(filestruct *line, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2121
{
Chris Allegretta's avatar
Chris Allegretta committed
2122
2123
2124
2125
2126
    char *back, *front;

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

    back = line->data + skip;
2131
    for (front = back; ; front++) {
2132
	bool remove_space = FALSE;
2133
2134
	    /* Do we want to remove this space? */

2135
	if (*front == '\t')
Chris Allegretta's avatar
Chris Allegretta committed
2136
	    *front = ' ';
2137

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

2143
	    remove_space = TRUE;
2144
	    for (; bob >= line->data + skip; bob--) {
2145
		if (strchr(punct, *bob) != NULL) {
2146
2147
2148
2149
2150
		    /* 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));
2151
2152
2153
2154
2155
2156
2157
2158
		    break;
		}
		if (strchr(brackets, *bob) == NULL)
		    break;
	    }
	}

	if (remove_space) {
Chris Allegretta's avatar
Chris Allegretta committed
2159
	    /* Now *front is a space we want to remove.  We do that by
2160
	     * simply failing to assign it to *back. */
Chris Allegretta's avatar
Chris Allegretta committed
2161
2162
2163
2164
#ifndef NANO_SMALL
	    if (mark_beginbuf == line && back - line->data < mark_beginx)
		mark_beginx--;
#endif
2165
2166
	    if (*front == '\0')
		*(back - 1) = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2167
2168
2169
2170
	} else {
	    *back = *front;
	    back++;
	}
2171
2172
	if (*front == '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2173
2174
    }

2175
    back--;
2176
    assert(*back == '\0' && *front == '\0');
Chris Allegretta's avatar
Chris Allegretta committed
2177

Chris Allegretta's avatar
Chris Allegretta committed
2178
2179
    /* Now back is the new end of line->data. */
    if (back != front) {
2180
	totsize -= front - back;
Chris Allegretta's avatar
Chris Allegretta committed
2181
2182
2183
2184
2185
	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
2186
    }
Chris Allegretta's avatar
Chris Allegretta committed
2187
}
Chris Allegretta's avatar
Chris Allegretta committed
2188

Chris Allegretta's avatar
Chris Allegretta committed
2189
2190
2191
2192
2193
2194
/* 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. */
2195
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2196
{
2197
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2198
    regmatch_t matches;
2199
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2200

2201
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2202
	return 0;
2203
2204
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2205
2206
2207
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2208

2209
    /* Compute quote depth level. */
2210
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2211
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2212
2213
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2214
}
Chris Allegretta's avatar
Chris Allegretta committed
2215

Chris Allegretta's avatar
Chris Allegretta committed
2216
2217
2218
/* 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. */
2219
2220
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2221
{
Chris Allegretta's avatar
Chris Allegretta committed
2222
    /* Here is the assumption about a_quote: */
2223
2224
    assert(a_quote == quote_length(a_line));
    return a_quote == quote_length(b_line) &&
2225
	strncmp(a_line, b_line, a_quote) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2226
}
2227

2228
2229
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2230
bool indents_match(const char *a_line, size_t a_indent, const char
2231
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2232
{
Chris Allegretta's avatar
Chris Allegretta committed
2233
2234
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2235

2236
2237
    return b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2238
}
Chris Allegretta's avatar
Chris Allegretta committed
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
2274
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
2303
2304
2305
2306
2307
2308
2309
2310
2311
/* 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;
2312
	    current_y--;
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
	} 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) &&
2341
	    !begpar(current->next)) {
2342
	current = current->next;
2343
2344
	current_y++;
    }
2345
2346
2347
2348
2349
2350
2351

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

    edit_redraw(old_current, old_pww);
}

Chris Allegretta's avatar
Chris Allegretta committed
2352
/* Put the next par_len lines, starting with first_line, in the cut
2353
2354
2355
 * 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. */
2356
2357
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2358
{
2359
2360
2361
    /* 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. */
2362
    filestruct *alice = first_line;
Chris Allegretta's avatar
Chris Allegretta committed
2363
2364

    set_modified();
2365
    cutbuffer = NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2366
    for (; par_len > 0; par_len--) {
2367
	filestruct *bob = copy_node(alice);
Chris Allegretta's avatar
Chris Allegretta committed
2368

2369
	if (alice == first_line)
Chris Allegretta's avatar
Chris Allegretta committed
2370
	    first_line = bob;
2371
	if (alice == current)
Chris Allegretta's avatar
Chris Allegretta committed
2372
	    current = bob;
2373
	if (alice == edittop)
Chris Allegretta's avatar
Chris Allegretta committed
2374
2375
	    edittop = bob;
#ifndef NANO_SMALL
2376
	if (alice == mark_beginbuf)
Chris Allegretta's avatar
Chris Allegretta committed
2377
2378
2379
	    mark_beginbuf = bob;
#endif

2380
	assert(alice != NULL && bob != NULL);
2381
	add_to_cutbuffer(alice, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
2382
	splice_node(bob->prev, bob, bob->next);
2383
	alice = bob->next;
Chris Allegretta's avatar
Chris Allegretta committed
2384
2385
2386
2387
    }
    return first_line;
}

Chris Allegretta's avatar
Chris Allegretta committed
2388
/* Is it possible to break line at or before goal? */
2389
bool breakable(const char *line, ssize_t goal)
Chris Allegretta's avatar
Chris Allegretta committed
2390
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2391
    for (; *line != '\0' && goal >= 0; line++) {
2392
	if (isblank(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2393
2394
	    return TRUE;

2395
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2396
2397
2398
2399
	    goal -= 2;
	else
	    goal -= 1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2400
2401
2402
    /* If goal is not negative, the whole line (one word) was short
     * enough. */
    return goal >= 0;
Chris Allegretta's avatar
Chris Allegretta committed
2403
2404
}

Chris Allegretta's avatar
Chris Allegretta committed
2405
/* We are trying to break a chunk off line.  We find the last space such
2406
 * that the display length to there is at most goal + 1.  If there is no
2407
2408
2409
 * 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. */
2410
int break_line(const char *line, ssize_t goal, bool force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2411
{
2412
    ssize_t space_loc = -1;
Chris Allegretta's avatar
Chris Allegretta committed
2413
2414
	/* Current tentative return value.  Index of the last space we
	 * found with short enough display width.  */
2415
    ssize_t cur_loc = 0;
2416
	/* Current index in line. */
Chris Allegretta's avatar
Chris Allegretta committed
2417
2418

    assert(line != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2419
    for (; *line != '\0' && goal >= 0; line++, cur_loc++) {
Chris Allegretta's avatar
Chris Allegretta committed
2420
2421
2422
2423
	if (*line == ' ')
	    space_loc = cur_loc;
	assert(*line != '\t');

Chris Allegretta's avatar
Chris Allegretta committed
2424
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
	    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
2435
	    for (; *line != '\0'; line++, cur_loc++)
2436
		if (*line == ' ' && *(line + 1) != ' ' && *(line + 1) != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
2437
2438
2439
2440
		    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
2441
     * of justify_format(), there can be only two adjacent. */
Chris Allegretta's avatar
Chris Allegretta committed
2442
2443
2444
2445
2446
2447
    if (*(line - cur_loc + space_loc + 1) == ' ' ||
	*(line - cur_loc + space_loc + 1) == '\0')
	space_loc++;
    return space_loc;
}

2448
2449
2450
2451
2452
/* 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
2453
 *
2454
2455
2456
 * 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)
2457
{
Chris Allegretta's avatar
Chris Allegretta committed
2458
    size_t quote_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2459
	/* Length of the initial quotation of the paragraph we
2460
	 * search. */
Chris Allegretta's avatar
Chris Allegretta committed
2461
2462
    size_t par_len;
	/* Number of lines in that paragraph. */
2463
2464
2465
2466
    size_t indent_len;
	/* Generic indentation length. */
    filestruct *line;
	/* Generic line of text. */
2467

Chris Allegretta's avatar
Chris Allegretta committed
2468
#ifdef HAVE_REGEX_H
2469
2470
2471
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
	return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
2472
    }
Chris Allegretta's avatar
Chris Allegretta committed
2473
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2474

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

2478
2479
    current_x = 0;

2480
    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2481
    indent_len = indent_length(current->data + quote_len);
Robert Siemborski's avatar
Robert Siemborski committed
2482

2483
2484
2485
2486
    /* 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
2487
2488
    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
2489
2490
	 * the first line of this paragraph.  First we check items 1)
	 * and 3) above. */
2491
2492
	while (current->prev != NULL &&	quotes_match(current->data,
		quote_len, current->prev->data)) {
2493
	    size_t temp_id_len =
2494
		indent_length(current->prev->data + quote_len);
2495
		/* The indentation length of the previous line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2496

2497
	    /* Is this line the beginning of a paragraph, according to
2498
	     * items 2), 5), or 4) above?  If so, stop. */
2499
	    if (current->prev->data[quote_len + temp_id_len] == '\0' ||
2500
		(quote_len == 0 && indent_len > 0
2501
#ifndef NANO_SMALL
2502
		&& !ISSET(AUTOINDENT)
2503
#endif
2504
2505
		) || !indents_match(current->prev->data + quote_len,
		temp_id_len, current->data + quote_len, indent_len))
2506
2507
2508
2509
		break;
	    indent_len = temp_id_len;
	    current = current->prev;
	    current_y--;
Chris Allegretta's avatar
Chris Allegretta committed
2510
	}
Chris Allegretta's avatar
Chris Allegretta committed
2511
    } else {
Chris Allegretta's avatar
Chris Allegretta committed
2512
2513
	/* 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
2514
	do {
2515
	    /* There is no next paragraph, so nothing to move to. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2516
2517
	    if (current->next == NULL) {
		placewewant = 0;
2518
		return TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2519
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2520
	    current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2521
	    current_y++;
2522
	    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2523
2524
2525
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
    }
2526

2527
2528
    /* 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
2529

2530
2531
    /* Next step, compute par_len, the number of lines in this
     * paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2532
2533
2534
2535
    line = current;
    par_len = 1;
    indent_len = indent_length(line->data + quote_len);

2536
2537
2538
    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);
2539

2540
	if (!indents_match(line->data + quote_len, indent_len,
2541
		line->next->data + quote_len, temp_id_len) ||
Chris Allegretta's avatar
Chris Allegretta committed
2542
2543
2544
		line->next->data[quote_len + temp_id_len] == '\0' ||
		(quote_len == 0 && temp_id_len > 0
#ifndef NANO_SMALL
2545
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
2546
2547
#endif
		))
2548
2549
2550
2551
	    break;
	indent_len = temp_id_len;
	line = line->next;
	par_len++;
2552
2553
    }

2554
2555
    /* Now par_len is the number of lines in this paragraph.  We should
     * never call quotes_match() or quote_length() again. */
2556

2557
2558
2559
2560
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
    *quote = quote_len;
    *par = par_len;
2561

2562
    return FALSE;
2563
2564
}

2565
2566
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
2567
void do_justify(bool full_justify)
2568
{
2569
2570
2571
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
	 * For restoring after uncut. */
2572
    filestruct *last_par_line;
2573
2574
2575
	/* 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
2576
	 * one down are stored in the cutbuffer.  We back up the
2577
	 * original to restore it later. */
2578
    bool allow_respacing;
2579
	/* Whether we should change the spacing at the end of a line
2580
	 * after justifying it.  This should be TRUE whenever we move
2581
	 * to the next line after justifying the current line. */
2582
2583

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2584
     * unjustifies.  Note that we don't need to save totlines. */
2585
2586
    size_t current_x_save = current_x;
    int current_y_save = current_y;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2587
2588
    long flags_save = flags, totsize_save = totsize;
    filestruct *edittop_save = edittop, *current_save = current;
2589
2590
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
2591
    size_t mark_beginx_save = mark_beginx;
2592
#endif
2593
    int kbinput;
2594
    bool meta_key, func_key;
2595

2596
2597
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
2598
	current = fileage;
2599
2600

    last_par_line = current;
2601
2602

    while (TRUE) {
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
	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)) {
2620
2621
2622
2623
2624
2625
2626
2627
	    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();
2628
		return;
2629
2630
	    }
	}
2631

2632
2633
2634
	/* Next step, we loop through the lines of this paragraph,
	 * justifying each one individually. */
	for (; par_len > 0; current_y++, par_len--) {
2635
	    size_t indent_len;	/* Generic indentation length. */
2636
2637
2638
	    size_t line_len;
	    size_t display_len;
		/* The width of current in screen columns. */
2639
	    ssize_t break_pos;
2640
2641
		/* Where we will break the line. */

2642
2643
2644
2645
2646
	    /* 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;

2647
2648
	    indent_len = quote_len + indent_length(current->data +
		quote_len);
2649

2650
2651
	    /* If we haven't already done it, copy the original
	     * paragraph to the cutbuffer for unjustification. */
2652
2653
	    if (first_par_line == NULL)
		first_par_line = backup_lines(current, full_justify ?
2654
2655
			filebot->lineno - current->lineno : par_len, quote_len);

2656
2657
2658
2659
2660
2661
	    /* 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));

2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
	    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. */
2682
		    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
2683
#ifndef NANO_SMALL
2684
			&& !ISSET(AUTOINDENT)
2685
#endif
2686
			)
2687
2688
2689
2690
2691
			    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
2692
			current->data + break_pos + 1);
2693
		    assert(strlen(current->next->data) ==
Chris Allegretta's avatar
Chris Allegretta committed
2694
			indent_len + line_len - break_pos - 1);
2695
2696
2697
2698
2699
		    totlines++;
		    totsize += indent_len;
		    par_len++;
		} else {
		    size_t next_line_len = strlen(current->next->data);
Chris Allegretta's avatar
Chris Allegretta committed
2700

2701
		    indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2702
			indent_length(current->next->data + quote_len);
2703
		    current->next->data = charealloc(current->next->data,
Chris Allegretta's avatar
Chris Allegretta committed
2704
			next_line_len + line_len - break_pos + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2705

2706
2707
		    charmove(current->next->data + indent_len + line_len -
			break_pos, current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2708
			next_line_len - indent_len + 1);
2709
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2710
			current->data + break_pos + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2711
2712
		    current->next->data[indent_len + line_len -
			break_pos - 1] = ' ';
Chris Allegretta's avatar
Chris Allegretta committed
2713
#ifndef NANO_SMALL
2714
2715
2716
2717
2718
		    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
2719
#endif
2720
		}
Chris Allegretta's avatar
Chris Allegretta committed
2721
#ifndef NANO_SMALL
2722
2723
2724
2725
		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
2726
#endif
2727
		null_at(&current->data, break_pos);
2728
2729

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

2734
		indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2735
			indent_length(current->next->data + quote_len);
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
		/* 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
2750
			current->next->data + indent_len, break_pos);
2751
		current->data[line_len + break_pos + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2752
#ifndef NANO_SMALL
2753
2754
		if (mark_beginbuf == current->next) {
		    if (mark_beginx < indent_len + break_pos) {
2755
			mark_beginbuf = current;
2756
2757
2758
			if (mark_beginx <= indent_len)
			    mark_beginx = line_len + 1;
			else
2759
2760
			    mark_beginx = line_len + 1 + mark_beginx -
				indent_len;
2761
2762
2763
		    } else
			mark_beginx -= break_pos + 1;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2764
#endif
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
		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--;
2778

2779
2780
		    /* 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
2781
2782
		     * previous justified line, so that we don't end up
		     * doing it more than once on the same line. */
2783
		    allow_respacing = FALSE;
2784
2785
		} else {
		    charmove(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2786
2787
			current->next->data + indent_len + break_pos + 1,
			next_line_len - break_pos - indent_len);
2788
		    null_at(&current->next->data, next_line_len - break_pos);
2789
2790

		    /* Go to the next line. */
2791
2792
2793
		    current = current->next;
		}
	    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2794
  continue_loc:
2795
		/* Go to the next line. */
2796
		current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
2797

2798
	    /* We've moved to the next line after justifying the
2799
2800
2801
2802
2803
2804
2805
	     * 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) {
2806
		size_t prev_line_len = strlen(current->prev->data);
2807
2808
2809

		if (par_len > 1) {
		    current->prev->data = charealloc(current->prev->data,
2810
			prev_line_len + 2);
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
		    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--;
		}
2821
	    }
2822
2823
	}

2824
2825
2826
	/* 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. */
2827
2828
2829
2830
	if (!full_justify)
	    break;

    } /* while (TRUE) */
2831

2832
2833
2834
    /* 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
2835
2836
2837
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
2838
    last_par_line = current->prev;
2839
2840
2841
2842
2843
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
2844

2845
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
2846

2847
    statusbar(_("Can now UnJustify!"));
2848

2849
    /* Display the shortcut list with UnJustify. */
2850
    shortcut_init(TRUE);
2851
    display_main_list();
2852

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

2857
    if (!meta_key && !func_key && kbinput == NANO_UNJUSTIFY_KEY) {
2858
	/* Restore the justify we just did (ungrateful user!). */
2859
2860
	filestruct *cutbottom = get_cutbottom();

Chris Allegretta's avatar
Chris Allegretta committed
2861
2862
2863
2864
2865
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

2866
2867
2868
2869
2870
	/* 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;
2871
2872
	    /* The line numbers after the end of the paragraph have been
	     * changed, so we change them back. */
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
	    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);
	}
2883
2884
2885
2886

	/* Restore global variables from before the justify. */
	totsize = totsize_save;
	totlines = filebot->lineno;
Chris Allegretta's avatar
Chris Allegretta committed
2887
#ifndef NANO_SMALL
2888
2889
	mark_beginbuf = mark_beginbuf_save;
	mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
2890
#endif
2891
	flags = flags_save;
2892
	if (!ISSET(MODIFIED))
2893
	    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2894
	edit_refresh();
2895
2896
    } else {
	placewewant = 0;
2897
	unget_kbinput(kbinput, meta_key, func_key);
2898
    }
2899

Chris Allegretta's avatar
Chris Allegretta committed
2900
    cutbuffer = cutbuffer_save;
2901
2902
    /* Note that now cutbottom is invalid, but that's okay. */
    blank_statusbar();
2903

2904
    /* Display the shortcut list with UnCut. */
2905
    shortcut_init(FALSE);
2906
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2907
}
2908

2909
void do_justify_void(void)
2910
{
2911
    do_justify(FALSE);
2912
2913
}

2914
void do_full_justify(void)
2915
{
2916
    do_justify(TRUE);
2917
}
2918
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2919

2920
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
2921
{
2922
2923
    int i;

2924
2925
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
2926
    else if (ISSET(TEMP_FILE))
2927
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2928
    else
2929
2930
2931
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

2932
2933
#ifdef DEBUG
    dump_buffer(fileage);
2934
#endif
2935

2936
    if (i == 0 || (i == 1 && do_writeout(TRUE) > 0)) {
2937
#ifdef ENABLE_MULTIBUFFER
2938
	/* Exit only if there are no more open buffers. */
2939
	if (!close_open_file())
2940
#endif
2941
	    finish();
2942
    } else if (i != 1)
2943
2944
2945
2946
2947
2948
2949
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
2950
2951
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
2952
2953
2954
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
2955
    sigaction(SIGQUIT, &act, NULL);
2956

2957
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
2958
    act.sa_handler = handle_hupterm;
2959
    sigaction(SIGHUP, &act, NULL);
2960
    sigaction(SIGTERM, &act, NULL);
2961

2962
#ifndef NANO_SMALL
2963
    /* Trap SIGWINCH because we want to handle window resizes. */
2964
2965
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
2966
    allow_pending_sigwinch(FALSE);
2967
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2968

2969
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
2970
2971
2972
2973
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
2974
2975
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
2976
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
2977

2978
2979
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
2980

2981
2982
2983
2984
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
2985

2986
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
2987
RETSIGTYPE handle_hupterm(int signal)
2988
{
2989
    die(_("Received SIGHUP or SIGTERM\n"));
2990
}
2991

2992
/* Handler for SIGTSTP (suspend). */
2993
2994
2995
RETSIGTYPE do_suspend(int signal)
{
    endwin();
2996
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
2997
    fflush(stdout);
2998

2999
    /* Restore the old terminal settings. */
3000
    tcsetattr(0, TCSANOW, &oldterm);
3001

3002
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3003
     * suspended. */
3004
3005
3006
3007
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3008
    /* Do what mutt does: send ourselves a SIGSTOP. */
3009
3010
    kill(0, SIGSTOP);
}
3011

3012
/* Handler for SIGCONT (continue after suspend). */
3013
3014
RETSIGTYPE do_cont(int signal)
{
3015
#ifndef NANO_SMALL
3016
3017
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3018
    handle_sigwinch(0);
3019
#else
3020
3021
    /* Just update the screen. */
    doupdate();
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
#endif
}

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

3033
    if (tty == NULL)
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
	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;
3049
3050
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
3051
3052
3053
3054
3055
3056
	die_too_small();

#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
3057
3058
    if (fill < 0)
	fill = 0;
3059
3060
#endif

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3061
    hblank = charealloc(hblank, COLS + 1);
3062
3063
3064
    memset(hblank, ' ', COLS);
    hblank[COLS] = '\0';

3065
3066
3067
3068
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
	unpartition_filestruct(filepart);

3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
#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
3082

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

3086
3087
3088
3089
3090
3091
    /* 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. */
3092
    blank_statusbar();
3093
    display_main_list();
3094
3095
    total_refresh();

3096
    /* Turn cursor back on for sure. */
3097
3098
    curs_set(1);

3099
3100
3101
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3102
    /* Jump back to the main loop. */
3103
3104
    siglongjmp(jmpbuf, 1);
}
3105

3106
void allow_pending_sigwinch(bool allow)
3107
3108
3109
3110
3111
3112
3113
3114
3115
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3116
#endif /* !NANO_SMALL */
3117

3118
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3119
void do_toggle(const toggle *which)
3120
{
3121
    bool enabled;
3122

3123
    /* Even easier! */
3124
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3125

3126
    switch (which->val) {
3127
3128
3129
    case TOGGLE_SUSPEND_KEY:
	signal_init();
	break;
3130
#ifndef DISABLE_MOUSE
3131
3132
3133
    case TOGGLE_MOUSE_KEY:
	mouse_init();
	break;
3134
#endif
3135
    case TOGGLE_NOHELP_KEY:
3136
3137
	blank_statusbar();
	blank_bottombars();
Chris Allegretta's avatar
Chris Allegretta committed
3138
3139
3140
3141
	wrefresh(bottomwin);
	window_init();
	edit_refresh();
	display_main_list();
3142
	break;
3143
#ifdef ENABLE_COLOR
3144
3145
    case TOGGLE_SYNTAX_KEY:
	edit_refresh();
3146
	break;
3147
3148
3149
3150
3151
#endif
#ifdef ENABLE_NANORC
    case TOGGLE_WHITESPACE_KEY:
	edit_refresh();
	break;
3152
#endif
3153
    }
Chris Allegretta's avatar
Chris Allegretta committed
3154

Chris Allegretta's avatar
Chris Allegretta committed
3155
3156
3157
3158
3159
3160
3161
    /* 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
3162
}
3163
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3164

3165
3166
3167
3168
3169
3170
3171
3172
3173
void disable_extended_input(void)
{
    struct termios term;

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

3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
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);
}

3212
3213
3214
3215
/* 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
3216
3217
3218
3219
 * 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. */
3220
3221
3222
3223
3224
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3225
    disable_extended_input();
3226
3227
3228
3229
3230
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3231
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
3232
3233
{
    int optchr;
3234
3235
    int startline = 0;
	/* Line to try and start at. */
3236
#ifndef DISABLE_WRAPJUSTIFY
3237
3238
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
3239
#endif
3240
3241
3242
3243
3244
3245
3246
#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. */
3247
    bool meta_key, func_key;
Chris Allegretta's avatar
Chris Allegretta committed
3248
#ifdef HAVE_GETOPT_LONG
3249
    const struct option long_options[] = {
3250
3251
3252
	{"help", 0, 0, 'h'},
#ifdef ENABLE_MULTIBUFFER
	{"multibuffer", 0, 0, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
3253
3254
#endif
#ifdef ENABLE_NANORC
3255
#ifndef NANO_SMALL
3256
	{"historylog", 0, 0, 'H'},
3257
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3258
	{"ignorercfiles", 0, 0, 'I'},
3259
3260
3261
3262
#endif
#ifndef DISABLE_JUSTIFY
	{"quotestr", 1, 0, 'Q'},
#endif
3263
#ifdef HAVE_REGEX_H
3264
	{"regexp", 0, 0, 'R'},
3265
#endif
3266
	{"tabsize", 1, 0, 'T'},
Chris Allegretta's avatar
Chris Allegretta committed
3267
	{"version", 0, 0, 'V'},
3268
3269
#ifdef ENABLE_COLOR
	{"syntax", 1, 0, 'Y'},
3270
#endif
3271
	{"const", 0, 0, 'c'},
3272
	{"rebinddelete", 0, 0, 'd'},
3273
	{"nofollow", 0, 0, 'l'},
3274
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3275
	{"mouse", 0, 0, 'm'},
3276
#endif
3277
3278
3279
#ifndef DISABLE_OPERATINGDIR
	{"operatingdir", 1, 0, 'o'},
#endif
3280
	{"preserve", 0, 0, 'p'},
3281
3282
3283
3284
3285
#ifndef DISABLE_WRAPJUSTIFY
	{"fill", 1, 0, 'r'},
#endif
#ifndef DISABLE_SPELLER
	{"speller", 1, 0, 's'},
3286
#endif
3287
3288
	{"tempfile", 0, 0, 't'},
	{"view", 0, 0, 'v'},
3289
#ifndef DISABLE_WRAPPING
3290
	{"nowrap", 0, 0, 'w'},
3291
#endif
3292
3293
	{"nohelp", 0, 0, 'x'},
	{"suspend", 0, 0, 'z'},
3294
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3295
	{"smarthome", 0, 0, 'A'},
3296
	{"backup", 0, 0, 'B'},
3297
	{"backupdir", 1, 0, 'E'},
3298
	{"noconvert", 0, 0, 'N'},
3299
	{"smooth", 0, 0, 'S'},
3300
	{"restricted", 0, 0, 'Z'},
3301
3302
	{"autoindent", 0, 0, 'i'},
	{"cut", 0, 0, 'k'},
3303
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3304
3305
3306
3307
3308
	{0, 0, 0, 0}
    };
#endif

    setlocale(LC_ALL, "");
3309
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
3310
3311
3312
3313
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3314
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3315
3316
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
3317
    if (geteuid() == NANO_ROOT_UID)
3318
3319
	SET(NO_WRAP);
#endif
3320

3321
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
3322
#ifdef HAVE_GETOPT_LONG
3323
	getopt_long(argc, argv, "h?ABE:FHINQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz", long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
3324
#else
3325
	getopt(argc, argv, "h?ABE:FHINQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
3326
#endif
3327
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3328
3329

	switch (optchr) {
3330
3331
3332
3333
3334
3335
3336
3337
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
3338
#ifndef NANO_SMALL
3339
3340
3341
3342
3343
3344
3345
3346
3347
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
	    case 'E':
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
3348
#endif
3349
#ifdef ENABLE_MULTIBUFFER
3350
3351
3352
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
3353
3354
#endif
#ifdef ENABLE_NANORC
3355
#ifndef NANO_SMALL
3356
3357
3358
	    case 'H':
		SET(HISTORYLOG);
		break;
3359
#endif
3360
3361
3362
	    case 'I':
		SET(NO_RCFILE);
		break;
3363
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3364
#ifndef NANO_SMALL
3365
3366
3367
	    case 'N':
		SET(NO_CONVERT);
		break;
3368
3369
#endif
#ifndef DISABLE_JUSTIFY
3370
3371
3372
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
3373
#endif
3374
#ifdef HAVE_REGEX_H
3375
3376
3377
	    case 'R':
		SET(USE_REGEXP);
		break;
3378
3379
#endif
#ifndef NANO_SMALL
3380
3381
3382
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
3383
#endif
3384
3385
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
3386
3387
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3388
3389
3390
3391
3392
3393
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
3394
#ifdef ENABLE_COLOR
3395
3396
3397
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
3398
#endif
3399
3400
3401
3402
3403
3404
3405
3406
3407
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
3408
#ifndef NANO_SMALL
3409
3410
3411
3412
3413
3414
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
3415
#endif
3416
3417
3418
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
3419
#ifndef DISABLE_MOUSE
3420
3421
3422
	    case 'm':
		SET(USE_MOUSE);
		break;
3423
#endif
3424
#ifndef DISABLE_OPERATINGDIR
3425
3426
3427
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
3428
#endif
3429
3430
3431
	    case 'p':
		SET(PRESERVE);
		break;
3432
#ifndef DISABLE_WRAPJUSTIFY
3433
3434
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
3435
3436
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3437
3438
3439
3440
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
3441
#endif
3442
#ifndef DISABLE_SPELLER
3443
3444
3445
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
3446
#endif
3447
3448
3449
3450
3451
3452
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
3453
#ifndef DISABLE_WRAPPING
3454
3455
3456
	    case 'w':
		SET(NO_WRAP);
		break;
3457
#endif
3458
3459
3460
3461
3462
3463
3464
3465
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
3466
3467
3468
	}
    }

3469
3470
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
3471
3472
3473
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

3474
3475
3476
    /* 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. */
3477
3478
3479
3480
3481
3482
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
3483
/* We've read through the command line options.  Now back up the flags
3484
3485
 * 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
3486
3487
3488
3489
3490
#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
3491
#ifndef DISABLE_WRAPJUSTIFY
3492
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
3493
#endif
3494
3495
3496
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3497
3498
3499
3500
3501
3502
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
3503
	ssize_t tabsize_cpy = tabsize;
Chris Allegretta's avatar
Chris Allegretta committed
3504
3505
	long flags_cpy = flags;

3506
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3507
	operating_dir = NULL;
3508
#endif
3509
3510
3511
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
3512
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3513
	quotestr = NULL;
3514
3515
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
3516
	alt_speller = NULL;
3517
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3518
3519
3520
3521
3522
3523
3524
3525
3526

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
3527
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3528
3529
3530
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
3531
3532
3533
3534
3535
3536
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
#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
3549
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
3550
3551
3552
3553
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3554
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
3555
3556
3557
3558
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

3559
3560
3561
3562
3563
3564
3565
3566
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

3567
#ifndef NANO_SMALL
3568
    /* Set up the backup directory (unless we're using restricted mode,
3569
3570
3571
3572
     * 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. */
3573
3574
    if (!ISSET(RESTRICTED))
	init_backup_dir();
3575
3576
#endif

3577
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3578
    /* Set up the operating directory.  This entails chdir()ing there,
3579
     * so that file reads and writes will be based there. */
3580
3581
3582
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3583
#ifndef DISABLE_JUSTIFY
3584
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3585
	punct = mallocstrcpy(punct, ".?!");
3586
3587

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

Chris Allegretta's avatar
Chris Allegretta committed
3590
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3591
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
3592
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3593
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
3594
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3595
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
3596
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3597
		);
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
#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
3614
#endif /* !DISABLE_JUSTIFY */
3615

3616
3617
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
3618
     * command line and/or rcfile(s), check $SPELL for one, as Pico
3619
     * does (unless we're using restricted mode, in which case spell
3620
3621
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
3622
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
3623
3624
3625
3626
3627
3628
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

3629
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
3630
    /* If whitespace wasn't specified, set its default value. */
3631
3632
3633
3634
    if (whitespace == NULL)
	whitespace = mallocstrcpy(NULL, "  ");
#endif

3635
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
3636
    if (tabsize == -1)
3637
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3638

3639
    /* Back up the old terminal settings so that they can be restored. */
3640
    tcgetattr(0, &oldterm);
3641

3642
3643
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
3644
    initscr();
3645
    terminal_init();
3646

3647
    /* Set up the global variables and the shortcuts. */
3648
3649
    global_init(FALSE);
    shortcut_init(FALSE);
3650
3651

    /* Set up the signal handlers. */
3652
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
3653
3654

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

3658
    window_init();
3659
#ifndef DISABLE_MOUSE
3660
    mouse_init();
3661
#endif
3662

Chris Allegretta's avatar
Chris Allegretta committed
3663
#ifdef DEBUG
3664
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
3665
#endif
3666

3667
3668
3669
3670
3671
3672
3673
3674
    /* 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
3675
#ifdef ENABLE_MULTIBUFFER
3676
3677
3678
3679
3680
3681
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

    /* Read all the files after the first one on the command line into
     * new buffers. */
    {
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
	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;
		}
	    }
	}
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
    }
#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
3717
    }
3718
3719
3720
3721

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

3724
3725
3726
3727
3728
3729
3730
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

Chris Allegretta's avatar
Chris Allegretta committed
3731
    if (startline > 0)
3732
	do_gotoline(startline, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
3733

3734
3735
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
3736
    sigsetjmp(jmpbuf, 1);
3737
#endif
3738

Robert Siemborski's avatar
Robert Siemborski committed
3739
3740
    edit_refresh();

3741
    while (TRUE) {
3742
	reset_cursor();
3743
	if (ISSET(CONSTUPDATE))
3744
	    do_cursorpos(TRUE);
3745

3746
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
3747
	currshortcut = main_list;
3748
#endif
3749

3750
	kbinput = get_edit_input(&meta_key, &func_key, TRUE);
3751

3752
	/* Last gasp, stuff that's not in the main lists. */
3753
	if (kbinput != ERR && !is_cntrl_char(kbinput)) {
3754
3755
	    /* Don't stop unhandled printable sequences, so that people
	     * with odd character sets can type. */
3756
3757
3758
3759
	    if (ISSET(VIEW_MODE))
		print_view_warning();
	    else
		do_char(kbinput);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3760
	}
Chris Allegretta's avatar
Chris Allegretta committed
3761
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3762
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
3763
}