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

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

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

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

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

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

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

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

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

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

117
118
119
    endwin();
    curses_ended = TRUE;

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

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

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

133
	die_save_file(filename);
134
    }
135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    UNSET(WHITESPACE_DISPLAY);
#endif
289
290
#endif

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

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

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

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

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

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

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

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

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

470
471
    ptr = help_text + strlen(help_text);

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

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

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

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

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

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

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

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

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

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

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

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

	    ptr += sprintf(ptr, help_ptr);

	    free(help_ptr);
	}
565

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

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

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

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

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

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

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

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

604
605
606
    return newnode;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return head;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

792
793
794
795
796
797
798
799
/* Move all the text between (top, top_x) and (bot, bot_x) in the
 * current filestruct to a filestruct beginning with file_top and ending
 * with file_bot.  If no text is between (top, top_x) and (bot, bot_x),
 * don't do anything. */
void move_to_filestruct(filestruct **file_top, filestruct **file_bot,
	filestruct *top, size_t top_x, filestruct *bot, size_t bot_x)
{
    filestruct *top_save;
800
    size_t part_totsize;
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
    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;
899
    size_t part_totsize;
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
    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
955
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
956
957
{
    filestruct *temp;
958
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
959

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
980
981
982
983
984
985
#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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
1106
1107
1108
    exit(0);
}

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

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

1172
1173
1174
1175
1176
int no_more_space(void)
{
    return ISSET(MORE_SPACE) ? 1 : 0;
}

Chris Allegretta's avatar
Chris Allegretta committed
1177
1178
int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
1179
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
1180
1181
}

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

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

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

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

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

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

1221
1222
1223
1224
1225
1226
    /* Else continue as parent. */
    close(fd[1]);

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

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

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

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

1250
    f = fdopen(fd[0], "rb");
1251
    if (f == NULL)
1252
	nperror("fdopen");
1253

1254
    read_file(f, "stdin");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1255

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

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

1264
    if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
1265
1266
	nperror("sigaction");

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

1271
    return TRUE;
1272
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1273
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1274

1275
void do_verbatim_input(void)
1276
{
1277
1278
1279
    int *kbinput;
    size_t kbinput_len, i;
    char *output;
1280

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1281
    statusbar(_("Verbatim Input"));
1282

1283
1284
    /* Read in all the verbatim characters. */
    kbinput = get_verbatim_kbinput(edit, &kbinput_len);
1285

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

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

1294
    do_output(output, kbinput_len, TRUE);
1295
1296

    free(output);
1297
1298
}

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

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

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

1315
    placewewant = xplustabs();
1316

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

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

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

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

1341
	assert(current_x == strlen(current->data));
1342
1343
1344
1345
1346
1347

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

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

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

    set_modified();
1372
1373
1374
1375

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

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

1386
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1387
{
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
#ifndef NANO_SMALL
    if (ISSET(TABS_TO_SPACES)) {
	char *output;
	size_t output_len = 0, new_pww = placewewant;

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

	output = charalloc(output_len + 1);

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

	do_output(output, output_len, TRUE);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1404
1405

	free(output);
1406
1407
1408
1409
1410
1411
    } else {
#endif
	do_output("\t", 1, TRUE);
#ifndef NANO_SMALL
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1412
1413
}

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

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

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

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

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

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

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

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

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

1480
1481
    char_mb = charalloc(mb_cur_max());

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

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

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

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

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

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

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

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

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

1522
1523
	current_x = 0;
    }
1524

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

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

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

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

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

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

1558
1559
    char_mb = charalloc(mb_cur_max());

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

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

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

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

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

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

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

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

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

1645
1646
    free(char_mb);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2025
	    edit_refresh();
2026

2027
	    do_replace_highlight(TRUE, exp_word);
2028

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

2036
2037
2038
	    do_replace_highlight(FALSE, exp_word);

	    free(exp_word);
2039

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2136
	close(tempfile_fd);
2137

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

2142
	close(spell_fd[1]);
2143

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

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

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

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

2161
2162
	close(spell_fd[0]);

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

	close(sort_fd[1]);

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

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

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

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

2187
2188
	close(sort_fd[0]);

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

	close(uniq_fd[1]);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2326
    endwin();
2327

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

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

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

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

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

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

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

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

    refresh();
2370

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

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

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

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

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

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

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

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

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

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

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

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

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

2467
    return NULL;
2468
}
2469

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

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

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

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

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

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

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

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

2518
#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING)
2519
2520
/* 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
2521
2522
2523
2524
 * 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)
2525
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
{
    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. */
2558
	bool found_blank = FALSE;
2559

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

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

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

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

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

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

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

    blank_mb = charalloc(mb_cur_max());

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

	if (!is_blank_mbchar(blank_mb))
	    break;

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

    free(blank_mb);

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

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

    /* These four asserts are assumptions about the input data. */
2643
2644
2645
    assert(paragraph != NULL);
    assert(paragraph->data != NULL);
    assert(skip < strlen(paragraph->data));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2646
    assert(!is_blank_mbchar(paragraph->data + skip));
2647
2648
2649
2650
2651
2652
2653

    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') {
2654
2655
	int end_len;

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

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

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

		end += end_len;
		shift += end_len;
2670

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

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

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

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

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

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

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

		end += end_len;
		shift += end_len;
2724

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

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

    *new_end = *end;

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

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

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

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

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

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

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

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

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

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

2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
/* 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
2846
2847
2848
2849
2850
 *	   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. */
2851
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
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. */
2889
void do_para_begin(bool allow_update)
2890
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2891
2892
    const filestruct *current_save = current;
    const size_t pww_save = placewewant;
2893
2894
2895
2896
2897
2898
2899

    current_x = 0;
    placewewant = 0;

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

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

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

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

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

    quote_len = quote_length(foo->data);

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

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

    current_x = 0;
    placewewant = 0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3100
    return TRUE;
3101
3102
}

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

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

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

    last_par_line = current;
3133
3134

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3295
	    assert(break_pos <= line_len);
3296

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3444
    blank_statusbar();
3445

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

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

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

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

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

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

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

    display_main_list();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3591
3592
    check_die_too_small();
    resize_variables();
3593

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /* 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. */
3795
    if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) {
3796
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
	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) {
3828
3829
3830
3831
3832
	/* 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) {
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
	    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
3851
		 * once, filtering out control characters. */
3852
3853
3854
3855
3856
3857
3858
		char *output = charalloc(kbinput_len + 1);
		size_t i;

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

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

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

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

3896
		    if (s->func != NULL) {
3897
			*ran_func = TRUE;
3898
3899
3900
3901
			if (ISSET(VIEW_MODE) && !s->viewok)
			    print_view_warning();
			else
			    s->func();
3902
		    }
3903
		    *finished = TRUE;
3904
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
		    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
3943
	    /* Subtract out the size of topwin. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3944
	    mouse_y -= 2 - no_more_space();
3945
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

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

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

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

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

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

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

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

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

	i += char_buf_len;
4022

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

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

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

4036
	assert(current_x <= current_len);
4037

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

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

4052
	do_right(FALSE);
4053
4054

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

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

4081
    free(char_buf);
4082

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

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

4162
4163
#ifdef NANO_WIDE
    {
4164
4165
	/* If the locale set exists and includes the case-insensitive
	 * string "UTF8" or "UTF-8", we should use UTF-8. */
4166
4167
	char *locale = setlocale(LC_ALL, "");

4168
4169
4170
	if (locale != NULL && (strcasestr(locale, "UTF8") != NULL ||
		strcasestr(locale, "UTF-8") != NULL)) {
	    SET(USE_UTF8);
4171
#ifdef USE_SLANG
4172
	    SLutf8_enable(TRUE);
4173
#endif
4174
	}
4175
4176
    }
#else
Chris Allegretta's avatar
Chris Allegretta committed
4177
    setlocale(LC_ALL, "");
4178
4179
#endif

4180
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
4181
4182
4183
4184
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4185
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4186
4187
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
4188
    if (geteuid() == NANO_ROOT_UID)
4189
4190
	SET(NO_WRAP);
#endif
4191

4192
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
4193
#ifdef HAVE_GETOPT_LONG
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4194
	getopt_long(argc, argv,
4195
		"h?ABC:EFHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4196
		long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
4197
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4198
	getopt(argc, argv,
4199
		"h?ABC:EFHINOQ:ST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
4200
#endif
4201
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
4202
4203

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

4344
4345
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
4346
4347
4348
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

4349
4350
4351
    /* 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. */
4352
4353
4354
4355
4356
4357
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

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

4381
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4382
	operating_dir = NULL;
4383
#endif
4384
4385
4386
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
4387
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4388
	quotestr = NULL;
4389
4390
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
4391
	alt_speller = NULL;
4392
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4393
4394
4395
4396
4397
4398
4399
4400
4401

	do_rcfile();

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

4434
4435
4436
4437
4438
4439
4440
4441
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4442
#ifndef NANO_SMALL
4443
    /* Set up the backup directory (unless we're using restricted mode,
4444
4445
4446
4447
     * 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. */
4448
4449
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4450
4451
#endif

4452
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4453
    /* Set up the operating directory.  This entails chdir()ing there,
4454
     * so that file reads and writes will be based there. */
4455
4456
4457
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4458
#ifndef DISABLE_JUSTIFY
4459
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4460
	punct = mallocstrcpy(punct, ".?!");
4461
4462

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

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

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

4504
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4505
    /* If whitespace wasn't specified, set its default value. */
4506
    if (whitespace == NULL) {
4507
	whitespace = mallocstrcpy(NULL, "  ");
4508
4509
4510
	whitespace_len[0] = 1;
	whitespace_len[1] = 1;
    }
4511
4512
#endif

4513
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4514
    if (tabsize == -1)
4515
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4516

4517
    /* Back up the old terminal settings so that they can be restored. */
4518
    tcgetattr(0, &oldterm);
4519

4520
4521
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4522
    initscr();
4523
    terminal_init();
4524

4525
4526
4527
    /* Turn the cursor on for sure. */
    curs_set(1);

4528
    /* Set up the global variables and the shortcuts. */
4529
4530
    global_init(FALSE);
    shortcut_init(FALSE);
4531
4532

    /* Set up the signal handlers. */
4533
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4534
4535

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

4539
    window_init();
4540
#ifndef DISABLE_MOUSE
4541
    mouse_init();
4542
#endif
4543

Chris Allegretta's avatar
Chris Allegretta committed
4544
#ifdef DEBUG
4545
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4546
#endif
4547

4548
4549
4550
    /* 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. */
4551
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
4552
	parse_line_column(&argv[optind][1], &startline, &startcol);
4553
4554
4555
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
4556
#ifdef ENABLE_MULTIBUFFER
4557
4558
4559
4560
4561
4562
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

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

4566
	for (; i < argc; i++) {
4567
4568
4569
4570
	    /* 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 &&
4571
		icol == 1)
4572
		parse_line_column(&argv[i][1], &iline, &icol);
4573
	    else {
4574
		load_buffer(argv[i]);
4575

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

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

4612
4613
4614
4615
4616
4617
4618
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

4619
    if (startline > 1 || startcol > 1)
4620
	do_gotolinecolumn(startline, startcol, FALSE, FALSE, FALSE);
4621

4622
4623
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4624
    sigsetjmp(jmpbuf, 1);
4625
#endif
4626

Robert Siemborski's avatar
Robert Siemborski committed
4627
4628
    edit_refresh();

4629
    while (TRUE) {
4630
	bool meta_key, func_key, s_or_t, ran_func, finished;
4631
4632

	/* Make sure the cursor is in the edit window. */
4633
	reset_cursor();
4634
4635
4636

	/* If constant cursor position display is on, display the cursor
	 * position. */
4637
	if (ISSET(CONSTUPDATE))
4638
	    do_cursorpos(TRUE);
4639

4640
4641
	currshortcut = main_list;

4642
	/* Read in and interpret characters. */
4643
4644
	do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
		TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4645
    }
4646

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4647
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4648
}