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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

117
118
119
    endwin();
    curses_ended = TRUE;

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

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

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

133
	die_save_file(filename);
134
    }
135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    UNSET(WHITESPACE_DISPLAY);
#endif
289
290
#endif

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

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

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

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

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

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

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

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

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

470
471
    ptr = help_text + strlen(help_text);

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

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

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

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

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

512
513
514
515
516
517
518
519
520
521
	/* 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. */
522
523
524
525
526
	    if (entries == 1) {
		/* Yucky sentinel values we can't handle a better
		 * way. */
		if (s->metaval == NANO_ALT_SPACE) {
		    char *space_ptr = display_string(_("Space"), 0, 5,
527
528
			FALSE);

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

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

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

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

	assert(s->help != NULL);
559
560
561
562
563
564
565
566
567

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

	    ptr += sprintf(ptr, help_ptr);

	    free(help_ptr);
	}
568

569
	ptr += sprintf(ptr, "\n");
570
571
572
573
    }

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

577
	    assert(t->desc != NULL);
578

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

#ifdef ENABLE_NANORC
    if (old_whitespace)
	SET(WHITESPACE_DISPLAY);
#endif
#endif
589
590

    /* If all went well, we didn't overwrite the allocated space for
591
     * help_text. */
592
    assert(strlen(help_text) <= allocsize + 1);
593
594
595
596
597
598
599
600
}
#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
601

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

607
608
609
    return newnode;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
615
    assert(src != NULL);
616
617
618

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

619
    dst->data = mallocstrcpy(NULL, src->data);
Chris Allegretta's avatar
Chris Allegretta committed
620
621
622
    dst->next = src->next;
    dst->prev = src->prev;
    dst->lineno = src->lineno;
623

Chris Allegretta's avatar
Chris Allegretta committed
624
625
626
    return dst;
}

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

633
634
635
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
636
637
638
639
    if (end != NULL)
	end->prev = newnode;
}

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

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

656
657
658
    if (fileptr->data != NULL)
	free(fileptr->data);
    free(fileptr);
659
660
}

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

Chris Allegretta's avatar
Chris Allegretta committed
666
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
667

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

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

Chris Allegretta's avatar
Chris Allegretta committed
678
	src = src->next;
Chris Allegretta's avatar
Chris Allegretta committed
679
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
680
    copy->next = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
681
682
683
684

    return head;
}

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

    while (src->next != NULL) {
	src = src->next;
	delete_node(src->prev);
693
    }
694
    delete_node(src);
695
696
}

697
698
699
700
701
702
/* 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;
703

704
    assert(top != NULL && bot != NULL && fileage != NULL && filebot != NULL);
705
706
707
708

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

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

    /* 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. */
752
void unpartition_filestruct(partition **p)
753
754
{
    char *tmp;
755

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
756
    assert(p != NULL && fileage != NULL && filebot != NULL);
757
758
759
760
761

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

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

    /* Uninitialize the partition. */
791
792
    free(*p);
    *p = NULL;
793
794
}

795
796
797
798
799
800
801
802
/* 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;
803
    size_t part_totsize;
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
898
899
900
901
    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;
902
    size_t part_totsize;
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
954
955
956
957
    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
958
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
959
960
{
    filestruct *temp;
961
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
962

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

Chris Allegretta's avatar
Chris Allegretta committed
965
    for (temp = fileage; temp != NULL; temp = temp->next)
Chris Allegretta's avatar
Chris Allegretta committed
966
967
968
	temp->lineno = i++;
}

Chris Allegretta's avatar
Chris Allegretta committed
969
void renumber(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
970
{
Chris Allegretta's avatar
Chris Allegretta committed
971
    if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage)
Chris Allegretta's avatar
Chris Allegretta committed
972
	renumber_all();
Chris Allegretta's avatar
Chris Allegretta committed
973
974
    else {
	int lineno = fileptr->prev->lineno;
975

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

Chris Allegretta's avatar
Chris Allegretta committed
978
979
980
	for (; fileptr != NULL; fileptr = fileptr->next)
	    fileptr->lineno = ++lineno;
    }
Chris Allegretta's avatar
Chris Allegretta committed
981
982
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
983
984
985
986
987
988
#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

989
/* Print one usage string to the screen.  This cuts down on duplicate
990
 * strings to translate, and leaves out the parts that shouldn't be
Chris Allegretta's avatar
Chris Allegretta committed
991
 * translatable (the flag names). */
992
993
994
995
996
void print1opt_full(const char *shortflag
#ifdef HAVE_GETOPT_LONG
	, const char *longflag
#endif
	, const char *desc)
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
{
    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

1010
1011
1012
    if (desc != NULL)
	printf("%s", _(desc));
    printf("\n");
1013
1014
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1257
    read_file(f, "stdin");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1258

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1284
    statusbar(_("Verbatim Input"));
1285

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

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

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

1297
    do_output(output, kbinput_len, TRUE);
1298
1299

    free(output);
1300
1301
}

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

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

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

1318
    placewewant = xplustabs();
1319

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

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

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

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

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

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

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

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

    set_modified();
1375
1376
1377
1378

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

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

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

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

	output = charalloc(output_len + 1);

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

	do_output(output, output_len, TRUE);
    } else {
#endif
	do_output("\t", 1, TRUE);
#ifndef NANO_SMALL
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1413
1414
}

1415
/* Someone hits Return *gasp!* */
1416
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1417
{
1418
1419
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1420

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1423
#ifndef NANO_SMALL
1424
1425
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1426
1427
1428
1429
1430
	/* 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)
1431
	    extra = current_x;
1432
1433
1434
1435
1436
1437
    }
#endif
    newnode->data = charalloc(strlen(current->data + current_x) +
	extra + 1);
    strcpy(&newnode->data[extra], current->data + current_x);
#ifndef NANO_SMALL
1438
    if (ISSET(AUTOINDENT)) {
1439
	charcpy(newnode->data, current->data, extra);
1440
1441
	totsize += mbstrlen(newnode->data);
    }
1442
#endif
1443
1444
1445
1446
1447
    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;
1448
    }
1449
1450
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1451

1452
    if (current == filebot)
1453
1454
	filebot = newnode;
    splice_node(current, newnode, current->next);
1455

1456
1457
    renumber(current);
    current = newnode;
1458
1459

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

1461
    totlines++;
1462
    totsize++;
1463
1464
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1465
1466
}

1467
#ifndef NANO_SMALL
1468
1469
1470
1471
/* 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
1472
{
1473
1474
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1475
    char *char_mb;
1476
    int char_mb_len;
1477
    bool started_on_word = FALSE;
1478

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

1481
1482
    char_mb = charalloc(mb_cur_max());

1483
1484
    /* Move forward until we find the character after the last letter of
     * the current word. */
1485
    while (current->data[current_x] != '\0') {
1486
1487
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1488

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

1494
1495
	/* If we haven't found it, then we've started on a word, so set
	 * started_on_word to TRUE. */
1496
	started_on_word = TRUE;
1497

1498
	current_x += char_mb_len;
1499
    }
Chris Allegretta's avatar
Chris Allegretta committed
1500

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

1505
    for (; current != NULL; current = current->next) {
1506
	while (current->data[current_x] != '\0') {
1507
	    char_mb_len = parse_mbchar(current->data + current_x,
1508
		char_mb, NULL, NULL);
1509

1510
1511
	    /* If we've found it, stop moving forward through the
	     * current line. */
1512
	    if (is_word_mbchar(char_mb, TRUE))
1513
1514
		break;

1515
	    current_x += char_mb_len;
1516
	}
Chris Allegretta's avatar
Chris Allegretta committed
1517

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

1523
1524
	current_x = 0;
    }
1525

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1526
1527
    free(char_mb);

1528
1529
    /* If we haven't found it, leave the cursor at the end of the
     * file. */
1530
1531
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1532

1533
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1534

1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
    /* 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);
1546
}
Chris Allegretta's avatar
Chris Allegretta committed
1547

1548
/* Move to the previous word. */
1549
void do_prev_word(void)
1550
{
1551
1552
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1553
    char *char_mb;
1554
1555
    int char_mb_len;
    bool begin_line = FALSE;
1556

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

1559
1560
    char_mb = charalloc(mb_cur_max());

1561
1562
1563
    /* Move backward until we find the character before the first letter
     * of the current word. */
    while (!begin_line) {
1564
1565
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1566

1567
1568
	/* If we've found it, stop moving backward through the current
	 * line. */
1569
	if (!is_word_mbchar(char_mb, TRUE))
1570
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1571

1572
1573
1574
1575
1576
1577
1578
1579
	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. */
1580
1581
1582
1583
1584
    if (current_x == 0)
	begin_line = TRUE;
    else
	current_x = move_mbleft(current->data, current_x);

1585
    for (; current != NULL; current = current->prev) {
1586
1587
	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1588
		char_mb, NULL, NULL);
1589
1590
1591

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

1595
1596
1597
1598
1599
1600
1601
1602
1603
	    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)
1604
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1605

1606
1607
1608
1609
	if (current->prev != NULL) {
	    begin_line = FALSE;
	    current_x = strlen(current->prev->data);
	}
Chris Allegretta's avatar
Chris Allegretta committed
1610
1611
    }

1612
1613
    /* If we haven't found it, leave the cursor at the beginning of the
     * file. */
1614
    if (current == NULL) {
1615
1616
	current = fileage;
	current_x = 0;
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
    /* 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,
1627
		char_mb, NULL, NULL);
1628
1629
1630

	    /* If we've found it, stop moving backward through the
	     * current line. */
1631
	    if (!is_word_mbchar(char_mb, TRUE))
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
		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;
1644
    }
Chris Allegretta's avatar
Chris Allegretta committed
1645

1646
1647
    free(char_mb);

1648
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1649

1650
    /* Update the screen. */
1651
    edit_redraw(current_save, pww_save);
1652
}
Chris Allegretta's avatar
Chris Allegretta committed
1653

1654
1655
1656
1657
1658
void do_word_count(void)
{
    size_t words = 0;
    size_t current_x_save = current_x, pww_save = placewewant;
    filestruct *current_save = current;
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
    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);
    }
1677

1678
    /* Start at the top of the file. */
1679
1680
1681
1682
    current = fileage;
    current_x = 0;
    placewewant = 0;

1683
1684
1685
    /* 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. */
1686
1687
1688
1689
1690
    while (current != filebot || current_x != 0) {
	if (do_next_word(FALSE))
	    words++;
    }

1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
    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. */
1704
1705
1706
1707
1708
    current = current_save;
    current_x = current_x_save;
    placewewant = pww_save;

    /* Display the total word count on the statusbar. */
1709
1710
    statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") :
	_("Word Count"), (unsigned long)words);
1711
1712
}

1713
void do_mark(void)
1714
{
1715
1716
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1717
1718
1719
1720
1721
1722
1723
1724
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1725
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1726

1727
#ifndef DISABLE_WRAPPING
1728
1729
void wrap_reset(void)
{
1730
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1731
}
1732
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1733

1734
#ifndef DISABLE_WRAPPING
1735
1736
1737
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1738
bool do_wrap(filestruct *line)
Chris Allegretta's avatar
Chris Allegretta committed
1739
{
1740
    size_t line_len;
1741
	/* Length of the line we wrap. */
1742
    ssize_t wrap_loc;
1743
	/* Index of line->data where we wrap. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1744
#ifndef NANO_SMALL
1745
    const char *indent_string = NULL;
1746
	/* Indentation to prepend to the new line. */
1747
    size_t indent_len = 0;	/* The length of indent_string. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1748
#endif
1749
1750
    const char *after_break;	/* The text after the wrap point. */
    size_t after_break_len;	/* The length of after_break. */
1751
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1752
    const char *next_line = NULL;
1753
	/* The next line, minus indentation. */
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
    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
1771

1772
1773
1774
1775
1776
    assert(line != NULL && line->data != NULL);

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

1777
1778
    /* 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
1779

1780
1781
1782
    /* 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')
1783
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1784

1785
1786
1787
1788
1789
1790
    /* 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
1791

1792
1793
1794
1795
1796
1797
1798
#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);
1799

1800
1801
1802
1803
	if (wrap_loc == indent_len)
	    return FALSE;
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1804

1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
    /* 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
1836

1837
1838
	next_line = line->next->data;
	next_line_len = strlen(next_line);
Chris Allegretta's avatar
Chris Allegretta committed
1839

1840
	if (after_break_len + next_line_len <= fill) {
1841
	    wrapping = TRUE;
1842
	    new_line_len += next_line_len;
1843
1844
	}
    }
1845

1846
1847
1848
1849
1850
    /* 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
1851
#ifndef NANO_SMALL
1852
    if (ISSET(AUTOINDENT)) {
1853
1854
1855
1856
1857
1858
1859
1860
1861
	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. */
1862
	    new_line_len += indent_len;
1863
1864
	    totsize += mbstrnlen(indent_string, indent_len);
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1865
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1866
1867
#endif

1868
1869
1870
    /* 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
1871

1872
1873
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
1874
1875
1876
1877
	/* Copy the indentation. */
	charcpy(new_line, indent_string, indent_len);
	new_line[indent_len] = '\0';
	new_line_len += indent_len;
1878
    }
1879
#endif
1880

1881
1882
1883
1884
1885
1886
    /* 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);

1887
    if (wrapping) {
1888
1889
1890
1891
1892
1893
	/* 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;
1894
    } else {
1895
1896
1897
1898
1899
	/* 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;
1900

1901
	totlines++;
1902
	totsize++;
1903
    }
Chris Allegretta's avatar
Chris Allegretta committed
1904

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

1908
1909
    /* Set the same_line_wrap flag, so that later wraps of this line
     * will be prepended to the next line. */
1910
    same_line_wrap = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
1911

1912
1913
1914
    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
    if (!wrapping)
1915
	renumber(line);
Chris Allegretta's avatar
Chris Allegretta committed
1916

1917
1918
    /* If the cursor was after the break point, we must move it.  We
     * also clear the same_line_wrap flag in this case. */
1919
    if (current_x > wrap_loc) {
1920
	same_line_wrap = FALSE;
1921
	current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1922
	current_x -= wrap_loc
1923
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1924
		- indent_len
1925
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1926
		;
1927
1928
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1929

1930
#ifndef NANO_SMALL
1931
    /* If the mark was on this line after the wrap point, we move it
1932
1933
1934
1935
     * 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;
1936
	mark_beginx -= wrap_loc - indent_len + 1;
1937
    } else if (wrapping && mark_beginbuf == line->next)
1938
	mark_beginx += after_break_len;
1939
#endif
1940

1941
    return TRUE;
1942
}
1943
#endif /* !DISABLE_WRAPPING */
1944

1945
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1946
/* A word is misspelled in the file.  Let the user replace it.  We
1947
1948
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1949
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1950
    char *save_search, *save_replace;
1951
    size_t match_len;
1952
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1953
    filestruct *edittop_save = edittop, *current_save = current;
1954
	/* Save where we are. */
1955
    bool canceled = FALSE;
1956
	/* The return value. */
1957
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1958
#ifndef NANO_SMALL
1959
1960
    bool reverse_search_set = ISSET(REVERSE_SEARCH);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1961
#ifdef HAVE_REGEX_H
1962
1963
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1964
1965
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1966
1967
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1968
1969
1970
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1971
    filestruct *top, *bot;
1972
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1973
#endif
1974

1975
    /* Make sure spell-check is case sensitive. */
1976
    SET(CASE_SENSITIVE);
1977

1978
#ifndef NANO_SMALL
1979
1980
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1981
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1982
#ifdef HAVE_REGEX_H
1983
1984
1985
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1986

1987
    /* Save the current search/replace strings. */
1988
1989
1990
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1991

1992
    /* Set the search/replace strings to the misspelled word. */
1993
1994
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1995

1996
1997
#ifndef NANO_SMALL
    if (old_mark_set) {
1998
	/* If the mark is on, partition the filestruct so that it
1999
2000
2001
	 * 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. */
2002
	mark_order((const filestruct **)&top, &top_x,
2003
	    (const filestruct **)&bot, &bot_x, &right_side_up);
2004
	filepart = partition_filestruct(top, top_x, bot, bot_x);
2005
	added_magicline = (filebot->data[0] != '\0');
2006
2007
2008
2009
	UNSET(MARK_ISSET);
    }
#endif

2010
    /* Start from the top of the file. */
2011
    edittop = fileage;
2012
    current = fileage;
2013
    current_x = (size_t)-1;
2014
    placewewant = 0;
2015

2016
    /* Find the first whole-word occurrence of word. */
2017
    findnextstr_wrap_reset();
2018
2019
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word,
	&match_len)) {
2020
	if (is_whole_word(current_x, current->data, word)) {
2021
2022
2023
2024
2025
	    size_t xpt = xplustabs();
	    char *exp_word = display_string(current->data, xpt,
		strnlenpt(current->data, current_x + match_len) - xpt,
		FALSE);

2026
	    edit_refresh();
2027

2028
	    do_replace_highlight(TRUE, exp_word);
2029

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2030
	    /* Allow all instances of the word to be corrected. */
2031
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
2032
#ifndef NANO_SMALL
2033
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2034
#endif
2035
			 _("Edit a replacement")) == -1);
2036

2037
2038
2039
	    do_replace_highlight(FALSE, exp_word);

	    free(exp_word);
2040

2041
	    if (!canceled && strcmp(word, answer) != 0) {
2042
		current_x--;
2043
2044
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
2045
	    }
2046
2047

	    break;
2048
	}
2049
    }
Chris Allegretta's avatar
Chris Allegretta committed
2050

2051
2052
#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2053
2054
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2055
2056
	if (added_magicline)
	    remove_magicline();
2057

2058
2059
2060
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2061
	    bot_x += top_x;
2062
	if (right_side_up) {
2063
2064
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2065
	} else {
2066
2067
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2068
	}
2069

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2070
2071
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
2072
	unpartition_filestruct(&filepart);
2073
	SET(MARK_ISSET);
2074
2075
    }
#endif
2076

2077
2078
2079
2080
2081
    /* 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
2082

2083
    /* Restore where we were. */
2084
    edittop = edittop_save;
2085
2086
    current = current_save;
    current_x = current_x_save;
2087
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
2088

2089
    /* Restore case sensitivity setting. */
2090
2091
2092
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

2093
#ifndef NANO_SMALL
2094
2095
2096
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
2097
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2098
#ifdef HAVE_REGEX_H
2099
2100
2101
2102
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
2103

2104
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
2105
2106
}

2107
2108
2109
/* Integrated spell checking using the spell program, filtered through
 * the sort and uniq programs.  Return NULL for normal termination,
 * and the error string otherwise. */
2110
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
2111
{
2112
2113
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
2114
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
2115
2116
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
2117

2118
    /* Create all three pipes up front. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2119
2120
    if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
	pipe(uniq_fd) == -1)
2121
	return _("Could not create pipe");
Chris Allegretta's avatar
Chris Allegretta committed
2122

2123
    statusbar(_("Creating misspelled word list, please wait..."));
2124

2125
    /* A new process to run spell in. */
2126
    if ((pid_spell = fork()) == 0) {
2127
	/* Child continues (i.e, future spell process). */
2128
	close(spell_fd[0]);
2129

2130
	/* Replace the standard input with the temp file. */
2131
2132
2133
2134
2135
2136
	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;

2137
	close(tempfile_fd);
2138

2139
	/* Send spell's standard output to the pipe. */
2140
2141
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
2142

2143
	close(spell_fd[1]);
2144

2145
	/* Start the spell program; we are using PATH. */
2146
	execlp("spell", "spell", NULL);
2147

2148
	/* This should not be reached if spell is found. */
2149
	exit(1);
2150
    }
Chris Allegretta's avatar
Chris Allegretta committed
2151

2152
    /* Parent continues here. */
2153
2154
    close(spell_fd[1]);

2155
    /* A new process to run sort in. */
2156
    if ((pid_sort = fork()) == 0) {
2157
2158
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
2159
2160
2161
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2162
2163
	close(spell_fd[0]);

2164
	/* Send sort's standard output to the new pipe. */
2165
2166
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2167
2168
2169

	close(sort_fd[1]);

2170
2171
	/* Start the sort program.  Use -f to remove mixed case.  If
	 * this isn't portable, let me know. */
2172
2173
	execlp("sort", "sort", "-f", NULL);

2174
	/* This should not be reached if sort is found. */
2175
2176
2177
	exit(1);
    }

2178
    close(spell_fd[0]);
2179
2180
    close(sort_fd[1]);

2181
    /* A new process to run uniq in. */
2182
    if ((pid_uniq = fork()) == 0) {
2183
2184
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
2185
2186
2187
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

2188
2189
	close(sort_fd[0]);

2190
	/* Send uniq's standard output to the new pipe. */
2191
2192
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
2193
2194
2195

	close(uniq_fd[1]);

2196
	/* Start the uniq program; we are using PATH. */
2197
2198
	execlp("uniq", "uniq", NULL);

2199
	/* This should not be reached if uniq is found. */
2200
2201
2202
	exit(1);
    }

2203
    close(sort_fd[0]);
2204
    close(uniq_fd[1]);
2205

2206
    /* The child process was not forked successfully. */
2207
2208
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
2209
	return _("Could not fork");
2210
    }
2211

2212
    /* Get the system pipe buffer size. */
2213
2214
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
2215
	return _("Could not get size of pipe buffer");
2216
    }
Chris Allegretta's avatar
Chris Allegretta committed
2217

2218
    /* Read in the returned spelling errors. */
2219
2220
2221
    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
2222

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2223
2224
    while ((bytesread = read(uniq_fd[0], read_buff_ptr,
	pipe_buff_size)) > 0) {
2225
2226
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2227
2228
	read_buff = read_buff_ptr = charealloc(read_buff,
		read_buff_size);
2229
2230
	read_buff_ptr += read_buff_read;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2231

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2232
    *read_buff_ptr = '\0';
2233
    close(uniq_fd[0]);
2234

2235
    /* Process the spelling errors. */
2236
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
2237

2238
    while (*read_buff_ptr != '\0') {
2239
	if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2240
	    *read_buff_ptr = '\0';
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
	    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
2251

2252
    /* Special case: the last word doesn't end with '\r' or '\n'. */
2253
2254
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
2255

2256
2257
    free(read_buff);
    replace_abort();
2258
    edit_refresh();
2259

2260
    /* Process the end of the spell process. */
2261
2262
2263
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
2264

2265
2266
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
2267

2268
2269
2270
2271
2272
2273
2274
2275
    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;
2276

2277
2278
  close_pipes_and_exit:
    /* Don't leak any handles. */
2279
2280
2281
2282
2283
2284
2285
2286
    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
2287
2288
}

2289
2290
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
2291
const char *do_alt_speller(char *tempfile_name)
2292
{
2293
2294
2295
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
    int current_y_save = current_y;
2296
2297
2298
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
2299
2300
    static char **spellargs = NULL;
    FILE *f;
2301
#ifndef NANO_SMALL
2302
    bool old_mark_set = ISSET(MARK_ISSET);
2303
2304
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
2305
2306
2307
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
2308
2309
    filestruct *top, *bot;
    size_t top_x, bot_x;
2310
    int mbb_lineno_save = 0;
2311
	/* We're going to close the current file, and open the output of
2312
2313
2314
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
2315
    size_t totsize_save = totsize;
2316
2317
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
2318

2319
    if (old_mark_set) {
2320
2321
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
2322
	mbb_lineno_save = mark_beginbuf->lineno;
2323
2324
	UNSET(MARK_ISSET);
    }
2325
#endif
2326

2327
    endwin();
2328

2329
    /* Set up an argument list to pass execvp(). */
2330
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2331
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
2332

2333
2334
2335
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2336
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
2337
2338
2339
2340
2341
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
2342

2343
    /* Start a new process for the alternate speller. */
2344
    if ((pid_spell = fork()) == 0) {
2345
	/* Start alternate spell program; we are using PATH. */
2346
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
2347

2348
2349
2350
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
2351

2352
2353
    /* Could not fork?? */
    if (pid_spell < 0)
2354
	return _("Could not fork");
2355

2356
    /* Wait for alternate speller to complete. */
2357
    wait(&alt_spell_status);
2358

2359
2360
    if (!WIFEXITED(alt_spell_status) ||
		WEXITSTATUS(alt_spell_status) != 0) {
2361
2362
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
2363
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;
2364
2365
2366
2367
2368

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
2369
2370

    refresh();
2371

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

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

2378
#ifndef NANO_SMALL
2379
    if (old_mark_set) {
2380
	size_t part_totsize;
2381
2382
2383
2384
2385
2386

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

2391
2392
2393
2394
	/* 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);
2395
	totsize_save -= part_totsize;
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
    }
#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
2412
	filestruct *top_save = fileage;
2413

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2414
2415
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2416
2417
2418
	if (added_magicline)
	    remove_magicline();

2419
2420
2421
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2422
	    bot_x += top_x;
2423
	if (right_side_up) {
2424
2425
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2426
	} else {
2427
2428
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2429
	}
2430

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2431
2432
2433
2434
	/* 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. */
2435
	unpartition_filestruct(&filepart);
2436
2437

	/* Renumber starting with the beginning line of the old
2438
2439
2440
2441
	 * 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
2442
	renumber(top_save);
2443
	totlines = filebot->lineno;
2444
2445
	totsize_save += totsize;
	totsize = totsize_save;
2446
2447
2448

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2449
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2450
	mark_beginbuf = current;
2451
2452
2453
2454

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

2457
2458
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2459
2460
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2461

2462
2463
    /* Go back to the old position, mark the file as modified, and
     * update the titlebar. */
2464
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2465
    SET(MODIFIED);
2466
    titlebar(NULL);
2467

2468
    return NULL;
2469
}
2470

2471
void do_spell(void)
2472
{
2473
    int i;
2474
2475
    FILE *temp_file;
    char *temp = safe_tempfile(&temp_file);
2476
    const char *spell_msg;
2477

2478
2479
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2480
	return;
2481
    }
2482

2483
2484
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2485
	i = write_marked(temp, temp_file, TRUE, FALSE);
2486
2487
    else
#endif
2488
	i = write_file(temp, temp_file, TRUE, FALSE, FALSE);
2489
2490

    if (i == -1) {
2491
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2492
	free(temp);
2493
	return;
2494
2495
    }

2496
#ifdef ENABLE_MULTIBUFFER
2497
2498
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2499
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2500
#endif
2501

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2502
    spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
2503
	do_int_speller(temp);
2504
    unlink(temp);
2505
    free(temp);
2506

2507
2508
2509
2510
    /* If the spell-checker printed any error messages onscreen, make
     * sure they're cleared off. */
    total_update();

2511
    if (spell_msg != NULL)
2512
2513
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2514
    else
2515
	statusbar(_("Finished checking spelling"));
2516
}
2517
#endif /* !DISABLE_SPELLER */
2518

2519
#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING)
2520
2521
/* 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
2522
2523
2524
2525
 * 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)
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
{
    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. */
2559
	bool found_blank = FALSE;
2560

2561
2562
	while (*line != '\0') {
	    line_len = parse_mbchar(line, NULL, NULL, NULL);
2563

2564
2565
2566
2567
2568
	    if (is_blank_mbchar(line) || (newline && *line == '\n')) {
		if (!found_blank)
		    found_blank = TRUE;
	    } else if (found_blank)
		return cur_loc - line_len;
2569

2570
2571
	    line += line_len;
	    cur_loc += line_len;
2572
	}
2573
2574

	return -1;
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
    }

    /* 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;
}
2593
#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */
2594

2595
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2596
2597
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2598
2599
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2600
    size_t len = 0;
2601
2602
    char *blank_mb;
    int blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2603

Chris Allegretta's avatar
Chris Allegretta committed
2604
    assert(line != NULL);
2605
2606
2607
2608

    blank_mb = charalloc(mb_cur_max());

    while (*line != '\0') {
2609
	blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL);
2610
2611
2612
2613
2614
2615

	if (!is_blank_mbchar(blank_mb))
	    break;

	line += blank_mb_len;
	len += blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2616
    }
2617
2618
2619

    free(blank_mb);

Chris Allegretta's avatar
Chris Allegretta committed
2620
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2621
}
2622
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2623

Chris Allegretta's avatar
Chris Allegretta committed
2624
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2625
/* justify_format() replaces blanks with spaces and multiple spaces by 1
2626
2627
 * (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
2628
 *
2629
2630
 * justify_format() might make paragraph->data shorter, and change the
 * actual pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2631
 *
2632
2633
2634
 * 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. */
2635
void justify_format(filestruct *paragraph, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2636
{
2637
2638
2639
2640
2641
    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
2642
2643

    /* These four asserts are assumptions about the input data. */
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
    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') {
2655
2656
	int end_len;

2657
2658
	/* If this character is blank, make sure that it's a space with
	 * no blanks after it. */
2659
2660
2661
	if (is_blank_mbchar(end)) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);

2662
2663
	    *new_end = ' ';
	    new_end++;
2664
	    end += end_len;
2665

2666
2667
2668
2669
2670
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2671

2672
#ifndef NANO_SMALL
2673
		/* Keep track of the change in the current line. */
2674
2675
		if (mark_beginbuf == paragraph &&
			mark_beginx >= end - paragraph->data)
2676
		    mark_shift += end_len;
2677
#endif
2678
	    }
2679
2680
2681
2682
	/* 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. */
2683
2684
	} else if (mbstrchr(punct, end) != NULL) {
	    end_len = parse_mbchar(end, NULL, NULL, NULL);
2685

2686
	    while (end_len > 0) {
2687
2688
2689
		*new_end = *end;
		new_end++;
		end++;
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
		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--;
		}
2702
	    }
2703

2704
2705
2706
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2707
2708
		*new_end = ' ';
		new_end++;
2709
		end += end_len;
2710
	    }
2711

2712
2713
2714
	    if (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

2715
2716
		*new_end = ' ';
		new_end++;
2717
		end += end_len;
2718
	    }
2719

2720
2721
2722
2723
2724
	    while (*end != '\0' && is_blank_mbchar(end)) {
		end_len = parse_mbchar(end, NULL, NULL, NULL);

		end += end_len;
		shift += end_len;
2725

Chris Allegretta's avatar
Chris Allegretta committed
2726
#ifndef NANO_SMALL
2727
2728
2729
2730
		/* 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
2731
#endif
2732
	    }
2733
2734
	/* If this character is neither blank nor punctuation, leave it
	 * alone. */
Chris Allegretta's avatar
Chris Allegretta committed
2735
	} else {
2736
2737
2738
2739
2740
2741
2742
2743
	    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
2744
	}
Chris Allegretta's avatar
Chris Allegretta committed
2745
2746
    }

2747
2748
2749
2750
    assert(*end == '\0');

    *new_end = *end;

2751
    /* Make sure that there are no spaces at the end of the line. */
2752
2753
2754
2755
2756
    while (new_end > new_paragraph_data + skip &&
	*(new_end - 1) == ' ') {
	new_end--;
	shift++;
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2757

2758
2759
2760
2761
2762
    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
2763

Chris Allegretta's avatar
Chris Allegretta committed
2764
#ifndef NANO_SMALL
2765
2766
2767
2768
2769
2770
2771
	/* 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
2772
#endif
2773
2774
    } else
	free(new_paragraph_data);
Chris Allegretta's avatar
Chris Allegretta committed
2775
}
Chris Allegretta's avatar
Chris Allegretta committed
2776

Chris Allegretta's avatar
Chris Allegretta committed
2777
2778
2779
2780
2781
2782
/* 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. */
2783
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2784
{
2785
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2786
    regmatch_t matches;
2787
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2788

2789
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2790
	return 0;
2791
2792
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2793
2794
2795
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2796

2797
    /* Compute quote depth level. */
2798
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2799
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2800
2801
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2802
}
Chris Allegretta's avatar
Chris Allegretta committed
2803

Chris Allegretta's avatar
Chris Allegretta committed
2804
2805
2806
/* 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. */
2807
2808
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2809
{
2810
    /* Here is the assumption about a_quote. */
2811
    assert(a_quote == quote_length(a_line));
2812

2813
2814
    return (a_quote == quote_length(b_line) &&
	strncmp(a_line, b_line, a_quote) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2815
}
2816

2817
2818
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2819
bool indents_match(const char *a_line, size_t a_indent, const char
2820
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2821
{
Chris Allegretta's avatar
Chris Allegretta committed
2822
2823
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2824

2825
2826
    return (b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0);
Chris Allegretta's avatar
Chris Allegretta committed
2827
}
Chris Allegretta's avatar
Chris Allegretta committed
2828

2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
/* 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
2847
2848
2849
2850
2851
 *	   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. */
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
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. */
2890
void do_para_begin(bool allow_update)
2891
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2892
2893
    const filestruct *current_save = current;
    const size_t pww_save = placewewant;
2894
2895
2896
2897
2898
2899
2900

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2901
	    current_y--;
2902
2903
2904
	} while (!begpar(current));
    }

2905
2906
    if (allow_update)
	edit_redraw(current_save, pww_save);
2907
2908
}

2909
void do_para_begin_void(void)
2910
{
2911
2912
2913
2914
2915
2916
2917
    do_para_begin(TRUE);
}

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

2919
2920
2921
2922
2923
2924
2925
    if (foo == NULL)
	return FALSE;

    quote_len = quote_length(foo->data);

    return foo->data[quote_len + indent_length(foo->data +
	quote_len)] != '\0';
2926
2927
2928
2929
2930
}

/* 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. */
2931
void do_para_end(bool allow_update)
2932
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2933
2934
    const filestruct *const current_save = current;
    const size_t pww_save = placewewant;
2935
2936
2937
2938

    current_x = 0;
    placewewant = 0;

2939
    while (current->next != NULL && !inpar(current))
2940
2941
	current = current->next;

2942
    while (current->next != NULL && inpar(current->next) &&
2943
	    !begpar(current->next)) {
2944
	current = current->next;
2945
2946
	current_y++;
    }
2947
2948
2949
2950

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

2951
2952
2953
2954
2955
2956
2957
    if (allow_update)
	edit_redraw(current_save, pww_save);
}

void do_para_end_void(void)
{
    do_para_end(TRUE);
2958
2959
}

2960
2961
2962
2963
/* 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. */
2964
2965
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2966
{
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
    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;
2980
    size_t mark_beginx_save = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2981

2982
    if (old_mark_set) {
2983
	mbb_lineno_save = mark_beginbuf->lineno;
2984
2985
	mark_beginx_save = mark_beginx;
    }
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
#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
3000

3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
    /* 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
3013
#ifndef NANO_SMALL
3014
	if (old_mark_set && top->lineno == mbb_lineno_save) {
3015
	    mark_beginbuf = top;
3016
3017
	    mark_beginx = mark_beginx_save;
	}
Chris Allegretta's avatar
Chris Allegretta committed
3018
#endif
3019
	top = top->prev;
Chris Allegretta's avatar
Chris Allegretta committed
3020
    }
3021
3022
3023
3024
3025
3026
3027

    /* 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
3028
3029
3030
    return first_line;
}

3031
3032
/* 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
3033
3034
 * 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
3035
 * find a paragraph.
Chris Allegretta's avatar
Chris Allegretta committed
3036
 *
3037
3038
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
3039
bool find_paragraph(size_t *const quote, size_t *const par)
3040
{
Chris Allegretta's avatar
Chris Allegretta committed
3041
    size_t quote_len;
3042
3043
	/* Length of the initial quotation of the paragraph we search
	 * for. */
Chris Allegretta's avatar
Chris Allegretta committed
3044
    size_t par_len;
3045
3046
3047
3048
3049
3050
	/* 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. */
3051

Chris Allegretta's avatar
Chris Allegretta committed
3052
#ifdef HAVE_REGEX_H
3053
3054
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
3055
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
3056
    }
Chris Allegretta's avatar
Chris Allegretta committed
3057
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3058

Chris Allegretta's avatar
Chris Allegretta committed
3059
    assert(current != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
3060

3061
    /* Move back to the beginning of the current line. */
3062
3063
    current_x = 0;

3064
3065
    /* 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
3066
3067
3068
3069
3070
3071
     * 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. */
3072
    if (!inpar(current)) {
3073
3074
	filestruct *current_save = current;

3075
	do_para_end(FALSE);
3076
	if (current == current_save || !inpar(current->prev))
3077
	    return FALSE;
3078
3079
	if (current->prev != NULL)
	    current = current->prev;
3080
    }
3081
3082
    if (!begpar(current))
	do_para_begin(FALSE);
3083

3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
    /* 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;
3094

3095
3096
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
3097

3098
3099
    *quote = quote_len;
    *par = par_len;
3100

3101
    return TRUE;
3102
3103
}

3104
3105
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
3106
void do_justify(bool full_justify)
3107
{
3108
3109
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
3110
	 * For restoring after unjustify. */
3111
    filestruct *last_par_line;
3112
3113
	/* Will be the line containing the newline after the last line
	 * of the result.  Also for restoring after unjustify. */
3114
3115

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3116
     * unjustifies.  Note that we don't need to save totlines. */
3117
3118
    size_t current_x_save = current_x;
    int current_y_save = current_y;
3119
3120
    unsigned long flags_save = flags;
    size_t totsize_save = totsize;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3121
    filestruct *edittop_save = edittop, *current_save = current;
3122
3123
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
3124
    size_t mark_beginx_save = mark_beginx;
3125
#endif
3126
    int kbinput;
3127
    bool meta_key, func_key, s_or_t, ran_func, finished;
3128

3129
3130
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
3131
	current = fileage;
3132
3133

    last_par_line = current;
3134
3135

    while (TRUE) {
3136
3137
	size_t i;
	    /* Generic loop variable. */
3138
3139
3140
	size_t quote_len;
	    /* Length of the initial quotation of the paragraph we
	     * justify. */
3141
3142
3143
	size_t indent_len;
	    /* Length of the initial indentation of the paragraph we
	     * justify. */
3144
	size_t par_len;
3145
3146
3147
	    /* Number of lines in the paragraph we justify. */
	ssize_t break_pos;
	    /* Where we will break lines. */
3148
	char *indent_string;
3149
3150
3151
	    /* 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
3152
3153
3154
3155
3156
	     * 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.) */
3157
3158
3159
3160

	/* 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
3161
	 * length (number of lines).  Don't refresh the screen yet,
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
	 * 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. */
3172
	if (!find_paragraph(&quote_len, &par_len)) {
3173
3174
	    if (full_justify && first_par_line != NULL &&
		first_par_line != filebot) {
3175
		last_par_line = filebot;
3176
3177
3178
		break;
	    } else {
		edit_refresh();
3179
		return;
3180
3181
	    }
	}
3182

3183
3184
3185
3186
3187
3188
	/* 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);

3189
3190
3191
	/* Initialize indent_string to a blank string. */
	indent_string = mallocstrcpy(NULL, "");

3192
3193
3194
3195
3196
3197
3198
3199
3200
	/* 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++) {
3201
		indent_len = quote_len +
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
			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. */
3222
3223
3224
3225
3226
3227
	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 +
3228
		quote_len);
3229

3230
3231
3232
	    next_line_len -= indent_len;
	    totsize -= indent_len;

3233
	    /* We're just about to tack the next line onto this one.  If
3234
	     * this line isn't empty, make sure it ends in a space. */
3235
3236
3237
3238
3239
3240
3241
	    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++;
	    }
3242

3243
3244
3245
3246
3247
3248
3249
	    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;
3250

Chris Allegretta's avatar
Chris Allegretta committed
3251
#ifndef NANO_SMALL
3252
3253
3254
3255
	    /* Adjust the mark coordinates to compensate for the change
	     * in the next line. */
	    if (mark_beginbuf == next_line) {
		mark_beginbuf = current;
3256
		mark_beginx += line_len - indent_len;
3257
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3258
#endif
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271

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

3272
3273
3274
	/* Call justify_format() on the paragraph, which will remove
	 * excess spaces from it and change all blank characters to
	 * spaces. */
3275
3276
3277
3278
3279
3280
	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);

3281
	    indent_len = strlen(indent_string);
3282
3283
3284
3285

	    /* 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,
3286
		fill - strnlenpt(current->data, indent_len), FALSE);
3287
3288
3289
3290
3291

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

3292
3293
3294
	    /* Move forward to the character after the indentation and
	     * just after the space. */
	    break_pos += indent_len + 1;
3295

3296
	    assert(break_pos <= line_len);
3297

3298
3299
3300
	    /* Make a new line, and copy the text after where we're
	     * going to break this line to the beginning of the new
	     * line. */
3301
3302
	    splice_node(current, make_new_node(current), current->next);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3303
3304
	    /* If this paragraph is non-quoted, and autoindent isn't
	     * turned on, set the indentation length to zero so that the
3305
3306
	     * indentation is treated as part of the line. */
	    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
3307
#ifndef NANO_SMALL
3308
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
3309
#endif
3310
3311
		)
		indent_len = 0;
3312

3313
3314
3315
	    /* 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 -
3316
		break_pos);
3317
	    charcpy(current->next->data, indent_string, indent_len);
3318
	    strcpy(current->next->data + indent_len, current->data +
3319
		break_pos);
Chris Allegretta's avatar
Chris Allegretta committed
3320

3321
3322
	    par_len++;
	    totlines++;
3323
	    totsize += indent_len + 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3324

Chris Allegretta's avatar
Chris Allegretta committed
3325
#ifndef NANO_SMALL
3326
3327
3328
3329
	    /* 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;
3330
		mark_beginx -= break_pos - indent_len;
3331
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3332
#endif
3333

3334
3335
	    /* Break the current line. */
	    null_at(&current->data, break_pos);
3336
3337
3338
3339
3340

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

3343
3344
3345
3346
	/* We're done breaking lines, so we don't need indent_string
	 * anymore. */
	free(indent_string);

3347
3348
3349
3350
3351
	/* Go to the next line, the line after the last line of the
	 * paragraph. */
	current_y++;
	current = current->next;

3352
3353
3354
	/* 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. */
3355
3356
	if (!full_justify)
	    break;
3357
    }
3358

3359
3360
3361
    /* 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
3362
3363
3364
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
3365
    last_par_line = current;
3366
3367
3368
3369
3370
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
3371

3372
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3373

3374
    statusbar(_("Can now UnJustify!"));
3375

3376
    /* Display the shortcut list with UnJustify. */
3377
    shortcut_init(TRUE);
3378
    display_main_list();
3379

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3380
3381
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
3382
3383
    kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
	&finished, FALSE);
3384

3385
3386
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
3387
	/* Restore the justify we just did (ungrateful user!). */
Chris Allegretta's avatar
Chris Allegretta committed
3388
3389
3390
3391
3392
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

3393
	/* Splice the justify buffer back into the file, but only if we
3394
3395
	 * actually justified something. */
	if (first_par_line != NULL) {
3396
	    filestruct *bot_save;
3397

3398
3399
3400
3401
	    /* 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);
3402

3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
	    /* 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
3424
#ifndef NANO_SMALL
3425
3426
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
3427
#endif
3428
3429
3430
3431
3432
3433
3434
3435
3436
	    flags = flags_save;

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

	    if (!ISSET(MODIFIED))
		titlebar(NULL);
	    edit_refresh();
	}
3437
    } else {
3438
	unget_kbinput(kbinput, meta_key, func_key);
3439

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3440
	/* Blow away the text in the justify buffer. */
3441
3442
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
3443
    }
3444

3445
    blank_statusbar();
3446

3447
    /* Display the shortcut list with UnCut. */
3448
    shortcut_init(FALSE);
3449
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
3450
}
3451

3452
void do_justify_void(void)
3453
{
3454
    do_justify(FALSE);
3455
3456
}

3457
void do_full_justify(void)
3458
{
3459
    do_justify(TRUE);
3460
}
3461
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
3462

3463
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
3464
{
3465
3466
    int i;

3467
3468
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
3469
    else if (ISSET(TEMP_FILE))
3470
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3471
    else
3472
3473
3474
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

3475
3476
#ifdef DEBUG
    dump_buffer(fileage);
3477
#endif
3478

3479
    if (i == 0 || (i == 1 && do_writeout(TRUE) == 0)) {
3480
#ifdef ENABLE_MULTIBUFFER
3481
	/* Exit only if there are no more open file buffers. */
3482
	if (!close_open_file())
3483
#endif
3484
	    finish();
3485
    } else if (i != 1)
3486
3487
3488
3489
3490
3491
3492
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
3493
3494
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
3495
3496
3497
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
3498
    sigaction(SIGQUIT, &act, NULL);
3499

3500
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
3501
    act.sa_handler = handle_hupterm;
3502
    sigaction(SIGHUP, &act, NULL);
3503
    sigaction(SIGTERM, &act, NULL);
3504

3505
#ifndef NANO_SMALL
3506
    /* Trap SIGWINCH because we want to handle window resizes. */
3507
3508
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
3509
    allow_pending_sigwinch(FALSE);
3510
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3511

3512
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
3513
3514
3515
3516
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
3517
3518
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
3519
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
3520

3521
3522
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
3523

3524
3525
3526
3527
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
3528

3529
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
3530
void handle_hupterm(int signal)
3531
{
3532
    die(_("Received SIGHUP or SIGTERM\n"));
3533
}
3534

3535
/* Handler for SIGTSTP (suspend). */
3536
void do_suspend(int signal)
3537
3538
{
    endwin();
3539
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
3540
    fflush(stdout);
3541

3542
    /* Restore the old terminal settings. */
3543
    tcsetattr(0, TCSANOW, &oldterm);
3544

3545
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3546
     * suspended. */
3547
3548
3549
3550
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3551
    /* Do what mutt does: send ourselves a SIGSTOP. */
3552
3553
    kill(0, SIGSTOP);
}
3554

3555
/* Handler for SIGCONT (continue after suspend). */
3556
void do_cont(int signal)
3557
{
3558
#ifndef NANO_SMALL
3559
3560
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3561
    handle_sigwinch(0);
3562
#else
3563
3564
    /* Just update the screen. */
    doupdate();
3565
3566
3567
3568
3569
3570
3571
#endif
}

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

3575
    if (tty == NULL)
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
	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;

3592
3593
    check_die_too_small();
    resize_variables();
3594

3595
3596
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
3597
	unpartition_filestruct(&filepart);
3598

3599
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3600
3601
    /* If the justify buffer isn't empty, blow away the text in it and
     * display the shortcut list with UnCut. */
3602
3603
3604
3605
3606
3607
3608
    if (jusbuffer != NULL) {
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
	shortcut_init(FALSE);
    }
#endif

3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
#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
3622

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

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

3629
3630
3631
3632
3633
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

3634
    /* Redraw the contents of the windows that need it. */
3635
    blank_statusbar();
3636
    currshortcut = main_list;
3637
3638
    total_refresh();

3639
3640
3641
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3642
    /* Jump back to the main loop. */
3643
3644
    siglongjmp(jmpbuf, 1);
}
3645

3646
void allow_pending_sigwinch(bool allow)
3647
3648
3649
3650
3651
3652
3653
3654
3655
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3656
#endif /* !NANO_SMALL */
3657

3658
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3659
void do_toggle(const toggle *which)
3660
{
3661
    bool enabled;
3662

3663
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3664

3665
    switch (which->val) {
3666
#ifndef DISABLE_MOUSE
3667
3668
3669
	case TOGGLE_MOUSE_KEY:
	    mouse_init();
	    break;
3670
#endif
3671
	case TOGGLE_MORESPACE_KEY:
3672
3673
	case TOGGLE_NOHELP_KEY:
	    window_init();
3674
	    total_refresh();
3675
	    break;
3676
3677
	case TOGGLE_SUSPEND_KEY:
	    signal_init();
3678
	    break;
3679
#ifdef ENABLE_NANORC
3680
	case TOGGLE_WHITESPACE_KEY:
3681
	    titlebar(NULL);
3682
3683
	    edit_refresh();
	    break;
3684
3685
3686
3687
3688
#endif
#ifdef ENABLE_COLOR
	case TOGGLE_SYNTAX_KEY:
	    edit_refresh();
	    break;
3689
#endif
3690
    }
Chris Allegretta's avatar
Chris Allegretta committed
3691

Chris Allegretta's avatar
Chris Allegretta committed
3692
    enabled = ISSET(which->flag);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3693
3694
3695

    if (which->val == TOGGLE_NOHELP_KEY ||
	which->val == TOGGLE_WRAP_KEY)
Chris Allegretta's avatar
Chris Allegretta committed
3696
	enabled = !enabled;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3697
3698
3699

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

3703
void disable_extended_io(void)
3704
3705
3706
3707
3708
{
    struct termios term;

    tcgetattr(0, &term);
    term.c_lflag &= ~IEXTEN;
3709
    term.c_oflag &= ~OPOST;
3710
3711
3712
    tcsetattr(0, TCSANOW, &term);
}

3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
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);
}

3751
3752
3753
3754
/* 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
3755
 * echoing of characters as they're typed.  Finally, disable extended
3756
3757
3758
 * 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. */
3759
3760
3761
3762
3763
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3764
    disable_extended_io();
3765
3766
3767
3768
3769
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3770
int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
3771
	*ran_func, bool *finished, bool allow_funcs)
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
{
    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;
3787
    *ran_func = FALSE;
3788
    *finished = FALSE;
3789
3790
3791
3792
3793
3794
3795

    /* 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. */
3796
    if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) {
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
	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) {
3829
3830
3831
3832
3833
	/* 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) {
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
	    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
3852
		 * once, filtering out control characters. */
3853
3854
3855
3856
3857
3858
3859
		char *output = charalloc(kbinput_len + 1);
		size_t i;

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

3860
		do_output(output, kbinput_len, FALSE);
3861
3862

		free(output);
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872

		/* 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
3873
		/* Handle the "universal" statusbar prompt shortcuts. */
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
		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
3886
		/* Handle the normal edit window shortcuts, setting
3887
3888
3889
		 * 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
3890
		 * functions. */
3891
3892
3893
3894
3895
3896
		default:
		    /* Blow away the text in the cutbuffer if we aren't
		     * cutting text. */
		    if (s->func != do_cut_text)
			cutbuffer_reset();

3897
		    if (s->func != NULL) {
3898
			*ran_func = TRUE;
3899
3900
3901
3902
			if (ISSET(VIEW_MODE) && !s->viewok)
			    print_view_warning();
			else
			    s->func();
3903
		    }
3904
		    *finished = TRUE;
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
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
		    break;
	    }
	}
#ifndef NANO_SMALL
	else if (have_toggle) {
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
	    /* Toggle the flag associated with this shortcut. */
	    if (allow_funcs)
		do_toggle(t);
	}
#endif
	else
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
    }

    return input;
}

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3944
	    /* Subtract out the size of topwin. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3945
	    mouse_y -= 2 - no_more_space();
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980

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

3981
/* The user typed kbinput_len multibyte characters.  Add them to the
3982
3983
3984
 * edit buffer, filtering out all control characters if allow_cntrls is
 * TRUE. */
void do_output(char *output, size_t output_len, bool allow_cntrls)
3985
{
3986
    size_t current_len, i = 0;
3987
3988
3989
3990
3991
    bool old_constupdate = ISSET(CONSTUPDATE);
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

3992
3993
    char *char_buf = charalloc(mb_cur_max());
    int char_buf_len;
3994
3995
3996

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

3997
3998
    current_len = strlen(current->data);

3999
4000
    /* Turn off constant cursor position display. */
    UNSET(CONSTUPDATE);
4001

4002
    while (i < output_len) {
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
	/* 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;
	    }
4015
4016
	}

4017
4018
4019
	/* Interpret the next multibyte character.  If it's an invalid
	 * multibyte character, interpret it as though it's a byte
	 * character. */
4020
	char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL);
4021
4022

	i += char_buf_len;
4023

4024
4025
4026
4027
	/* If allow_cntrls is FALSE, filter out a control character. */
	if (!allow_cntrls && is_cntrl_mbchar(output + i - char_buf_len))
	    continue;

4028
4029
4030
4031
4032
4033
	/* When a character is inserted on the current magicline, it
	 * means we need a new one! */
	if (filebot == current)
	    new_magicline();

	/* More dangerousness fun =) */
4034
4035
	current->data = charealloc(current->data, current_len +
		(char_buf_len * 2));
4036

4037
	assert(current_x <= current_len);
4038

4039
	charmove(&current->data[current_x + char_buf_len],
4040
		&current->data[current_x],
4041
4042
4043
		current_len - current_x + char_buf_len);
	charcpy(&current->data[current_x], char_buf, char_buf_len);
	current_len += char_buf_len;
4044
	totsize++;
4045
4046
4047
4048
4049
	set_modified();

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

4053
	do_right(FALSE);
4054
4055

#ifndef DISABLE_WRAPPING
4056
4057
	/* If we're wrapping text, we need to call edit_refresh(). */
	if (!ISSET(NO_WRAP)) {
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
	    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(). */
4072
	if (!ISSET(NO_COLOR_SYNTAX))
4073
4074
4075
4076
	    do_refresh = TRUE;
#endif
    }

4077
4078
    /* Turn constant cursor position display back on if it was on
     * before. */
4079
4080
4081
    if (old_constupdate)
	SET(CONSTUPDATE);

4082
    free(char_buf);
4083

4084
4085
4086
4087
4088
4089
    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
}

4090
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
4091
4092
{
    int optchr;
4093
    int startline = 1;
4094
	/* Line to try and start at. */
4095
4096
    ssize_t startcol = 1;
	/* Column to try and start at. */
4097
#ifndef DISABLE_WRAPJUSTIFY
4098
4099
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
4100
#endif
4101
4102
4103
4104
4105
#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
4106
#ifdef HAVE_GETOPT_LONG
4107
    const struct option long_options[] = {
4108
	{"help", 0, NULL, 'h'},
4109
#ifdef ENABLE_MULTIBUFFER
4110
	{"multibuffer", 0, NULL, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
4111
4112
#endif
#ifdef ENABLE_NANORC
4113
#ifndef NANO_SMALL
4114
	{"historylog", 0, NULL, 'H'},
4115
#endif
4116
	{"ignorercfiles", 0, NULL, 'I'},
4117
#endif
4118
	{"morespace", 0, NULL, 'O'},
4119
#ifndef DISABLE_JUSTIFY
4120
	{"quotestr", 1, NULL, 'Q'},
4121
#endif
4122
4123
	{"tabsize", 1, NULL, 'T'},
	{"version", 0, NULL, 'V'},
4124
#ifdef ENABLE_COLOR
4125
	{"syntax", 1, NULL, 'Y'},
4126
#endif
4127
4128
4129
	{"const", 0, NULL, 'c'},
	{"rebinddelete", 0, NULL, 'd'},
	{"nofollow", 0, NULL, 'l'},
4130
#ifndef DISABLE_MOUSE
4131
	{"mouse", 0, NULL, 'm'},
4132
#endif
4133
#ifndef DISABLE_OPERATINGDIR
4134
	{"operatingdir", 1, NULL, 'o'},
4135
#endif
4136
	{"preserve", 0, NULL, 'p'},
4137
#ifndef DISABLE_WRAPJUSTIFY
4138
	{"fill", 1, NULL, 'r'},
4139
4140
#endif
#ifndef DISABLE_SPELLER
4141
	{"speller", 1, NULL, 's'},
4142
#endif
4143
4144
	{"tempfile", 0, NULL, 't'},
	{"view", 0, NULL, 'v'},
4145
#ifndef DISABLE_WRAPPING
4146
	{"nowrap", 0, NULL, 'w'},
4147
#endif
4148
4149
	{"nohelp", 0, NULL, 'x'},
	{"suspend", 0, NULL, 'z'},
4150
#ifndef NANO_SMALL
4151
4152
	{"smarthome", 0, NULL, 'A'},
	{"backup", 0, NULL, 'B'},
4153
4154
	{"backupdir", 1, NULL, 'C'},
	{"tabstospaces", 0, NULL, 'E'},
4155
4156
4157
4158
4159
	{"noconvert", 0, NULL, 'N'},
	{"smooth", 0, NULL, 'S'},
	{"restricted", 0, NULL, 'Z'},
	{"autoindent", 0, NULL, 'i'},
	{"cut", 0, NULL, 'k'},
4160
#endif
4161
	{NULL, 0, NULL, 0}
Chris Allegretta's avatar
Chris Allegretta committed
4162
4163
4164
    };
#endif

4165
4166
4167
#ifdef NANO_WIDE
    {
	/* If the locale set doesn't exist, or it exists but doesn't
4168
4169
	 * include the case-insensitive string "UTF8" or "UTF-8", we
	 * shouldn't go into UTF-8 mode. */
4170
4171
4172
	char *locale = setlocale(LC_ALL, "");

	if (locale == NULL || (locale != NULL &&
4173
		strcasestr(locale, "UTF8") == NULL &&
4174
		strcasestr(locale, "UTF-8") == NULL))
4175
	    SET(NO_UTF8);
4176
4177
4178

#ifdef USE_SLANG
	if (!ISSET(NO_UTF8))
4179
	    SLutf8_enable(TRUE);
4180
#endif
4181
4182
    }
#else
Chris Allegretta's avatar
Chris Allegretta committed
4183
    setlocale(LC_ALL, "");
4184
4185
#endif

4186
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
4187
4188
4189
4190
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4191
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4192
4193
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
4194
    if (geteuid() == NANO_ROOT_UID)
4195
4196
	SET(NO_WRAP);
#endif
4197

4198
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
4199
#ifdef HAVE_GETOPT_LONG
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4200
	getopt_long(argc, argv,
4201
		"h?ABC:EFHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4202
		long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
4203
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4204
	getopt(argc, argv,
4205
		"h?ABC:EFHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
4206
#endif
4207
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
4208
4209

	switch (optchr) {
4210
4211
4212
4213
4214
4215
4216
4217
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
4218
#ifndef NANO_SMALL
4219
4220
4221
4222
4223
4224
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
4225
	    case 'C':
4226
4227
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
4228
4229
4230
	    case 'E':
		SET(TABS_TO_SPACES);
		break;
4231
#endif
4232
#ifdef ENABLE_MULTIBUFFER
4233
4234
4235
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
4236
4237
#endif
#ifdef ENABLE_NANORC
4238
#ifndef NANO_SMALL
4239
4240
4241
	    case 'H':
		SET(HISTORYLOG);
		break;
4242
#endif
4243
4244
4245
	    case 'I':
		SET(NO_RCFILE);
		break;
4246
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4247
#ifndef NANO_SMALL
4248
4249
4250
	    case 'N':
		SET(NO_CONVERT);
		break;
4251
#endif
4252
4253
4254
	    case 'O':
		SET(MORE_SPACE);
		break;
4255
#ifndef DISABLE_JUSTIFY
4256
4257
4258
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
4259
#endif
4260
#ifndef NANO_SMALL
4261
4262
4263
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
4264
#endif
4265
4266
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
4267
4268
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4269
4270
4271
4272
4273
4274
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
4275
#ifdef ENABLE_COLOR
4276
4277
4278
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
4279
#endif
4280
4281
4282
4283
4284
4285
4286
4287
4288
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
4289
#ifndef NANO_SMALL
4290
4291
4292
4293
4294
4295
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
4296
#endif
4297
4298
4299
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
4300
#ifndef DISABLE_MOUSE
4301
4302
4303
	    case 'm':
		SET(USE_MOUSE);
		break;
4304
#endif
4305
#ifndef DISABLE_OPERATINGDIR
4306
4307
4308
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
4309
#endif
4310
4311
4312
	    case 'p':
		SET(PRESERVE);
		break;
4313
#ifndef DISABLE_WRAPJUSTIFY
4314
4315
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
4316
4317
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4318
4319
4320
4321
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
4322
#endif
4323
#ifndef DISABLE_SPELLER
4324
4325
4326
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
4327
#endif
4328
4329
4330
4331
4332
4333
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
4334
#ifndef DISABLE_WRAPPING
4335
4336
4337
	    case 'w':
		SET(NO_WRAP);
		break;
4338
#endif
4339
4340
4341
4342
4343
4344
4345
4346
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
4347
4348
4349
	}
    }

4350
4351
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
4352
4353
4354
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

4355
4356
4357
    /* 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. */
4358
4359
4360
4361
4362
4363
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
4364
/* We've read through the command line options.  Now back up the flags
4365
4366
 * 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
4367
4368
4369
4370
4371
#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
4372
#ifndef DISABLE_WRAPJUSTIFY
4373
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
4374
#endif
4375
4376
4377
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4378
4379
4380
4381
4382
4383
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
4384
	ssize_t tabsize_cpy = tabsize;
4385
	unsigned long flags_cpy = flags;
Chris Allegretta's avatar
Chris Allegretta committed
4386

4387
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4388
	operating_dir = NULL;
4389
#endif
4390
4391
4392
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
4393
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4394
	quotestr = NULL;
4395
4396
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
4397
	alt_speller = NULL;
4398
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4399
4400
4401
4402
4403
4404
4405
4406
4407

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
4408
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4409
4410
4411
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
4412
4413
4414
4415
4416
4417
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
#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
4430
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
4431
4432
4433
4434
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4435
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
4436
4437
4438
4439
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

4440
4441
4442
4443
4444
4445
4446
4447
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4448
#ifndef NANO_SMALL
4449
    /* Set up the backup directory (unless we're using restricted mode,
4450
4451
4452
4453
     * 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. */
4454
4455
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4456
4457
#endif

4458
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4459
    /* Set up the operating directory.  This entails chdir()ing there,
4460
     * so that file reads and writes will be based there. */
4461
4462
4463
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4464
#ifndef DISABLE_JUSTIFY
4465
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4466
	punct = mallocstrcpy(punct, ".?!");
4467
4468

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

Chris Allegretta's avatar
Chris Allegretta committed
4471
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4472
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
4473
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4474
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
4475
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4476
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
4477
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4478
		);
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
#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
4495
#endif /* !DISABLE_JUSTIFY */
4496

4497
4498
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
4499
     * command line and/or rcfile(s), check $SPELL for one, as Pico
4500
     * does (unless we're using restricted mode, in which case spell
4501
4502
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
4503
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
4504
4505
4506
4507
4508
4509
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

4510
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4511
    /* If whitespace wasn't specified, set its default value. */
4512
    if (whitespace == NULL) {
4513
	whitespace = mallocstrcpy(NULL, "  ");
4514
4515
4516
	whitespace_len[0] = 1;
	whitespace_len[1] = 1;
    }
4517
4518
#endif

4519
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4520
    if (tabsize == -1)
4521
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4522

4523
    /* Back up the old terminal settings so that they can be restored. */
4524
    tcgetattr(0, &oldterm);
4525

4526
4527
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4528
    initscr();
4529
    terminal_init();
4530

4531
4532
4533
    /* Turn the cursor on for sure. */
    curs_set(1);

4534
    /* Set up the global variables and the shortcuts. */
4535
4536
    global_init(FALSE);
    shortcut_init(FALSE);
4537
4538

    /* Set up the signal handlers. */
4539
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4540
4541

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

4545
    window_init();
4546
#ifndef DISABLE_MOUSE
4547
    mouse_init();
4548
#endif
4549

Chris Allegretta's avatar
Chris Allegretta committed
4550
#ifdef DEBUG
4551
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4552
#endif
4553

4554
4555
4556
    /* 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. */
4557
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
4558
	parse_line_column(&argv[optind][1], &startline, &startcol);
4559
4560
4561
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
4562
#ifdef ENABLE_MULTIBUFFER
4563
4564
4565
4566
4567
4568
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

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

4572
	for (; i < argc; i++) {
4573
4574
4575
4576
	    /* 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 &&
4577
		icol == 1)
4578
		parse_line_column(&argv[i][1], &iline, &icol);
4579
	    else {
4580
		load_buffer(argv[i]);
4581

4582
		if (iline > 1 || icol > 1) {
4583
		    do_gotolinecolumn(iline, icol, FALSE, FALSE, FALSE);
4584
4585
		    iline = 1;
		    icol = 1;
4586
4587
4588
		}
	    }
	}
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
    }
#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
4608
4609
	 * multibuffer support, or to the main filestruct if we
	 * don't. */
4610
	load_file();
Chris Allegretta's avatar
Chris Allegretta committed
4611
    }
4612
4613
4614
4615

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

4618
4619
4620
4621
4622
4623
4624
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

4625
    if (startline > 1 || startcol > 1)
4626
	do_gotolinecolumn(startline, startcol, FALSE, FALSE, FALSE);
4627

4628
4629
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4630
    sigsetjmp(jmpbuf, 1);
4631
#endif
4632

Robert Siemborski's avatar
Robert Siemborski committed
4633
4634
    edit_refresh();

4635
    while (TRUE) {
4636
	bool meta_key, func_key, s_or_t, ran_func, finished;
4637
4638

	/* Make sure the cursor is in the edit window. */
4639
	reset_cursor();
4640
4641
4642

	/* If constant cursor position display is on, display the cursor
	 * position. */
4643
	if (ISSET(CONSTUPDATE))
4644
	    do_cursorpos(TRUE);
4645

4646
4647
	currshortcut = main_list;

4648
	/* Read in and interpret characters. */
4649
4650
	do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
		TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4651
    }
4652

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4653
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4654
}