nano.c 124 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   nano.c                                                               *
 *                                                                        *
5
 *   Copyright (C) 1999-2005 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
Chris Allegretta's avatar
Chris Allegretta committed
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

118
119
120
    endwin();
    curses_ended = TRUE;

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

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

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

134
	die_save_file(filename);
135
    }
136

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

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

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

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

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

166
167
168
    /* 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. */
169
170
171
    if (ISSET(RESTRICTED))
	return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#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)
{
276
    size_t allocsize = 0;	/* Space needed for help_text. */
277
278
279
280
    const char *htx[3];		/* Untranslated help message.  We break
				 * it up into three chunks in case the
				 * full string is too long for the
				 * compiler to handle. */
281
    char *ptr;
282
    const shortcut *s;
283
284
#ifndef NANO_SMALL
    const toggle *t;
285
286
287
288
289
#ifdef ENABLE_NANORC
    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);

    UNSET(WHITESPACE_DISPLAY);
#endif
290
291
#endif

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

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

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

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

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

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

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

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

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

471
472
    ptr = help_text + strlen(help_text);

473
474
475
476
477
    /* 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. */
478
    for (s = currshortcut; s != NULL; s = s->next) {
479
	int entries = 0;
480

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

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

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

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

513
514
515
516
517
518
519
520
521
522
	/* 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. */
523
524
525
526
527
528
529
530
	    if (entries == 1 && s->metaval == NANO_ALT_SPACE) {
		char *space_ptr = display_string(_("Space"), 0, 5,
			FALSE);

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

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

536
537
538
539
540
541
542
543
	/* 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';
	    }
544
	    ptr += sprintf(ptr, "(M-%c)", toupper(s->miscval));
545
546
	    *(ptr++) = '\t';
	}
547

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

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

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

	    ptr += sprintf(ptr, help_ptr);

	    free(help_ptr);
	}
564

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

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

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

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

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

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

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

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

603
604
605
    return newnode;
}

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

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

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

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

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

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

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

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

641
642
643
644
645
646
647
    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
648
void delete_node(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
649
{
650
    assert(fileptr != NULL && fileptr->data != NULL);
651

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

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

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

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

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

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

    return head;
}

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

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

693
694
695
696
697
698
/* 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;
699

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

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

705
706
707
708
709
710
711
712
713
714
715
716
717
    /* 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;
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747

    /* 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. */
748
void unpartition_filestruct(partition **p)
749
750
{
    char *tmp;
751

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

    /* 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);
758
    fileage->prev = (*p)->top_prev;
759
760
    if (fileage->prev != NULL)
	fileage->prev->next = fileage;
761
    fileage->data = charealloc(fileage->data, strlen((*p)->top_data) +
762
	strlen(fileage->data) + 1);
763
764
    strcpy(fileage->data, (*p)->top_data);
    free((*p)->top_data);
765
766
767
768
769
770
    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. */
771
    filebot->next = (*p)->bot_next;
772
773
    if (filebot->next != NULL)
	filebot->next->prev = filebot;
774
    filebot->data = charealloc(filebot->data, strlen(filebot->data) +
775
776
777
	strlen((*p)->bot_data) + 1);
    strcat(filebot->data, (*p)->bot_data);
    free((*p)->bot_data);
778

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

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

791
792
793
794
795
796
797
798
/* Move all the text between (top, top_x) and (bot, bot_x) in the
 * current filestruct to a filestruct beginning with file_top and ending
 * with file_bot.  If no text is between (top, top_x) and (bot, bot_x),
 * don't do anything. */
void move_to_filestruct(filestruct **file_top, filestruct **file_bot,
	filestruct *top, size_t top_x, filestruct *bot, size_t bot_x)
{
    filestruct *top_save;
799
    size_t part_totsize;
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
    bool at_edittop;
#ifndef NANO_SMALL
    bool mark_inside = FALSE;
#endif

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

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

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

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

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

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

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

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

    top_save = fileage;

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

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

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

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

/* Copy all the text from the filestruct beginning with file_top and
 * ending with file_bot to the current filestruct at the current cursor
 * position. */
void copy_from_filestruct(filestruct *file_top, filestruct *file_bot)
{
    filestruct *top_save;
    int part_totlines;
898
    size_t part_totsize;
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
    bool at_edittop;

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

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

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

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

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

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

    top_save = fileage;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1099
    /* This is a special case. */
1100
    print1opt("-a, -b, -e,", "", NULL);
1101
    print1opt("-f, -g, -j", "", N_("(ignored, for Pico compatibility)"));
1102

Chris Allegretta's avatar
Chris Allegretta committed
1103
1104
1105
    exit(0);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1106
void version(void)
Chris Allegretta's avatar
Chris Allegretta committed
1107
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1108
1109
1110
1111
    printf(_(" GNU nano version %s (compiled %s, %s)\n"), VERSION,
	__TIME__, __DATE__);
    printf(
	_(" Email: nano@nano-editor.org	Web: http://www.nano-editor.org/"));
1112
    printf(_("\n Compiled options:"));
1113

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

1169
1170
1171
1172
1173
int no_more_space(void)
{
    return ISSET(MORE_SPACE) ? 1 : 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
1174
1175
int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
1176
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
1177
1178
}

1179
void nano_disabled_msg(void)
1180
{
Chris Allegretta's avatar
Chris Allegretta committed
1181
    statusbar(_("Sorry, support for this function has been disabled"));
1182
1183
}

Chris Allegretta's avatar
Chris Allegretta committed
1184
#ifndef NANO_SMALL
1185
void cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
1186
{
1187
1188
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
1189
1190
}

1191
1192
/* Return TRUE on success. */
bool open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
1193
{
1194
1195
1196
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
1197
1198
			/* Original and temporary handlers for
			 * SIGINT. */
1199
1200
1201
    bool sig_failed = FALSE;
    /* sig_failed means that sigaction() failed without changing the
     * signal handlers.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1202
     *
1203
1204
     * 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
1205

1206
    /* Make our pipes. */
1207

1208
1209
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
1210
	return FALSE;
1211
    }
1212

1213
    /* Fork a child. */
1214

1215
1216
1217
1218
1219
    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. */
1220

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1221
	execl("/bin/sh", "sh", "-c", command, NULL);
1222
1223
	exit(0);
    }
1224

1225
1226
1227
1228
1229
1230
1231
    /* Else continue as parent. */

    close(fd[1]);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    free(output);
1302
1303
}

1304
void do_backspace(void)
Chris Allegretta's avatar
Chris Allegretta committed
1305
{
1306
    if (current != fileage || current_x > 0) {
1307
	do_left(FALSE);
1308
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1309
1310
1311
    }
}

1312
void do_delete(void)
Chris Allegretta's avatar
Chris Allegretta committed
1313
{
1314
    bool do_refresh = FALSE;
1315
1316
1317
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

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

1320
    placewewant = xplustabs();
1321

1322
    if (current->data[current_x] != '\0') {
1323
1324
	int char_buf_len = parse_mbchar(current->data + current_x, NULL,
		NULL, NULL);
1325
	size_t line_len = strlen(current->data + current_x);
1326

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

1329
	/* Let's get dangerous. */
1330
	charmove(&current->data[current_x],
1331
1332
		&current->data[current_x + char_buf_len],
		line_len - char_buf_len + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1333

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

1346
	assert(current_x == strlen(current->data));
1347
1348
1349
1350
1351
1352

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

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

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
1369
	totsize--;
1370
#ifndef DISABLE_WRAPPING
1371
	wrap_reset();
1372
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1373
    } else
1374
	return;
Chris Allegretta's avatar
Chris Allegretta committed
1375
1376

    set_modified();
1377
1378
1379
1380

#ifdef ENABLE_COLOR
    /* If color syntaxes are turned on, we need to call
     * edit_refresh(). */
1381
    if (!ISSET(NO_COLOR_SYNTAX))
1382
1383
1384
1385
1386
1387
1388
	do_refresh = TRUE;
#endif

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

1391
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1392
{
1393
    do_output("\t", 1, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
1394
1395
}

1396
/* Someone hits Return *gasp!* */
1397
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1398
{
1399
1400
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1401

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1404
#ifndef NANO_SMALL
1405
1406
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1407
1408
1409
1410
1411
	/* 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)
1412
	    extra = current_x;
1413
1414
1415
1416
1417
1418
    }
#endif
    newnode->data = charalloc(strlen(current->data + current_x) +
	extra + 1);
    strcpy(&newnode->data[extra], current->data + current_x);
#ifndef NANO_SMALL
1419
    if (ISSET(AUTOINDENT)) {
1420
	charcpy(newnode->data, current->data, extra);
1421
1422
	totsize += mbstrlen(newnode->data);
    }
1423
#endif
1424
1425
1426
1427
1428
    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;
1429
    }
1430
1431
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1432

1433
    if (current == filebot)
1434
1435
	filebot = newnode;
    splice_node(current, newnode, current->next);
1436

1437
1438
    renumber(current);
    current = newnode;
1439
1440

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

1442
    totlines++;
1443
    totsize++;
1444
1445
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1446
1447
}

1448
#ifndef NANO_SMALL
1449
1450
1451
1452
/* Move to the next word.  If allow_update is FALSE, don't update the
 * screen afterward.  Return TRUE if we started on a word, and FALSE
 * otherwise. */
bool do_next_word(bool allow_update)
Chris Allegretta's avatar
Chris Allegretta committed
1453
{
1454
1455
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1456
    char *char_mb;
1457
    int char_mb_len;
1458
    bool started_on_word = FALSE;
1459

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

1462
1463
    char_mb = charalloc(mb_cur_max());

1464
1465
    /* Move forward until we find the character after the last letter of
     * the current word. */
1466
    while (current->data[current_x] != '\0') {
1467
1468
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1469

1470
1471
	/* If we've found it, stop moving forward through the current
	 * line. */
1472
	if (!is_word_mbchar(char_mb))
1473
	    break;
1474

1475
1476
	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
1477
	started_on_word = TRUE;
1478

1479
	current_x += char_mb_len;
1480
    }
Chris Allegretta's avatar
Chris Allegretta committed
1481

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

1486
    for (; current != NULL; current = current->next) {
1487
	while (current->data[current_x] != '\0') {
1488
	    char_mb_len = parse_mbchar(current->data + current_x,
1489
		char_mb, NULL, NULL);
1490

1491
1492
	    /* If we've found it, stop moving forward through the
	     * current line. */
1493
	    if (is_word_mbchar(char_mb))
1494
1495
		break;

1496
	    current_x += char_mb_len;
1497
	}
Chris Allegretta's avatar
Chris Allegretta committed
1498

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

1504
1505
	current_x = 0;
    }
1506

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1507
1508
    free(char_mb);

1509
1510
    /* If we haven't found it, leave the cursor at the end of the
     * file. */
1511
1512
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1513

1514
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1515

1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
    /* If allow_update is TRUE, update the screen. */
    if (allow_update)
	edit_redraw(current_save, pww_save);

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

void do_next_word_void(void)
{
    do_next_word(TRUE);
1527
}
Chris Allegretta's avatar
Chris Allegretta committed
1528

1529
/* Move to the previous word. */
1530
void do_prev_word(void)
1531
{
1532
1533
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1534
    char *char_mb;
1535
1536
    int char_mb_len;
    bool begin_line = FALSE;
1537

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

1540
1541
    char_mb = charalloc(mb_cur_max());

1542
1543
1544
    /* Move backward until we find the character before the first letter
     * of the current word. */
    while (!begin_line) {
1545
1546
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1547

1548
1549
	/* If we've found it, stop moving backward through the current
	 * line. */
1550
	if (!is_word_mbchar(char_mb))
1551
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1552

1553
1554
1555
1556
1557
1558
1559
1560
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);
    }

    /* Move backward until we find the last letter of the previous
     * word. */
1561
1562
1563
1564
1565
    if (current_x == 0)
	begin_line = TRUE;
    else
	current_x = move_mbleft(current->data, current_x);

1566
    for (; current != NULL; current = current->prev) {
1567
1568
	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1569
		char_mb, NULL, NULL);
1570
1571
1572

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

1576
1577
1578
1579
1580
1581
1582
1583
1584
	    if (current_x == 0)
		begin_line = TRUE;
	    else
		current_x = move_mbleft(current->data, current_x);
	}

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

1587
1588
1589
1590
	if (current->prev != NULL) {
	    begin_line = FALSE;
	    current_x = strlen(current->prev->data);
	}
Chris Allegretta's avatar
Chris Allegretta committed
1591
1592
    }

1593
1594
    /* If we haven't found it, leave the cursor at the beginning of the
     * file. */
1595
    if (current == NULL) {
1596
1597
	current = fileage;
	current_x = 0;
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
    /* If we've found it, move backward until we find the character
     * before the first letter of the previous word. */
    } else if (!begin_line) {
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);

	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1608
		char_mb, NULL, NULL);
1609
1610
1611

	    /* If we've found it, stop moving backward through the
	     * current line. */
1612
	    if (!is_word_mbchar(char_mb))
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
		break;

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

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

1627
1628
    free(char_mb);

1629
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1630

1631
    /* Update the screen. */
1632
    edit_redraw(current_save, pww_save);
1633
}
Chris Allegretta's avatar
Chris Allegretta committed
1634

1635
1636
1637
1638
1639
void do_word_count(void)
{
    size_t words = 0;
    size_t current_x_save = current_x, pww_save = placewewant;
    filestruct *current_save = current;
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
    bool old_mark_set = ISSET(MARK_ISSET);
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

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

1659
    /* Start at the top of the file. */
1660
1661
1662
1663
    current = fileage;
    current_x = 0;
    placewewant = 0;

1664
1665
1666
    /* Keep moving to the next word, without updating the screen, until
     * we reach the end of the file, incrementing the total word count
     * whenever we're on a word just before moving. */
1667
1668
1669
1670
1671
    while (current != filebot || current_x != 0) {
	if (do_next_word(FALSE))
	    words++;
    }

1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
    if (old_mark_set) {
	/* If the mark was on and we added a magicline, remove it
	 * now. */
	if (added_magicline)
	    remove_magicline();

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

    /* Restore where we were. */
1685
1686
1687
1688
1689
    current = current_save;
    current_x = current_x_save;
    placewewant = pww_save;

    /* Display the total word count on the statusbar. */
1690
1691
    statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") :
	_("Word Count"), (unsigned long)words);
1692
1693
}

1694
void do_mark(void)
1695
{
1696
1697
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1698
1699
1700
1701
1702
1703
1704
1705
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1706
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1707

1708
#ifndef DISABLE_WRAPPING
1709
1710
void wrap_reset(void)
{
1711
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1712
}
1713
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1714

1715
#ifndef DISABLE_WRAPPING
1716
1717
1718
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1719
bool do_wrap(filestruct *line)
Chris Allegretta's avatar
Chris Allegretta committed
1720
{
1721
    size_t line_len;
1722
	/* Length of the line we wrap. */
1723
    ssize_t wrap_loc;
1724
	/* Index of line->data where we wrap. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1725
#ifndef NANO_SMALL
1726
    const char *indent_string = NULL;
1727
	/* Indentation to prepend to the new line. */
1728
    size_t indent_len = 0;	/* The length of indent_string. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1729
#endif
1730
1731
    const char *after_break;	/* The text after the wrap point. */
    size_t after_break_len;	/* The length of after_break. */
1732
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1733
    const char *next_line = NULL;
1734
	/* The next line, minus indentation. */
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
    size_t next_line_len = 0;	/* The length of next_line. */
    char *new_line = NULL;	/* The line we create. */
    size_t new_line_len = 0;	/* The eventual length of new_line. */

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

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

1753
1754
1755
1756
1757
    assert(line != NULL && line->data != NULL);

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

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

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

1766
1767
1768
1769
1770
1771
    /* Otherwise, move forward to the character just after the blank. */
    wrap_loc += move_mbright(line->data + wrap_loc, 0);

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

1773
1774
1775
1776
1777
1778
1779
#ifndef NANO_SMALL
    /* If autoindent is turned on, and we're on the character just after
     * the indentation, we don't wrap. */
    if (ISSET(AUTOINDENT)) {
	/* Get the indentation of this line. */
	indent_string = line->data;
	indent_len = indent_length(indent_string);
1780

1781
1782
1783
1784
	if (wrap_loc == indent_len)
	    return FALSE;
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1785

1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
    /* Step 2, making the new wrap line.  It will consist of indentation
     * followed by the text after the wrap point, optionally followed by
     * a space (if the text after the wrap point doesn't end in a blank)
     * and the text of the next line, if they can fit without
     * wrapping, the next line exists, and the same_line_wrap flag is
     * set. */

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

    assert(strlen(after_break) == after_break_len);

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

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

1818
1819
	next_line = line->next->data;
	next_line_len = strlen(next_line);
Chris Allegretta's avatar
Chris Allegretta committed
1820

1821
	if ((after_break_len + next_line_len) <= fill) {
1822
	    wrapping = TRUE;
1823
	    new_line_len += next_line_len;
1824
1825
	}
    }
1826

1827
1828
1829
1830
1831
    /* new_line_len is now the length of the text that will be wrapped
     * to the next line, plus (if we're prepending to it) the length of
     * the text of the next line. */
    new_line_len += after_break_len;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1832
#ifndef NANO_SMALL
1833
    if (ISSET(AUTOINDENT)) {
1834
1835
1836
1837
1838
1839
1840
1841
1842
	if (wrapping) {
	    /* If we're wrapping, the indentation will come from the
	     * next line. */
	    indent_string = next_line;
	    indent_len = indent_length(indent_string);
	    next_line += indent_len;
	} else {
	    /* Otherwise, it will come from this line, in which case
	     * we should increase new_line_len to make room for it. */
1843
	    new_line_len += indent_len;
1844
1845
	    totsize += mbstrnlen(indent_string, indent_len);
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1846
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1847
1848
#endif

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

1853
1854
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
1855
1856
1857
1858
	/* Copy the indentation. */
	charcpy(new_line, indent_string, indent_len);
	new_line[indent_len] = '\0';
	new_line_len += indent_len;
1859
    }
1860
#endif
1861

1862
1863
1864
1865
1866
1867
    /* Copy all the text after the wrap point of the current line. */
    strcat(new_line, after_break);

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

1868
    if (wrapping) {
1869
1870
1871
1872
1873
1874
	/* If we're wrapping, copy the text from the next line, minus
	 * the indentation that we already copied above. */
	strcat(new_line, next_line);

	free(line->next->data);
	line->next->data = new_line;
1875
    } else {
1876
1877
1878
1879
1880
	/* Otherwise, make a new line and copy the text after where we
	 * broke this line to the beginning of the new line. */
	splice_node(current, make_new_node(current), current->next);

	current->next->data = new_line;
1881

1882
	totlines++;
1883
	totsize++;
1884
    }
Chris Allegretta's avatar
Chris Allegretta committed
1885

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

1889
1890
    /* Set the same_line_wrap flag, so that later wraps of this line
     * will be prepended to the next line. */
1891
    same_line_wrap = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
1892

1893
1894
1895
    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
    if (!wrapping)
1896
	renumber(line);
Chris Allegretta's avatar
Chris Allegretta committed
1897

1898
1899
    /* If the cursor was after the break point, we must move it.  We
     * also clear the same_line_wrap flag in this case. */
1900
    if (current_x > wrap_loc) {
1901
	same_line_wrap = FALSE;
1902
1903
1904
1905
	current = current->next;
	current_x -=
#ifndef NANO_SMALL
		-indent_len +
1906
#endif
1907
		wrap_loc;
1908
1909
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1910

1911
#ifndef NANO_SMALL
1912
    /* If the mark was on this line after the wrap point, we move it
1913
1914
1915
1916
     * down.  If it was on the next line and we wrapped onto that line,
     * we move it right. */
    if (mark_beginbuf == line && mark_beginx > wrap_loc) {
	mark_beginbuf = line->next;
1917
	mark_beginx -= wrap_loc - indent_len + 1;
1918
    } else if (wrapping && mark_beginbuf == line->next)
1919
	mark_beginx += after_break_len;
1920
#endif
1921

1922
    return TRUE;
1923
}
1924
#endif /* !DISABLE_WRAPPING */
1925

1926
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1927
/* A word is misspelled in the file.  Let the user replace it.  We
1928
1929
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1930
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1931
    char *save_search, *save_replace;
1932
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1933
    filestruct *edittop_save = edittop, *current_save = current;
1934
	/* Save where we are. */
1935
    bool canceled = FALSE;
1936
	/* The return value. */
1937
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1938
#ifndef NANO_SMALL
1939
1940
    bool reverse_search_set = ISSET(REVERSE_SEARCH);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1941
#ifdef HAVE_REGEX_H
1942
1943
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1944
1945
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1946
1947
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1948
1949
1950
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1951
    filestruct *top, *bot;
1952
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1953
#endif
1954

1955
    /* Make sure spell-check is case sensitive. */
1956
    SET(CASE_SENSITIVE);
1957

1958
#ifndef NANO_SMALL
1959
1960
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1961
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1962
#ifdef HAVE_REGEX_H
1963
1964
1965
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1966

1967
    /* Save the current search/replace strings. */
1968
1969
1970
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1971

1972
    /* Set the search/replace strings to the misspelled word. */
1973
1974
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1975

1976
1977
#ifndef NANO_SMALL
    if (old_mark_set) {
1978
	/* If the mark is on, partition the filestruct so that it
1979
1980
1981
	 * 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. */
1982
	mark_order((const filestruct **)&top, &top_x,
1983
	    (const filestruct **)&bot, &bot_x, &right_side_up);
1984
	filepart = partition_filestruct(top, top_x, bot, bot_x);
1985
	added_magicline = (filebot->data[0] != '\0');
1986
1987
1988
1989
	UNSET(MARK_ISSET);
    }
#endif

1990
    /* Start from the top of the file. */
1991
    edittop = fileage;
1992
    current = fileage;
1993
    current_x = (size_t)-1;
1994
    placewewant = 0;
1995

1996
    /* Find the first whole-word occurrence of word. */
1997
    findnextstr_wrap_reset();
1998
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) {
1999
2000
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
2001

2002
	    do_replace_highlight(TRUE, word);
2003

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2004
	    /* Allow all instances of the word to be corrected. */
2005
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
2006
#ifndef NANO_SMALL
2007
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2008
#endif
2009
			 _("Edit a replacement")) == -1);
2010

2011
	    do_replace_highlight(FALSE, word);
2012

2013
	    if (!canceled && strcmp(word, answer) != 0) {
2014
		current_x--;
2015
2016
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
2017
	    }
2018
2019

	    break;
2020
	}
2021
    }
Chris Allegretta's avatar
Chris Allegretta committed
2022

2023
2024
#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2025
2026
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2027
2028
	if (added_magicline)
	    remove_magicline();
2029

2030
2031
2032
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2033
	    bot_x += top_x;
2034
	if (right_side_up) {
2035
2036
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2037
	} else {
2038
2039
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2040
	}
2041

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2042
2043
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
2044
	unpartition_filestruct(&filepart);
2045
	SET(MARK_ISSET);
2046
2047
    }
#endif
2048

2049
2050
2051
2052
2053
    /* 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
2054

2055
    /* Restore where we were. */
2056
    edittop = edittop_save;
2057
2058
    current = current_save;
    current_x = current_x_save;
2059
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
2060

2061
    /* Restore case sensitivity setting. */
2062
2063
2064
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

2065
#ifndef NANO_SMALL
2066
2067
2068
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
2069
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2070
#ifdef HAVE_REGEX_H
2071
2072
2073
2074
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
2075

2076
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
2077
2078
}

2079
2080
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
2081
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
2082
{
2083
2084
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
2085
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
2086
2087
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
2088

2089
    /* Create all three pipes up front. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2090
2091
    if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
	pipe(uniq_fd) == -1)
2092
	return _("Could not create pipe");
Chris Allegretta's avatar
Chris Allegretta committed
2093

2094
    statusbar(_("Creating misspelled word list, please wait..."));
2095

2096
    /* A new process to run spell in. */
2097
    if ((pid_spell = fork()) == 0) {
2098

2099
	/* Child continues (i.e, future spell process). */
2100

2101
	close(spell_fd[0]);
2102

2103
	/* Replace the standard input with the temp file. */
2104
2105
2106
2107
2108
2109
	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;

2110
	close(tempfile_fd);
2111

2112
	/* Send spell's standard output to the pipe. */
2113
2114
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
2115

2116
	close(spell_fd[1]);
2117

2118
	/* Start spell program; we are using PATH. */
2119
	execlp("spell", "spell", NULL);
2120

2121
	/* Should not be reached, if spell is found. */
2122
	exit(1);
2123
    }
Chris Allegretta's avatar
Chris Allegretta committed
2124

2125
    /* Parent continues here. */
2126
2127
    close(spell_fd[1]);

2128
    /* A new process to run sort in. */
2129
2130
    if ((pid_sort = fork()) == 0) {

2131
2132
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
2133
2134
2135
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2136
2137
	close(spell_fd[0]);

2138
	/* Send sort's standard output to the new pipe. */
2139
2140
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2141
2142
2143

	close(sort_fd[1]);

2144
2145
2146
	/* 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. */
2147
2148
	execlp("sort", "sort", "-f", NULL);

2149
	/* Should not be reached, if sort is found. */
2150
2151
2152
	exit(1);
    }

2153
    close(spell_fd[0]);
2154
2155
    close(sort_fd[1]);

2156
    /* A new process to run uniq in. */
2157
2158
    if ((pid_uniq = fork()) == 0) {

2159
2160
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
2161
2162
2163
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2164
2165
	close(sort_fd[0]);

2166
	/* Send uniq's standard output to the new pipe. */
2167
2168
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2169
2170
2171

	close(uniq_fd[1]);

2172
	/* Start uniq program; we are using PATH. */
2173
2174
	execlp("uniq", "uniq", NULL);

2175
	/* Should not be reached, if uniq is found. */
2176
2177
2178
	exit(1);
    }

2179
    close(sort_fd[0]);
2180
    close(uniq_fd[1]);
2181

2182
    /* Child process was not forked successfully. */
2183
2184
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
2185
	return _("Could not fork");
2186
    }
2187

2188
    /* Get system pipe buffer size. */
2189
2190
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
2191
	return _("Could not get size of pipe buffer");
2192
    }
Chris Allegretta's avatar
Chris Allegretta committed
2193

2194
    /* Read in the returned spelling errors. */
2195
2196
2197
    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
2198

2199
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
2200
2201
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2202
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
2203
	read_buff_ptr += read_buff_read;
2204

2205
    }
Chris Allegretta's avatar
Chris Allegretta committed
2206

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2207
    *read_buff_ptr = '\0';
2208
    close(uniq_fd[0]);
2209

2210
    /* Process the spelling errors. */
2211
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
2212

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

2215
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2216
	    *read_buff_ptr = '\0';
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
	    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
2227

2228
    /* Special case where last word doesn't end with \n or \r. */
2229
2230
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
2231

2232
2233
    free(read_buff);
    replace_abort();
2234
    edit_refresh();
2235

2236
    /* Process end of spell process. */
2237
2238
2239
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
2240

2241
2242
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
2243

2244
2245
2246
2247
2248
2249
2250
2251
    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;
2252

2253
2254
  close_pipes_and_exit:
    /* Don't leak any handles. */
2255
2256
2257
2258
2259
2260
2261
2262
    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
2263
2264
}

2265
2266
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
2267
const char *do_alt_speller(char *tempfile_name)
2268
{
2269
2270
2271
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
    int current_y_save = current_y;
2272
2273
2274
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
2275
2276
    static char **spellargs = NULL;
    FILE *f;
2277
#ifndef NANO_SMALL
2278
    bool old_mark_set = ISSET(MARK_ISSET);
2279
2280
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
2281
2282
2283
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
2284
2285
    filestruct *top, *bot;
    size_t top_x, bot_x;
2286
    int mbb_lineno_save = 0;
2287
	/* We're going to close the current file, and open the output of
2288
2289
2290
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
2291
    size_t totsize_save = totsize;
2292
2293
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
2294

2295
    if (old_mark_set) {
2296
2297
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
2298
	mbb_lineno_save = mark_beginbuf->lineno;
2299
2300
	UNSET(MARK_ISSET);
    }
2301
#endif
2302

2303
    endwin();
2304

2305
    /* Set up an argument list to pass execvp(). */
2306
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2307
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
2308

2309
2310
2311
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2312
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
2313
2314
2315
2316
2317
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
2318

2319
    /* Start a new process for the alternate speller. */
2320
    if ((pid_spell = fork()) == 0) {
2321
	/* Start alternate spell program; we are using PATH. */
2322
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
2323

2324
2325
2326
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
2327

2328
2329
    /* Could not fork?? */
    if (pid_spell < 0)
2330
	return _("Could not fork");
2331

2332
    /* Wait for alternate speller to complete. */
2333
    wait(&alt_spell_status);
2334

2335
2336
    if (!WIFEXITED(alt_spell_status) ||
		WEXITSTATUS(alt_spell_status) != 0) {
2337
2338
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
2339
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;
2340
2341
2342
2343
2344

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
2345
2346

    refresh();
2347

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

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

2354
#ifndef NANO_SMALL
2355
    if (old_mark_set) {
2356
	size_t part_totsize;
2357
2358
2359
2360
2361
2362

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

2367
2368
2369
2370
	/* Get the number of characters in the marked text, and subtract
	 * it from the saved value of totsize.  Note that we don't need
	 * to save totlines. */
	get_totals(top, bot, NULL, &part_totsize);
2371
	totsize_save -= part_totsize;
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
    }
#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
2388
	filestruct *top_save = fileage;
2389

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2390
2391
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2392
2393
2394
	if (added_magicline)
	    remove_magicline();

2395
2396
2397
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2398
	    bot_x += top_x;
2399
	if (right_side_up) {
2400
2401
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2402
	} else {
2403
2404
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2405
	}
2406

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2407
2408
2409
2410
	/* 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. */
2411
	unpartition_filestruct(&filepart);
2412
2413

	/* Renumber starting with the beginning line of the old
2414
2415
2416
2417
	 * partition.  Also set totlines to the new number of lines in
	 * the file, add the number of characters in the spell-checked
	 * marked text to the saved value of totsize, and then make that
	 * saved value the actual value. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2418
	renumber(top_save);
2419
	totlines = filebot->lineno;
2420
2421
	totsize_save += totsize;
	totsize = totsize_save;
2422
2423
2424

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2425
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2426
	mark_beginbuf = current;
2427
2428
2429
2430

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

2433
2434
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2435
2436
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2437

2438
2439
    /* Go back to the old position, mark the file as modified, and
     * update the titlebar. */
2440
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2441
    SET(MODIFIED);
2442
    titlebar(NULL);
2443

2444
    return NULL;
2445
}
2446

2447
void do_spell(void)
2448
{
2449
    int i;
2450
2451
    FILE *temp_file;
    char *temp = safe_tempfile(&temp_file);
2452
    const char *spell_msg;
2453

2454
2455
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2456
	return;
2457
    }
2458

2459
2460
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2461
	i = write_marked(temp, temp_file, TRUE, FALSE);
2462
2463
    else
#endif
2464
	i = write_file(temp, temp_file, TRUE, FALSE, FALSE);
2465
2466

    if (i == -1) {
2467
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2468
	free(temp);
2469
	return;
2470
2471
    }

2472
#ifdef ENABLE_MULTIBUFFER
2473
2474
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2475
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2476
#endif
2477

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2478
    spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
2479
	do_int_speller(temp);
2480
    unlink(temp);
2481
    free(temp);
2482

2483
    if (spell_msg != NULL)
2484
2485
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2486
    else
2487
	statusbar(_("Finished checking spelling"));
2488
}
2489
#endif /* !DISABLE_SPELLER */
2490

2491
#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING)
2492
2493
/* We are trying to break a chunk off line.  We find the last blank such
 * that the display length to there is at most goal + 1.  If there is no
2494
2495
2496
2497
 * such blank, then we find the first blank.  We then take the last
 * blank in that group of blanks.  The terminating '\0' counts as a
 * blank, as does a '\n' if newline is TRUE. */
ssize_t break_line(const char *line, ssize_t goal, bool newline)
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
{
    ssize_t blank_loc = -1;
	/* Current tentative return value.  Index of the last blank we
	 * found with short enough display width.  */
    ssize_t cur_loc = 0;
	/* Current index in line. */
    int line_len;

    assert(line != NULL);

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

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

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

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

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

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

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

2533
2534
	while (*line != '\0') {
	    line_len = parse_mbchar(line, NULL, NULL, NULL);
2535

2536
2537
2538
2539
2540
	    if (is_blank_mbchar(line) || (newline && *line == '\n')) {
		if (!found_blank)
		    found_blank = TRUE;
	    } else if (found_blank)
		return cur_loc - line_len;
2541

2542
2543
	    line += line_len;
	    cur_loc += line_len;
2544
	}
2545
2546

	return -1;
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
    }

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

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

	line += line_len;
	blank_loc += line_len;
    }

    return blank_loc;
}
2565
#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */
2566

2567
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2568
2569
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2570
2571
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2572
    size_t len = 0;
2573
2574
    char *blank_mb;
    int blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2575

Chris Allegretta's avatar
Chris Allegretta committed
2576
    assert(line != NULL);
2577
2578
2579
2580

    blank_mb = charalloc(mb_cur_max());

    while (*line != '\0') {
2581
	blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL);
2582
2583
2584
2585
2586
2587

	if (!is_blank_mbchar(blank_mb))
	    break;

	line += blank_mb_len;
	len += blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2588
    }
2589
2590
2591

    free(blank_mb);

Chris Allegretta's avatar
Chris Allegretta committed
2592
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2593
}
2594
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2595

Chris Allegretta's avatar
Chris Allegretta committed
2596
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2597
/* justify_format() replaces blanks with spaces and multiple spaces by 1
2598
2599
 * (except it maintains up to 2 after a character in punct optionally
 * followed by a character in brackets, and removes all from the end).
Chris Allegretta's avatar
Chris Allegretta committed
2600
 *
2601
2602
 * justify_format() might make paragraph->data shorter, and change the
 * actual pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2603
 *
2604
2605
2606
 * justify_format() will not look at the first skip characters of
 * paragraph.  skip should be at most strlen(paragraph->data).  The
 * character at paragraph[skip + 1] must not be blank. */
2607
void justify_format(filestruct *paragraph, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2608
{
2609
2610
2611
2612
2613
    char *end, *new_end, *new_paragraph_data;
    size_t shift = 0;
#ifndef NANO_SMALL
    size_t mark_shift = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2614
2615

    /* These four asserts are assumptions about the input data. */
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
    assert(paragraph != NULL);
    assert(paragraph->data != NULL);
    assert(skip < strlen(paragraph->data));
    assert(!is_blank_char(paragraph->data[skip]));

    end = paragraph->data + skip;
    new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
    charcpy(new_paragraph_data, paragraph->data, skip);
    new_end = new_paragraph_data + skip;

    while (*end != '\0') {
2627
2628
	int end_len;

2629
2630
	/* If this character is blank, make sure that it's a space with
	 * no blanks after it. */
2631
2632
2633
	if (is_blank_mbchar(end)) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);

2634
2635
	    *new_end = ' ';
	    new_end++;
2636
	    end += end_len;
2637

2638
2639
2640
2641
2642
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2643

2644
#ifndef NANO_SMALL
2645
		/* Keep track of the change in the current line. */
2646
2647
		if (mark_beginbuf == paragraph &&
			mark_beginx >= end - paragraph->data)
2648
		    mark_shift += end_len;
2649
#endif
2650
	    }
2651
2652
2653
2654
	/* If this character is punctuation optionally followed by a
	 * bracket and then followed by blanks, make sure there are no
	 * more than two blanks after it, and make sure that the blanks
	 * are spaces. */
2655
2656
	} else if (mbstrchr(punct, end) != NULL) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);
2657

2658
	    while (end_len > 0) {
2659
2660
2661
		*new_end = *end;
		new_end++;
		end++;
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
		end_len--;
	    }

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

		while (end_len > 0) {
		    *new_end = *end;
		    new_end++;
		    end++;
		    end_len--;
		}
2674
	    }
2675

2676
2677
2678
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2679
2680
		*new_end = ' ';
		new_end++;
2681
		end += end_len;
2682
	    }
2683

2684
2685
2686
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2687
2688
		*new_end = ' ';
		new_end++;
2689
		end += end_len;
2690
	    }
2691

2692
2693
2694
2695
2696
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2697

Chris Allegretta's avatar
Chris Allegretta committed
2698
#ifndef NANO_SMALL
2699
2700
2701
2702
		/* Keep track of the change in the current line. */
		if (mark_beginbuf == paragraph &&
			mark_beginx >= end - paragraph->data)
		    mark_shift += end_len;
Chris Allegretta's avatar
Chris Allegretta committed
2703
#endif
2704
	    }
2705
2706
	/* If this character is neither blank nor punctuation, leave it
	 * alone. */
Chris Allegretta's avatar
Chris Allegretta committed
2707
	} else {
2708
2709
2710
2711
2712
2713
2714
2715
	    end_len = parse_mbchar(end, NULL, NULL, NULL);

	    while (end_len > 0) {
		*new_end = *end;
		new_end++;
		end++;
		end_len--;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2716
	}
Chris Allegretta's avatar
Chris Allegretta committed
2717
2718
    }

2719
2720
2721
2722
    assert(*end == '\0');

    *new_end = *end;

2723
    /* Make sure that there are no spaces at the end of the line. */
2724
2725
2726
2727
2728
    while (new_end > new_paragraph_data + skip &&
	*(new_end - 1) == ' ') {
	new_end--;
	shift++;
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2729

2730
2731
2732
2733
2734
    if (shift > 0) {
	totsize -= shift;
	null_at(&new_paragraph_data, new_end - new_paragraph_data);
	free(paragraph->data);
	paragraph->data = new_paragraph_data;
Chris Allegretta's avatar
Chris Allegretta committed
2735

Chris Allegretta's avatar
Chris Allegretta committed
2736
#ifndef NANO_SMALL
2737
2738
2739
2740
2741
2742
2743
	/* Adjust the mark coordinates to compensate for the change in
	 * the current line. */
	if (mark_beginbuf == paragraph) {
	    mark_beginx -= mark_shift;
	    if (mark_beginx > new_end - new_paragraph_data)
		mark_beginx = new_end - new_paragraph_data;
	}
Chris Allegretta's avatar
Chris Allegretta committed
2744
#endif
2745
2746
    } else
	free(new_paragraph_data);
Chris Allegretta's avatar
Chris Allegretta committed
2747
}
Chris Allegretta's avatar
Chris Allegretta committed
2748

Chris Allegretta's avatar
Chris Allegretta committed
2749
2750
2751
2752
2753
2754
/* 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. */
2755
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2756
{
2757
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2758
    regmatch_t matches;
2759
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2760

2761
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2762
	return 0;
2763
2764
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2765
2766
2767
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2768

2769
    /* Compute quote depth level. */
2770
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2771
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2772
2773
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2774
}
Chris Allegretta's avatar
Chris Allegretta committed
2775

Chris Allegretta's avatar
Chris Allegretta committed
2776
2777
2778
/* 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. */
2779
2780
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2781
{
2782
    /* Here is the assumption about a_quote. */
2783
    assert(a_quote == quote_length(a_line));
2784

2785
2786
    return (a_quote == quote_length(b_line) &&
	strncmp(a_line, b_line, a_quote) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2787
}
2788

2789
2790
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2791
bool indents_match(const char *a_line, size_t a_indent, const char
2792
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2793
{
Chris Allegretta's avatar
Chris Allegretta committed
2794
2795
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2796

2797
2798
    return (b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2799
}
Chris Allegretta's avatar
Chris Allegretta committed
2800

2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
/* Is foo the beginning of a paragraph?
 *
 *   A line of text consists of a "quote part", followed by an
 *   "indentation part", followed by text.  The functions quote_length()
 *   and indent_length() calculate these parts.
 *
 *   A line is "part of a paragraph" if it has a part not in the quote
 *   part or the indentation.
 *
 *   A line is "the beginning of a paragraph" if it is part of a
 *   paragraph and
 *	1) it is the top line of the file, or
 *	2) the line above it is not part of a paragraph, or
 *	3) the line above it does not have precisely the same quote
 *	   part, or
 *	4) the indentation of this line is not an initial substring of
 *	   the indentation of the previous line, or
 *	5) this line has no quote part and some indentation, and
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2819
2820
2821
2822
2823
 *	   autoindent isn't turned on.
 *   The reason for number 5) is that if autoindent isn't turned on,
 *   then an indented line is expected to start a paragraph, as in
 *   books.  Thus, nano can justify an indented paragraph only if
 *   autoindent is turned on. */
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
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. */
2862
void do_para_begin(bool allow_update)
2863
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2864
2865
    const filestruct *current_save = current;
    const size_t pww_save = placewewant;
2866
2867
2868
2869
2870
2871
2872

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2873
	    current_y--;
2874
2875
2876
	} while (!begpar(current));
    }

2877
2878
    if (allow_update)
	edit_redraw(current_save, pww_save);
2879
2880
}

2881
void do_para_begin_void(void)
2882
{
2883
2884
2885
2886
2887
2888
2889
    do_para_begin(TRUE);
}

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

2891
2892
2893
2894
2895
2896
2897
    if (foo == NULL)
	return FALSE;

    quote_len = quote_length(foo->data);

    return foo->data[quote_len + indent_length(foo->data +
	quote_len)] != '\0';
2898
2899
2900
2901
2902
}

/* 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. */
2903
void do_para_end(bool allow_update)
2904
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2905
2906
    const filestruct *const current_save = current;
    const size_t pww_save = placewewant;
2907
2908
2909
2910

    current_x = 0;
    placewewant = 0;

2911
    while (current->next != NULL && !inpar(current))
2912
2913
	current = current->next;

2914
    while (current->next != NULL && inpar(current->next) &&
2915
	    !begpar(current->next)) {
2916
	current = current->next;
2917
2918
	current_y++;
    }
2919
2920
2921
2922

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

2923
2924
2925
2926
2927
2928
2929
    if (allow_update)
	edit_redraw(current_save, pww_save);
}

void do_para_end_void(void)
{
    do_para_end(TRUE);
2930
2931
}

2932
2933
2934
2935
/* Put the next par_len lines, starting with first_line, into the
 * justify buffer, leaving copies of those lines in place.  Assume there
 * are enough lines after first_line.  Return the new copy of
 * first_line. */
2936
2937
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2938
{
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
    filestruct *top = first_line;
	/* The top of the paragraph we're backing up. */
    filestruct *bot = first_line;
	/* The bottom of the paragraph we're backing up. */
    size_t i;
	/* Generic loop variable. */
    size_t current_x_save = current_x;
    int fl_lineno_save = first_line->lineno;
    int edittop_lineno_save = edittop->lineno;
    int current_lineno_save = current->lineno;
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
    int mbb_lineno_save = 0;
2952
    size_t mark_beginx_save = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2953

2954
    if (old_mark_set) {
2955
	mbb_lineno_save = mark_beginbuf->lineno;
2956
2957
	mark_beginx_save = mark_beginx;
    }
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
#endif

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

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

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

2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
    /* Move upward from the last line of the paragraph to the first
     * line, putting first_line, edittop, current, and mark_beginbuf at
     * the same lines in the copied paragraph that they had in the
     * original paragraph. */
    top = current->prev;
    for (i = par_len; i > 0; i--) {
	if (top->lineno == fl_lineno_save)
	    first_line = top;
	if (top->lineno == edittop_lineno_save)
	    edittop = top;
	if (top->lineno == current_lineno_save)
	    current = top;
Chris Allegretta's avatar
Chris Allegretta committed
2985
#ifndef NANO_SMALL
2986
	if (old_mark_set && top->lineno == mbb_lineno_save) {
2987
	    mark_beginbuf = top;
2988
2989
	    mark_beginx = mark_beginx_save;
	}
Chris Allegretta's avatar
Chris Allegretta committed
2990
#endif
2991
	top = top->prev;
Chris Allegretta's avatar
Chris Allegretta committed
2992
    }
2993
2994
2995
2996
2997
2998
2999

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

    set_modified();

Chris Allegretta's avatar
Chris Allegretta committed
3000
3001
3002
    return first_line;
}

3003
3004
/* 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
3005
3006
 * quote length and paragraph length in *quote and *par.  Return TRUE if
 * we found a paragraph, or FALSE if there was an error or we didn't
3007
 * find a paragraph.
Chris Allegretta's avatar
Chris Allegretta committed
3008
 *
3009
3010
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
3011
bool find_paragraph(size_t *const quote, size_t *const par)
3012
{
Chris Allegretta's avatar
Chris Allegretta committed
3013
    size_t quote_len;
3014
3015
	/* Length of the initial quotation of the paragraph we search
	 * for. */
Chris Allegretta's avatar
Chris Allegretta committed
3016
    size_t par_len;
3017
3018
3019
3020
3021
3022
	/* Number of lines in the paragraph we search for. */
    filestruct *current_save;
	/* The line at the beginning of the paragraph we search for. */
    size_t current_y_save;
	/* The y-coordinate at the beginning of the paragraph we search
	 * for. */
3023

Chris Allegretta's avatar
Chris Allegretta committed
3024
#ifdef HAVE_REGEX_H
3025
3026
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
3027
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3028
    }
Chris Allegretta's avatar
Chris Allegretta committed
3029
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3030

Chris Allegretta's avatar
Chris Allegretta committed
3031
    assert(current != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
3032

3033
    /* Move back to the beginning of the current line. */
3034
3035
    current_x = 0;

3036
3037
    /* Find the first line of the current or next paragraph.  First, if
     * the current line isn't in a paragraph, move forward to the line
3038
3039
3040
3041
3042
3043
     * after the last line of the next paragraph.  If we end up on the
     * same line, or the line before that isn't in a paragraph, it means
     * that there aren't any paragraphs left, so get out.  Otherwise,
     * move back to the last line of the paragraph.  If the current line
     * is in a paragraph and it isn't the first line of that paragraph,
     * move back to the first line. */
3044
    if (!inpar(current)) {
3045
3046
	filestruct *current_save = current;

3047
	do_para_end(FALSE);
3048
	if (current == current_save || !inpar(current->prev))
3049
	    return FALSE;
3050
3051
	if (current->prev != NULL)
	    current = current->prev;
3052
    }
3053
3054
    if (!begpar(current))
	do_para_begin(FALSE);
3055

3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
    /* Now current is the first line of the paragraph.  Set quote_len to
     * the quotation length of that line, and set par_len to the number
     * of lines in this paragraph. */
    quote_len = quote_length(current->data);
    current_save = current;
    current_y_save = current_y;
    do_para_end(FALSE);
    par_len = current->lineno - current_save->lineno;
    current = current_save;
    current_y = current_y_save;
3066

3067
3068
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
3069

3070
3071
    *quote = quote_len;
    *par = par_len;
3072

3073
    return TRUE;
3074
3075
}

3076
3077
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
3078
void do_justify(bool full_justify)
3079
{
3080
3081
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
3082
	 * For restoring after unjustify. */
3083
    filestruct *last_par_line;
3084
3085
	/* Will be the line containing the newline after the last line
	 * of the result.  Also for restoring after unjustify. */
3086
3087

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3088
     * unjustifies.  Note that we don't need to save totlines. */
3089
3090
    size_t current_x_save = current_x;
    int current_y_save = current_y;
3091
3092
    unsigned long flags_save = flags;
    size_t totsize_save = totsize;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3093
    filestruct *edittop_save = edittop, *current_save = current;
3094
3095
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
3096
    size_t mark_beginx_save = mark_beginx;
3097
#endif
3098
    int kbinput;
3099
    bool meta_key, func_key, s_or_t, ran_func, finished;
3100

3101
3102
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
3103
	current = fileage;
3104
3105

    last_par_line = current;
3106
3107

    while (TRUE) {
3108
3109
	size_t i;
	    /* Generic loop variable. */
3110
3111
3112
	size_t quote_len;
	    /* Length of the initial quotation of the paragraph we
	     * justify. */
3113
3114
3115
	size_t indent_len;
	    /* Length of the initial indentation of the paragraph we
	     * justify. */
3116
	size_t par_len;
3117
3118
3119
	    /* Number of lines in the paragraph we justify. */
	ssize_t break_pos;
	    /* Where we will break lines. */
3120
	char *indent_string;
3121
3122
3123
	    /* The first indentation that doesn't match the initial
	     * indentation of the paragraph we justify.  This is put at
	     * the beginning of every line broken off the first
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3124
3125
3126
3127
3128
	     * justified line of the paragraph.  (Note that this works
	     * because a paragraph can only contain two indentations at
	     * most: the initial one, and a different one starting on a
	     * line after the first.  See the comment at begpar() for
	     * more about when a line is part of a paragraph.) */
3129
3130
3131
3132

	/* 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
3133
	 * length (number of lines).  Don't refresh the screen yet,
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
	 * since we'll do that after we justify.
	 *
	 * If the search failed, we do one of two things.  If we're
	 * justifying the whole file, we've found at least one
	 * paragraph, and the search didn't leave us on the last line of
	 * the file, it means that we should justify all the way to the
	 * last line of the file, so set the last line of the text to be
	 * justified to the last line of the file and break out of the
	 * loop.  Otherwise, it means that there are no paragraph(s) to
	 * justify, so refresh the screen and get out. */
3144
	if (!find_paragraph(&quote_len, &par_len)) {
3145
3146
	    if (full_justify && first_par_line != NULL &&
		first_par_line != filebot) {
3147
		last_par_line = filebot;
3148
3149
3150
		break;
	    } else {
		edit_refresh();
3151
		return;
3152
3153
	    }
	}
3154

3155
3156
3157
3158
3159
3160
	/* If we haven't already done it, copy the original paragraph(s)
	 * to the justify buffer. */
	if (first_par_line == NULL)
	    first_par_line = backup_lines(current, full_justify ?
		filebot->lineno - current->lineno : par_len, quote_len);

3161
3162
3163
	/* Initialize indent_string to a blank string. */
	indent_string = mallocstrcpy(NULL, "");

3164
3165
3166
3167
3168
3169
3170
3171
3172
	/* Find the first indentation in the paragraph that doesn't
	 * match the indentation of the first line, and save itin
	 * indent_string.  If all the indentations are the same, save
	 * the indentation of the first line in indent_string. */
	{
	    const filestruct *indent_line = current;
	    bool past_first_line = FALSE;

	    for (i = 0; i < par_len; i++) {
3173
		indent_len = quote_len +
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
			indent_length(indent_line->data + quote_len);

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

		    if (past_first_line)
			break;
		}

		if (indent_line == current)
		    past_first_line = TRUE;

		indent_line = indent_line->next;
	    }
	}

	/* Now tack all the lines of the paragraph together, skipping
	 * the quoting and indentation on all lines after the first. */
3194
3195
3196
3197
3198
3199
	for (i = 0; i < par_len - 1; i++) {
	    filestruct *next_line = current->next;
	    size_t line_len = strlen(current->data);
	    size_t next_line_len = strlen(current->next->data);

	    indent_len = quote_len + indent_length(current->next->data +
3200
		quote_len);
3201

3202
3203
3204
	    next_line_len -= indent_len;
	    totsize -= indent_len;

3205
	    /* We're just about to tack the next line onto this one.  If
3206
	     * this line isn't empty, make sure it ends in a space. */
3207
3208
3209
3210
3211
3212
3213
	    if (line_len > 0 && current->data[line_len - 1] != ' ') {
		line_len++;
		current->data = charealloc(current->data, line_len + 1);
		current->data[line_len - 1] = ' ';
		current->data[line_len] = '\0';
		totsize++;
	    }
3214

3215
3216
3217
3218
3219
3220
3221
	    current->data = charealloc(current->data, line_len +
		next_line_len + 1);
	    strcat(current->data, next_line->data + indent_len);

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

Chris Allegretta's avatar
Chris Allegretta committed
3223
#ifndef NANO_SMALL
3224
3225
3226
3227
	    /* Adjust the mark coordinates to compensate for the change
	     * in the next line. */
	    if (mark_beginbuf == next_line) {
		mark_beginbuf = current;
3228
		mark_beginx += line_len - indent_len;
3229
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3230
#endif
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243

	    unlink_node(next_line);
	    delete_node(next_line);

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

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

3244
3245
3246
	/* Call justify_format() on the paragraph, which will remove
	 * excess spaces from it and change all blank characters to
	 * spaces. */
3247
3248
3249
3250
3251
3252
	justify_format(current, quote_len +
		indent_length(current->data + quote_len));

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

3253
	    indent_len = strlen(indent_string);
3254
3255
3256
3257

	    /* If this line is too long, try to wrap it to the next line
	     * to make it short enough. */
	    break_pos = break_line(current->data + indent_len,
3258
		fill - strnlenpt(current->data, indent_len), FALSE);
3259
3260
3261
3262
3263

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

3264
3265
3266
	    /* Move forward to the character after the indentation and
	     * just after the space. */
	    break_pos += indent_len + 1;
3267

3268
	    assert(break_pos <= line_len);
3269

3270
3271
3272
	    /* Make a new line, and copy the text after where we're
	     * going to break this line to the beginning of the new
	     * line. */
3273
3274
	    splice_node(current, make_new_node(current), current->next);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3275
3276
	    /* If this paragraph is non-quoted, and autoindent isn't
	     * turned on, set the indentation length to zero so that the
3277
3278
	     * indentation is treated as part of the line. */
	    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
3279
#ifndef NANO_SMALL
3280
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
3281
#endif
3282
3283
		)
		indent_len = 0;
3284

3285
3286
3287
	    /* Copy the text after where we're going to break the
	     * current line to the next line. */
	    current->next->data = charalloc(indent_len + 1 + line_len -
3288
		break_pos);
3289
	    charcpy(current->next->data, indent_string, indent_len);
3290
	    strcpy(current->next->data + indent_len, current->data +
3291
		break_pos);
Chris Allegretta's avatar
Chris Allegretta committed
3292

3293
3294
	    par_len++;
	    totlines++;
3295
	    totsize += indent_len + 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3296

Chris Allegretta's avatar
Chris Allegretta committed
3297
#ifndef NANO_SMALL
3298
3299
3300
3301
	    /* Adjust the mark coordinates to compensate for the change
	     * in the current line. */
	    if (mark_beginbuf == current && mark_beginx > break_pos) {
		mark_beginbuf = current->next;
3302
		mark_beginx -= break_pos - indent_len;
3303
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3304
#endif
3305

3306
3307
	    /* Break the current line. */
	    null_at(&current->data, break_pos);
3308
3309
3310
3311
3312

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

3315
3316
3317
3318
	/* We're done breaking lines, so we don't need indent_string
	 * anymore. */
	free(indent_string);

3319
3320
3321
3322
3323
	/* Go to the next line, the line after the last line of the
	 * paragraph. */
	current_y++;
	current = current->next;

3324
3325
3326
	/* 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. */
3327
3328
	if (!full_justify)
	    break;
3329
    }
3330

3331
3332
3333
    /* 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
3334
3335
3336
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
3337
    last_par_line = current;
3338
3339
3340
3341
3342
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
3343

3344
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3345

3346
    statusbar(_("Can now UnJustify!"));
3347

3348
    /* Display the shortcut list with UnJustify. */
3349
    shortcut_init(TRUE);
3350
    display_main_list();
3351

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3352
3353
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
3354
3355
    kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
	&finished, FALSE);
3356

3357
3358
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
3359
	/* Restore the justify we just did (ungrateful user!). */
Chris Allegretta's avatar
Chris Allegretta committed
3360
3361
3362
3363
3364
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

3365
	/* Splice the justify buffer back into the file, but only if we
3366
3367
	 * actually justified something. */
	if (first_par_line != NULL) {
3368
	    filestruct *bot_save;
3369

3370
3371
3372
3373
	    /* Partition the filestruct so that it contains only the
	     * text of the justified paragraph. */
	    filepart = partition_filestruct(first_par_line, 0,
		last_par_line, 0);
3374

3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
	    /* Remove the text of the justified paragraph, and
	     * put the text in the justify buffer in its place. */
	    free_filestruct(fileage);
	    fileage = jusbuffer;
	    filebot = jusbottom;

	    bot_save = filebot;

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

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

	    /* Restore global variables from before the justify. */
	    totsize = totsize_save;
	    totlines = filebot->lineno;
Chris Allegretta's avatar
Chris Allegretta committed
3396
#ifndef NANO_SMALL
3397
3398
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
3399
#endif
3400
3401
3402
3403
3404
3405
3406
3407
3408
	    flags = flags_save;

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

	    if (!ISSET(MODIFIED))
		titlebar(NULL);
	    edit_refresh();
	}
3409
    } else {
3410
	unget_kbinput(kbinput, meta_key, func_key);
3411

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3412
	/* Blow away the text in the justify buffer. */
3413
3414
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
3415
    }
3416

3417
    blank_statusbar();
3418

3419
    /* Display the shortcut list with UnCut. */
3420
    shortcut_init(FALSE);
3421
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
3422
}
3423

3424
void do_justify_void(void)
3425
{
3426
    do_justify(FALSE);
3427
3428
}

3429
void do_full_justify(void)
3430
{
3431
    do_justify(TRUE);
3432
}
3433
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
3434

3435
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
3436
{
3437
3438
    int i;

3439
3440
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
3441
    else if (ISSET(TEMP_FILE))
3442
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3443
    else
3444
3445
3446
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

3447
3448
#ifdef DEBUG
    dump_buffer(fileage);
3449
#endif
3450

3451
    if (i == 0 || (i == 1 && do_writeout(TRUE) == 0)) {
3452
#ifdef ENABLE_MULTIBUFFER
3453
	/* Exit only if there are no more open file buffers. */
3454
	if (!close_open_file())
3455
#endif
3456
	    finish();
3457
    } else if (i != 1)
3458
3459
3460
3461
3462
3463
3464
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
3465
3466
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
3467
3468
3469
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
3470
    sigaction(SIGQUIT, &act, NULL);
3471

3472
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
3473
    act.sa_handler = handle_hupterm;
3474
    sigaction(SIGHUP, &act, NULL);
3475
    sigaction(SIGTERM, &act, NULL);
3476

3477
#ifndef NANO_SMALL
3478
    /* Trap SIGWINCH because we want to handle window resizes. */
3479
3480
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
3481
    allow_pending_sigwinch(FALSE);
3482
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3483

3484
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
3485
3486
3487
3488
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
3489
3490
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
3491
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
3492

3493
3494
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
3495

3496
3497
3498
3499
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
3500

3501
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
3502
void handle_hupterm(int signal)
3503
{
3504
    die(_("Received SIGHUP or SIGTERM\n"));
3505
}
3506

3507
/* Handler for SIGTSTP (suspend). */
3508
void do_suspend(int signal)
3509
3510
{
    endwin();
3511
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
3512
    fflush(stdout);
3513

3514
    /* Restore the old terminal settings. */
3515
    tcsetattr(0, TCSANOW, &oldterm);
3516

3517
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3518
     * suspended. */
3519
3520
3521
3522
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3523
    /* Do what mutt does: send ourselves a SIGSTOP. */
3524
3525
    kill(0, SIGSTOP);
}
3526

3527
/* Handler for SIGCONT (continue after suspend). */
3528
void do_cont(int signal)
3529
{
3530
#ifndef NANO_SMALL
3531
3532
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3533
    handle_sigwinch(0);
3534
#else
3535
3536
    /* Just update the screen. */
    doupdate();
3537
3538
3539
3540
3541
3542
3543
#endif
}

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

3547
    if (tty == NULL)
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
	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;

3564
3565
    check_die_too_small();
    resize_variables();
3566

3567
3568
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
3569
	unpartition_filestruct(&filepart);
3570

3571
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3572
3573
    /* If the justify buffer isn't empty, blow away the text in it and
     * display the shortcut list with UnCut. */
3574
3575
3576
3577
3578
3579
3580
    if (jusbuffer != NULL) {
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
	shortcut_init(FALSE);
    }
#endif

3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
#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
3594

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

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

3601
3602
3603
3604
3605
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

3606
    /* Redraw the contents of the windows that need it. */
3607
    blank_statusbar();
3608
    currshortcut = main_list;
3609
3610
    total_refresh();

3611
3612
3613
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3614
    /* Jump back to the main loop. */
3615
3616
    siglongjmp(jmpbuf, 1);
}
3617

3618
void allow_pending_sigwinch(bool allow)
3619
3620
3621
3622
3623
3624
3625
3626
3627
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3628
#endif /* !NANO_SMALL */
3629

3630
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3631
void do_toggle(const toggle *which)
3632
{
3633
    bool enabled;
3634

3635
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3636

3637
    switch (which->val) {
3638
#ifndef DISABLE_MOUSE
3639
3640
3641
	case TOGGLE_MOUSE_KEY:
	    mouse_init();
	    break;
3642
#endif
3643
	case TOGGLE_MORESPACE_KEY:
3644
3645
	case TOGGLE_NOHELP_KEY:
	    window_init();
3646
	    total_refresh();
3647
	    break;
3648
3649
	case TOGGLE_SUSPEND_KEY:
	    signal_init();
3650
	    break;
3651
#ifdef ENABLE_NANORC
3652
	case TOGGLE_WHITESPACE_KEY:
3653
	    titlebar(NULL);
3654
3655
	    edit_refresh();
	    break;
3656
3657
3658
3659
3660
#endif
#ifdef ENABLE_COLOR
	case TOGGLE_SYNTAX_KEY:
	    edit_refresh();
	    break;
3661
#endif
3662
    }
Chris Allegretta's avatar
Chris Allegretta committed
3663

Chris Allegretta's avatar
Chris Allegretta committed
3664
    enabled = ISSET(which->flag);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3665
3666
3667

    if (which->val == TOGGLE_NOHELP_KEY ||
	which->val == TOGGLE_WRAP_KEY)
Chris Allegretta's avatar
Chris Allegretta committed
3668
	enabled = !enabled;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3669
3670
3671

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

3675
void disable_extended_io(void)
3676
3677
3678
3679
3680
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_lflag &= ~IEXTEN;
3681
    term.c_oflag &= ~OPOST;
3682
3683
3684
    tcsetattr(0, TCSANOW, &term);
}

3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
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);
}

3723
3724
3725
3726
/* 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
3727
 * echoing of characters as they're typed.  Finally, disable extended
3728
3729
3730
 * input and output processing, disable interpretation of the special
 * control keys, and if we're not in preserve mode, disable
 * interpretation of the flow control characters too. */
3731
3732
3733
3734
3735
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3736
    disable_extended_io();
3737
3738
3739
3740
3741
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3742
int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
3743
	*ran_func, bool *finished, bool allow_funcs)
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
{
    int input;
	/* The character we read in. */
    static int *kbinput = NULL;
	/* The input buffer. */
    static size_t kbinput_len = 0;
	/* The length of the input buffer. */
    const shortcut *s;
    bool have_shortcut;
#ifndef NANO_SMALL
    const toggle *t;
    bool have_toggle;
#endif

    *s_or_t = FALSE;
3759
    *ran_func = FALSE;
3760
    *finished = FALSE;
3761
3762
3763
3764
3765
3766
3767

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

#ifndef DISABLE_MOUSE
    /* If we got a mouse click and it was on a shortcut, read in the
     * shortcut character. */
3768
    if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) {
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
	if (do_mouse())
	    input = get_kbinput(edit, meta_key, func_key);
	else
	    input = ERR;
    }
#endif

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

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

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

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

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

    if (allow_funcs) {
3801
3802
3803
3804
3805
	/* If we got a character, and it isn't a shortcut or toggle,
	 * it's a normal text character.  Display the warning if we're
	 * in view mode, or add the character to the input buffer if
	 * we're not. */
	if (input != ERR && *s_or_t == FALSE) {
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
	    if (ISSET(VIEW_MODE))
		print_view_warning();
	    else {
		kbinput_len++;
		kbinput = (int *)nrealloc(kbinput, kbinput_len *
			sizeof(int));
		kbinput[kbinput_len - 1] = input;
	    }
	}

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

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

3832
		do_output(output, kbinput_len, FALSE);
3833
3834

		free(output);
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844

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

	if (have_shortcut) {
	    switch (input) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3845
		/* Handle the "universal" statusbar prompt shortcuts. */
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
		case NANO_XON_KEY:
		    statusbar(_("XON ignored, mumble mumble."));
		    break;
		case NANO_XOFF_KEY:
		    statusbar(_("XOFF ignored, mumble mumble."));
		    break;
#ifndef NANO_SMALL
		case NANO_SUSPEND_KEY:
		    if (ISSET(SUSPEND))
			do_suspend(0);
		    break;
#endif
3858
		/* Handle the normal edit window shortcuts, setting
3859
3860
3861
		 * ran_func to TRUE if we try to run their associated
		 * functions and setting finished to TRUE to indicate
		 * that we're done after trying to run their associated
3862
		 * functions. */
3863
3864
3865
3866
3867
3868
		default:
		    /* Blow away the text in the cutbuffer if we aren't
		     * cutting text. */
		    if (s->func != do_cut_text)
			cutbuffer_reset();

3869
		    if (s->func != NULL) {
3870
			*ran_func = TRUE;
3871
3872
3873
3874
			if (ISSET(VIEW_MODE) && !s->viewok)
			    print_view_warning();
			else
			    s->func();
3875
		    }
3876
		    *finished = TRUE;
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
		    break;
	    }
	}
#ifndef NANO_SMALL
	else if (have_toggle) {
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
	    /* Toggle the flag associated with this shortcut. */
	    if (allow_funcs)
		do_toggle(t);
	}
#endif
	else
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
    }

    return input;
}

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

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

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

	    /* Subtract out the size of topwin.  Perhaps we need a
	     * constant somewhere? */
3918
	    mouse_y -= (2 - no_more_space());
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953

	    sameline = (mouse_y == current_y);

	    /* Move to where the click occurred. */
	    for (; current_y < mouse_y && current->next != NULL; current_y++)
		current = current->next;
	    for (; current_y > mouse_y && current->prev != NULL; current_y--)
		current = current->prev;

	    xcur = actual_x(current->data, get_page_start(xplustabs()) +
		mouse_x);

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

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

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

3954
/* The user typed kbinput_len multibyte characters.  Add them to the
3955
3956
3957
 * edit buffer, filtering out all control characters if allow_cntrls is
 * TRUE. */
void do_output(char *output, size_t output_len, bool allow_cntrls)
3958
{
3959
    size_t current_len, i = 0;
3960
3961
3962
3963
3964
    bool old_constupdate = ISSET(CONSTUPDATE);
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

3965
3966
    char *char_buf = charalloc(mb_cur_max());
    int char_buf_len;
3967
3968
3969

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

3970
3971
    current_len = strlen(current->data);

3972
3973
    /* Turn off constant cursor position display. */
    UNSET(CONSTUPDATE);
3974

3975
    while (i < output_len) {
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
	/* If allow_cntrls is FALSE, filter out nulls and newlines,
	 * since they're control characters. */
	if (allow_cntrls) {
	    /* Null to newline, if needed. */
	    if (output[i] == '\0')
		output[i] = '\n';
	    /* Newline to Enter, if needed. */
	    else if (output[i] == '\n') {
		do_enter();
		i++;
		continue;
	    }
3988
3989
	}

3990
3991
3992
	/* Interpret the next multibyte character.  If it's an invalid
	 * multibyte character, interpret it as though it's a byte
	 * character. */
3993
	char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL);
3994
3995

	i += char_buf_len;
3996

3997
3998
3999
4000
	/* If allow_cntrls is FALSE, filter out a control character. */
	if (!allow_cntrls && is_cntrl_mbchar(output + i - char_buf_len))
	    continue;

4001
4002
4003
4004
4005
4006
	/* When a character is inserted on the current magicline, it
	 * means we need a new one! */
	if (filebot == current)
	    new_magicline();

	/* More dangerousness fun =) */
4007
4008
	current->data = charealloc(current->data, current_len +
		(char_buf_len * 2));
4009

4010
	assert(current_x <= current_len);
4011

4012
	charmove(&current->data[current_x + char_buf_len],
4013
		&current->data[current_x],
4014
4015
4016
		current_len - current_x + char_buf_len);
	charcpy(&current->data[current_x], char_buf, char_buf_len);
	current_len += char_buf_len;
4017
	totsize++;
4018
4019
4020
4021
4022
	set_modified();

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

4026
	do_right(FALSE);
4027
4028

#ifndef DISABLE_WRAPPING
4029
4030
	/* If we're wrapping text, we need to call edit_refresh(). */
	if (!ISSET(NO_WRAP)) {
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
	    bool do_refresh_save = do_refresh;

	    do_refresh = do_wrap(current);

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

#ifdef ENABLE_COLOR
	/* If color syntaxes are turned on, we need to call
	 * edit_refresh(). */
4045
	if (!ISSET(NO_COLOR_SYNTAX))
4046
4047
4048
4049
	    do_refresh = TRUE;
#endif
    }

4050
4051
    /* Turn constant cursor position display back on if it was on
     * before. */
4052
4053
4054
    if (old_constupdate)
	SET(CONSTUPDATE);

4055
    free(char_buf);
4056

4057
4058
4059
4060
4061
4062
    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
}

4063
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
4064
4065
{
    int optchr;
4066
    int startline = 1;
4067
	/* Line to try and start at. */
4068
4069
    ssize_t startcol = 1;
	/* Column to try and start at. */
4070
#ifndef DISABLE_WRAPJUSTIFY
4071
4072
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
4073
#endif
4074
4075
4076
4077
4078
#ifdef ENABLE_MULTIBUFFER
    bool old_multibuffer;
	/* The old value of the multibuffer option, restored after we
	 * load all files on the command line. */
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4079
#ifdef HAVE_GETOPT_LONG
4080
    const struct option long_options[] = {
4081
	{"help", 0, NULL, 'h'},
4082
#ifdef ENABLE_MULTIBUFFER
4083
	{"multibuffer", 0, NULL, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
4084
4085
#endif
#ifdef ENABLE_NANORC
4086
#ifndef NANO_SMALL
4087
	{"historylog", 0, NULL, 'H'},
4088
#endif
4089
	{"ignorercfiles", 0, NULL, 'I'},
4090
#endif
4091
	{"morespace", 0, NULL, 'O'},
4092
#ifndef DISABLE_JUSTIFY
4093
	{"quotestr", 1, NULL, 'Q'},
4094
#endif
4095
4096
	{"tabsize", 1, NULL, 'T'},
	{"version", 0, NULL, 'V'},
4097
#ifdef ENABLE_COLOR
4098
	{"syntax", 1, NULL, 'Y'},
4099
#endif
4100
4101
4102
	{"const", 0, NULL, 'c'},
	{"rebinddelete", 0, NULL, 'd'},
	{"nofollow", 0, NULL, 'l'},
4103
#ifndef DISABLE_MOUSE
4104
	{"mouse", 0, NULL, 'm'},
4105
#endif
4106
#ifndef DISABLE_OPERATINGDIR
4107
	{"operatingdir", 1, NULL, 'o'},
4108
#endif
4109
	{"preserve", 0, NULL, 'p'},
4110
#ifndef DISABLE_WRAPJUSTIFY
4111
	{"fill", 1, NULL, 'r'},
4112
4113
#endif
#ifndef DISABLE_SPELLER
4114
	{"speller", 1, NULL, 's'},
4115
#endif
4116
4117
	{"tempfile", 0, NULL, 't'},
	{"view", 0, NULL, 'v'},
4118
#ifndef DISABLE_WRAPPING
4119
	{"nowrap", 0, NULL, 'w'},
4120
#endif
4121
4122
	{"nohelp", 0, NULL, 'x'},
	{"suspend", 0, NULL, 'z'},
4123
#ifndef NANO_SMALL
4124
4125
4126
4127
4128
4129
4130
4131
	{"smarthome", 0, NULL, 'A'},
	{"backup", 0, NULL, 'B'},
	{"backupdir", 1, NULL, 'E'},
	{"noconvert", 0, NULL, 'N'},
	{"smooth", 0, NULL, 'S'},
	{"restricted", 0, NULL, 'Z'},
	{"autoindent", 0, NULL, 'i'},
	{"cut", 0, NULL, 'k'},
4132
#endif
4133
	{NULL, 0, NULL, 0}
Chris Allegretta's avatar
Chris Allegretta committed
4134
4135
4136
    };
#endif

4137
4138
4139
#ifdef NANO_WIDE
    {
	/* If the locale set doesn't exist, or it exists but doesn't
4140
4141
	 * include the case-insensitive string "UTF8" or "UTF-8", we
	 * shouldn't go into UTF-8 mode. */
4142
4143
4144
	char *locale = setlocale(LC_ALL, "");

	if (locale == NULL || (locale != NULL &&
4145
		strcasestr(locale, "UTF8") == NULL &&
4146
		strcasestr(locale, "UTF-8") == NULL))
4147
	    SET(NO_UTF8);
4148
4149
4150

#ifdef USE_SLANG
	if (!ISSET(NO_UTF8))
4151
	    SLutf8_enable(TRUE);
4152
#endif
4153
4154
    }
#else
Chris Allegretta's avatar
Chris Allegretta committed
4155
    setlocale(LC_ALL, "");
4156
4157
#endif

4158
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
4159
4160
4161
4162
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4163
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4164
4165
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
4166
    if (geteuid() == NANO_ROOT_UID)
4167
4168
	SET(NO_WRAP);
#endif
4169

4170
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
4171
#ifdef HAVE_GETOPT_LONG
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4172
4173
4174
	getopt_long(argc, argv,
		"h?ABDE:FHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz",
		long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
4175
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4176
4177
	getopt(argc, argv,
		"h?ABDE:FHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
4178
#endif
4179
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
4180
4181

	switch (optchr) {
4182
4183
4184
4185
4186
4187
4188
4189
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
4190
#ifndef NANO_SMALL
4191
4192
4193
4194
4195
4196
4197
4198
4199
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
	    case 'E':
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
4200
#endif
4201
#ifdef ENABLE_MULTIBUFFER
4202
4203
4204
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
4205
4206
#endif
#ifdef ENABLE_NANORC
4207
#ifndef NANO_SMALL
4208
4209
4210
	    case 'H':
		SET(HISTORYLOG);
		break;
4211
#endif
4212
4213
4214
	    case 'I':
		SET(NO_RCFILE);
		break;
4215
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4216
#ifndef NANO_SMALL
4217
4218
4219
	    case 'N':
		SET(NO_CONVERT);
		break;
4220
#endif
4221
4222
4223
	    case 'O':
		SET(MORE_SPACE);
		break;
4224
#ifndef DISABLE_JUSTIFY
4225
4226
4227
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
4228
#endif
4229
#ifndef NANO_SMALL
4230
4231
4232
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
4233
#endif
4234
4235
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
4236
4237
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4238
4239
4240
4241
4242
4243
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
4244
#ifdef ENABLE_COLOR
4245
4246
4247
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
4248
#endif
4249
4250
4251
4252
4253
4254
4255
4256
4257
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
4258
#ifndef NANO_SMALL
4259
4260
4261
4262
4263
4264
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
4265
#endif
4266
4267
4268
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
4269
#ifndef DISABLE_MOUSE
4270
4271
4272
	    case 'm':
		SET(USE_MOUSE);
		break;
4273
#endif
4274
#ifndef DISABLE_OPERATINGDIR
4275
4276
4277
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
4278
#endif
4279
4280
4281
	    case 'p':
		SET(PRESERVE);
		break;
4282
#ifndef DISABLE_WRAPJUSTIFY
4283
4284
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
4285
4286
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4287
4288
4289
4290
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
4291
#endif
4292
#ifndef DISABLE_SPELLER
4293
4294
4295
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
4296
#endif
4297
4298
4299
4300
4301
4302
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
4303
#ifndef DISABLE_WRAPPING
4304
4305
4306
	    case 'w':
		SET(NO_WRAP);
		break;
4307
#endif
4308
4309
4310
4311
4312
4313
4314
4315
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
4316
4317
4318
	}
    }

4319
4320
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
4321
4322
4323
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

4324
4325
4326
    /* 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. */
4327
4328
4329
4330
4331
4332
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
4333
/* We've read through the command line options.  Now back up the flags
4334
4335
 * 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
4336
4337
4338
4339
4340
#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
4341
#ifndef DISABLE_WRAPJUSTIFY
4342
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
4343
#endif
4344
4345
4346
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4347
4348
4349
4350
4351
4352
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
4353
	ssize_t tabsize_cpy = tabsize;
4354
	unsigned long flags_cpy = flags;
Chris Allegretta's avatar
Chris Allegretta committed
4355

4356
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4357
	operating_dir = NULL;
4358
#endif
4359
4360
4361
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
4362
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4363
	quotestr = NULL;
4364
4365
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
4366
	alt_speller = NULL;
4367
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4368
4369
4370
4371
4372
4373
4374
4375
4376

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
4377
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4378
4379
4380
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
4381
4382
4383
4384
4385
4386
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
#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
4399
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
4400
4401
4402
4403
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4404
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
4405
4406
4407
4408
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

4409
4410
4411
4412
4413
4414
4415
4416
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4417
#ifndef NANO_SMALL
4418
    /* Set up the backup directory (unless we're using restricted mode,
4419
4420
4421
4422
     * 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. */
4423
4424
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4425
4426
#endif

4427
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4428
    /* Set up the operating directory.  This entails chdir()ing there,
4429
     * so that file reads and writes will be based there. */
4430
4431
4432
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4433
#ifndef DISABLE_JUSTIFY
4434
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4435
	punct = mallocstrcpy(punct, ".?!");
4436
4437

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

Chris Allegretta's avatar
Chris Allegretta committed
4440
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4441
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
4442
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4443
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
4444
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4445
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
4446
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4447
		);
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
#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
4464
#endif /* !DISABLE_JUSTIFY */
4465

4466
4467
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
4468
     * command line and/or rcfile(s), check $SPELL for one, as Pico
4469
     * does (unless we're using restricted mode, in which case spell
4470
4471
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
4472
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
4473
4474
4475
4476
4477
4478
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

4479
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4480
    /* If whitespace wasn't specified, set its default value. */
4481
    if (whitespace == NULL) {
4482
	whitespace = mallocstrcpy(NULL, "  ");
4483
4484
4485
	whitespace_len[0] = 1;
	whitespace_len[1] = 1;
    }
4486
4487
#endif

4488
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4489
    if (tabsize == -1)
4490
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4491

4492
    /* Back up the old terminal settings so that they can be restored. */
4493
    tcgetattr(0, &oldterm);
4494

4495
4496
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4497
    initscr();
4498
    terminal_init();
4499

4500
4501
4502
    /* Turn the cursor on for sure. */
    curs_set(1);

4503
    /* Set up the global variables and the shortcuts. */
4504
4505
    global_init(FALSE);
    shortcut_init(FALSE);
4506
4507

    /* Set up the signal handlers. */
4508
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4509
4510

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

4514
    window_init();
4515
#ifndef DISABLE_MOUSE
4516
    mouse_init();
4517
#endif
4518

Chris Allegretta's avatar
Chris Allegretta committed
4519
#ifdef DEBUG
4520
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4521
#endif
4522

4523
4524
4525
    /* If there's a +LINE or +LINE,COLUMN flag here, it is the first
     * non-option argument, and it is followed by at least one other
     * argument, the filename it applies to. */
4526
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
4527
	parse_line_column(&argv[optind][1], &startline, &startcol);
4528
4529
4530
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
4531
#ifdef ENABLE_MULTIBUFFER
4532
4533
4534
4535
4536
4537
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

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

4541
	for (; i < argc; i++) {
4542
4543
4544
4545
	    /* If there's a +LINE or +LINE,COLUMN flag here, it is
	     * followed by at least one other argument, the filename it
	     * applies to. */
	    if (i < argc - 1 && argv[i][0] == '+' && iline == 1 &&
4546
		icol == 1)
4547
		parse_line_column(&argv[i][1], &iline, &icol);
4548
	    else {
4549
		load_buffer(argv[i]);
4550

4551
		if (iline > 1 || icol > 1) {
4552
		    do_gotolinecolumn(iline, icol, FALSE, FALSE, FALSE);
4553
4554
		    iline = 1;
		    icol = 1;
4555
4556
4557
		}
	    }
	}
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
    }
#endif

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

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

	/* Add this new entry to the open_files structure if we have
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4577
4578
	 * multibuffer support, or to the main filestruct if we
	 * don't. */
4579
	load_file();
Chris Allegretta's avatar
Chris Allegretta committed
4580
    }
4581
4582
4583
4584

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

4587
4588
4589
4590
4591
4592
4593
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

4594
    if (startline > 1 || startcol > 1)
4595
	do_gotolinecolumn(startline, startcol, FALSE, FALSE, FALSE);
4596

4597
4598
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4599
    sigsetjmp(jmpbuf, 1);
4600
#endif
4601

Robert Siemborski's avatar
Robert Siemborski committed
4602
4603
    edit_refresh();

4604
    while (TRUE) {
4605
	bool meta_key, func_key, s_or_t, ran_func, finished;
4606
4607

	/* Make sure the cursor is in the edit window. */
4608
	reset_cursor();
4609
4610
4611

	/* If constant cursor position display is on, display the cursor
	 * position. */
4612
	if (ISSET(CONSTUPDATE))
4613
	    do_cursorpos(TRUE);
4614

4615
4616
	currshortcut = main_list;

4617
	/* Read in and interpret characters. */
4618
4619
	do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
		TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4620
    }
4621

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4622
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4623
}