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

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

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

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

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

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

55
56
57
58
#ifndef NANO_SMALL
#include <setjmp.h>
#endif

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

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

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

81
82
83
84
85
#ifndef DISABLE_JUSTIFY
static filestruct *jusbottom = NULL;
	/* Pointer to end of justify buffer. */
#endif

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
    if (ISSET(MODIFIED))
	die_save_file(filename);
130

131
#ifdef ENABLE_MULTIBUFFER
132
    /* Save all of the other modified file buffers, if any. */
133
    if (open_files != NULL) {
134
	openfilestruct *tmp = open_files;
135

136
137
	while (tmp != open_files->next) {
	    open_files = open_files->next;
138

139
140
141
142
	    /* 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. */
143
		fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
144
		filebot = open_files->filebot;
145
		die_save_file(open_files->filename);
146
147
148
149
150
	    }
	}
    }
#endif

151
152
    /* Get out. */
    exit(1);
153
154
}

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

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

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

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

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

    free(ret);
Chris Allegretta's avatar
Chris Allegretta committed
181
182
}

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

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

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

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

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

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

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

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

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

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

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

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

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

275
    /* First, set up the initial help text for the current function. */
276
277
    if (currshortcut == whereis_list || currshortcut == replace_list
	     || currshortcut == replace_list_2)
278
	htx = N_("Search Command Help Text\n\n "
279
		"Enter the words or characters you would like to search "
280
		"for, then hit Enter.  If there is a match for the text you "
281
		"entered, the screen will be updated to the location of the "
282
283
284
285
286
287
288
		"nearest match for the search string.\n\n The previous "
		"search string will be shown in brackets after the search "
		"prompt.  Hitting Enter without entering any text will "
		"perform the previous search.  If you have selected text "
		"with the mark and then search to replace, only matches in "
		"the selected text will be replaced.\n\n The following "
		"function keys are available in Search mode:\n\n");
289
    else if (currshortcut == gotoline_list)
290
	htx = N_("Go To Line Help Text\n\n "
291
292
293
294
295
296
		"Enter the line number that you wish to go to and hit "
		"Enter.  If there are fewer lines of text than the "
		"number you entered, you will be brought to the last line "
		"of the file.\n\n The following function keys are "
		"available in Go To Line mode:\n\n");
    else if (currshortcut == insertfile_list)
297
	htx = N_("Insert File Help Text\n\n "
298
299
300
301
302
303
304
		"Type in the name of a file to be inserted into the current "
		"file buffer at the current cursor location.\n\n "
		"If you have compiled nano with multiple file buffer "
		"support, and enable multiple buffers with the -F "
		"or --multibuffer command line flags, the Meta-F toggle, or "
		"a nanorc file, inserting a file will cause it to be "
		"loaded into a separate buffer (use Meta-< and > to switch "
305
306
307
308
		"between file buffers). If you need another blank buffer, "
		"do not enter any filename, or type in a nonexistent "
		"filename at the prompt and press Enter.\n\n The following "
		"function keys are available in Insert File mode:\n\n");
309
    else if (currshortcut == writefile_list)
310
	htx = N_("Write File Help Text\n\n "
311
312
		"Type the name that you wish to save the current file "
		"as and hit Enter to save the file.\n\n If you have "
313
		"selected text with the mark, you will be prompted to "
314
315
316
317
318
319
320
		"save only the selected portion to a separate file.  To "
		"reduce the chance of overwriting the current file with "
		"just a portion of it, the current filename is not the "
		"default in this mode.\n\n The following function keys "
		"are available in Write File mode:\n\n");
#ifndef DISABLE_BROWSER
    else if (currshortcut == browser_list)
321
	htx = N_("File Browser Help Text\n\n "
322
323
324
325
326
327
328
329
330
331
		"The file browser is used to visually browse the "
		"directory structure to select a file for reading "
		"or writing.  You may use the arrow keys or Page Up/"
		"Down to browse through the files, and S or Enter to "
		"choose the selected file or enter the selected "
		"directory.  To move up one level, select the directory "
		"called \"..\" at the top of the file list.\n\n The "
		"following function keys are available in the file "
		"browser:\n\n");
    else if (currshortcut == gotodir_list)
332
	htx = N_("Browser Go To Directory Help Text\n\n "
333
334
		"Enter the name of the directory you would like to "
		"browse to.\n\n If tab completion has not been disabled, "
335
		"you can use the Tab key to (attempt to) automatically "
336
337
338
		"complete the directory name.\n\n The following function "
		"keys are available in Browser Go To Directory mode:\n\n");
#endif
339
#ifndef DISABLE_SPELLER
340
    else if (currshortcut == spell_list)
341
	htx = N_("Spell Check Help Text\n\n "
342
343
344
345
346
		"The spell checker checks the spelling of all text "
		"in the current file.  When an unknown word is "
		"encountered, it is highlighted and a replacement can "
		"be edited.  It will then prompt to replace every "
		"instance of the given misspelled word in the "
347
348
349
		"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");
350
#endif
351
352
#ifndef NANO_SMALL
    else if (currshortcut == extcmd_list)
353
	htx = N_("External Command Help Text\n\n "
354
355
		"This menu allows you to insert the output of a command "
		"run by the shell into the current buffer (or a new "
356
357
358
		"buffer in multibuffer mode). If you need another blank "
		"buffer, do not enter any command.\n\n The following keys "
		"are available in this mode:\n\n");
359
#endif
360
361
362
    else
	/* Default to the main help list. */
	htx = N_(" nano help text\n\n "
363
364
365
366
367
368
369
370
371
372
	  "The nano editor is designed to emulate the functionality and "
	  "ease-of-use of the UW Pico text editor.  There are four main "
	  "sections of the editor: The top line shows the program "
	  "version, the current filename being edited, and whether "
	  "or not the file has been modified.  Next is the main editor "
	  "window showing the file being edited.  The status line is "
	  "the third line from the bottom and shows important messages. "
	  "The bottom two lines show the most commonly used shortcuts "
	  "in the editor.\n\n "
	  "The notation for shortcuts is as follows: Control-key "
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
373
374
375
376
377
378
379
380
381
	  "sequences are notated with a caret (^) symbol and can be "
	  "entered either by using the Control (Ctrl) key or pressing the "
	  "Esc key twice.  Escape-key sequences are notated with the Meta "
	  "(M) symbol and can be entered using either the Esc, Alt or "
	  "Meta key depending on your keyboard setup.  Also, pressing Esc "
	  "twice and then typing a three-digit number from 000 to 255 "
	  "will enter the character with the corresponding ASCII code.  "
	  "The following keystrokes are available in the main editor "
	  "window.  Alternative keys are shown in parentheses:\n\n");
382

383
384
385
    htx = _(htx);

    allocsize += strlen(htx);
386
387
388

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

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

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

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

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

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

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

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

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

452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
	/* Primary meta key sequence. */
	if (s->metaval != NANO_NO_KEY) {
	    entries++;
	    /* If this is the last entry, put it at the end. */
	    if (entries == 2 && s->miscval == NANO_NO_KEY) {
		entries++;
		*(ptr++) = '\t';
	    }
	    /* If the primary meta key sequence is the first entry,
	     * don't put parentheses around it. */
	    if (entries == 1 && s->metaval == NANO_ALT_SPACE)
		ptr += sprintf(ptr, "M-%.5s", _("Space"));
	    else
		ptr += sprintf(ptr, entries == 1 ? "M-%c" : "(M-%c)",
			toupper(s->metaval));
	    *(ptr++) = '\t';
	}
469

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

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

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

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

    /* If all went well, we didn't overwrite the allocated space for
504
     * help_text. */
505
    assert(strlen(help_text) < allocsize);
506
507
508
509
510
511
512
513
514
515
516
}
#endif

/* Create a new filestruct node.  Note that we specifically do not set
 * prevnode->next equal to the new line. */
filestruct *make_new_node(filestruct *prevnode)
{
    filestruct *newnode = (filestruct *)nmalloc(sizeof(filestruct));
    newnode->data = NULL;
    newnode->prev = prevnode;
    newnode->next = NULL;
517
    newnode->lineno = (prevnode != NULL) ? prevnode->lineno + 1 : 1;
518
519
520
    return newnode;
}

521
/* Make a copy of a filestruct node. */
Chris Allegretta's avatar
Chris Allegretta committed
522
filestruct *copy_node(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
523
{
Chris Allegretta's avatar
Chris Allegretta committed
524
525
    filestruct *dst = (filestruct *)nmalloc(sizeof(filestruct));
    assert(src != NULL);
526
    dst->data = mallocstrcpy(NULL, src->data);
Chris Allegretta's avatar
Chris Allegretta committed
527
528
529
530
531
532
    dst->next = src->next;
    dst->prev = src->prev;
    dst->lineno = src->lineno;
    return dst;
}

533
/* Splice a node into an existing filestruct. */
534
535
void splice_node(filestruct *begin, filestruct *newnode, filestruct
	*end)
536
{
537
538
539
540
    assert(newnode != NULL && begin != NULL);
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
541
542
543
544
    if (end != NULL)
	end->prev = newnode;
}

545
/* Unlink a node from the rest of the filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
546
void unlink_node(const filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
547
{
Chris Allegretta's avatar
Chris Allegretta committed
548
    assert(fileptr != NULL);
549
550
551
552
553
554
555
    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
556
void delete_node(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
557
{
558
559
560
561
    assert(fileptr != NULL && fileptr->data != NULL);
    if (fileptr->data != NULL)
	free(fileptr->data);
    free(fileptr);
562
563
564
}

/* Okay, now let's duplicate a whole struct! */
Chris Allegretta's avatar
Chris Allegretta committed
565
filestruct *copy_filestruct(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
566
{
Chris Allegretta's avatar
Chris Allegretta committed
567
568
    filestruct *head;	/* copy of src, top of the copied list */
    filestruct *prev;	/* temp that traverses the list */
Chris Allegretta's avatar
Chris Allegretta committed
569

Chris Allegretta's avatar
Chris Allegretta committed
570
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
571

Chris Allegretta's avatar
Chris Allegretta committed
572
573
574
575
576
577
578
579
    prev = copy_node(src);
    prev->prev = NULL;
    head = prev;
    src = src->next;
    while (src != NULL) {
	prev->next = copy_node(src);
	prev->next->prev = prev;
	prev = prev->next;
Chris Allegretta's avatar
Chris Allegretta committed
580

Chris Allegretta's avatar
Chris Allegretta committed
581
	src = src->next;
Chris Allegretta's avatar
Chris Allegretta committed
582
583
    }

Chris Allegretta's avatar
Chris Allegretta committed
584
    prev->next = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
585
586
587
    return head;
}

588
/* Frees a filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
589
void free_filestruct(filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
590
{
591
592
593
594
595
    assert(src != NULL);

    while (src->next != NULL) {
	src = src->next;
	delete_node(src->prev);
596
    }
597
    delete_node(src);
598
599
}

600
601
602
603
604
605
/* 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;
606
    assert(top != NULL && bot != NULL && fileage != NULL && filebot != NULL);
607
608
609
610

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

611
612
613
614
615
616
617
618
619
620
621
622
623
    /* 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;
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653

    /* 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. */
654
void unpartition_filestruct(partition **p)
655
656
{
    char *tmp;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
657
    assert(p != NULL && fileage != NULL && filebot != NULL);
658
659
660
661
662

    /* 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);
663
    fileage->prev = (*p)->top_prev;
664
665
    if (fileage->prev != NULL)
	fileage->prev->next = fileage;
666
    fileage->data = charealloc(fileage->data, strlen((*p)->top_data) +
667
	strlen(fileage->data) + 1);
668
669
    strcpy(fileage->data, (*p)->top_data);
    free((*p)->top_data);
670
671
672
673
674
675
    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. */
676
    filebot->next = (*p)->bot_next;
677
678
    if (filebot->next != NULL)
	filebot->next->prev = filebot;
679
    filebot->data = charealloc(filebot->data, strlen(filebot->data) +
680
681
682
	strlen((*p)->bot_data) + 1);
    strcat(filebot->data, (*p)->bot_data);
    free((*p)->bot_data);
683

684
685
    /* Restore the top and bottom of the filestruct, if they were
     * different from the top and bottom of the partition. */
686
687
688
689
    if ((*p)->fileage != NULL)
	fileage = (*p)->fileage;
    if ((*p)->filebot != NULL)
	filebot = (*p)->filebot;
690
691

    /* Uninitialize the partition. */
692
693
    free(*p);
    *p = NULL;
694
695
}

696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
/* 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;
    long part_totsize;
    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;
    long part_totsize;
    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
859
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
860
861
{
    filestruct *temp;
862
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
863

Chris Allegretta's avatar
Chris Allegretta committed
864
865
    assert(fileage == NULL || fileage != fileage->next);
    for (temp = fileage; temp != NULL; temp = temp->next)
Chris Allegretta's avatar
Chris Allegretta committed
866
867
868
	temp->lineno = i++;
}

Chris Allegretta's avatar
Chris Allegretta committed
869
void renumber(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
870
{
Chris Allegretta's avatar
Chris Allegretta committed
871
    if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage)
Chris Allegretta's avatar
Chris Allegretta committed
872
	renumber_all();
Chris Allegretta's avatar
Chris Allegretta committed
873
874
    else {
	int lineno = fileptr->prev->lineno;
875

Chris Allegretta's avatar
Chris Allegretta committed
876
877
878
879
	assert(fileptr != fileptr->next);
	for (; fileptr != NULL; fileptr = fileptr->next)
	    fileptr->lineno = ++lineno;
    }
Chris Allegretta's avatar
Chris Allegretta committed
880
881
}

882
883
/* Print one usage string to the screen.  This cuts down on duplicate
 * strings to translate and leaves out the parts that shouldn't be
Chris Allegretta's avatar
Chris Allegretta committed
884
 * translatable (the flag names). */
885
886
void print1opt(const char *shortflag, const char *longflag, const char
	*desc)
887
888
889
890
891
892
893
894
895
896
897
898
899
{
    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

900
    printf("%s\n", _(desc));
901
902
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
903
void usage(void)
Chris Allegretta's avatar
Chris Allegretta committed
904
905
{
#ifdef HAVE_GETOPT_LONG
Chris Allegretta's avatar
Chris Allegretta committed
906
907
    printf(_("Usage: nano [+LINE] [GNU long option] [option] [file]\n\n"));
    printf(_("Option\t\tLong option\t\tMeaning\n"));
Chris Allegretta's avatar
Chris Allegretta committed
908
#else
Chris Allegretta's avatar
Chris Allegretta committed
909
910
    printf(_("Usage: nano [+LINE] [option] [file]\n\n"));
    printf(_("Option\t\tMeaning\n"));
911
#endif
912

913
914
    print1opt("-h, -?", "--help", N_("Show this message"));
    print1opt(_("+LINE"), "", N_("Start at line number LINE"));
915
#ifndef NANO_SMALL
916
917
918
    print1opt("-A", "--smarthome", N_("Enable smart home key"));
    print1opt("-B", "--backup", N_("Backup existing files on save"));
    print1opt(_("-E [dir]"), _("--backupdir=[dir]"), N_("Directory for writing backup files"));
919
#endif
920
#ifdef ENABLE_MULTIBUFFER
921
    print1opt("-F", "--multibuffer", N_("Enable multiple file buffers"));
Chris Allegretta's avatar
Chris Allegretta committed
922
923
#endif
#ifdef ENABLE_NANORC
924
#ifndef NANO_SMALL
925
    print1opt("-H", "--historylog", N_("Log & read search/replace string history"));
926
#endif
927
    print1opt("-I", "--ignorercfiles", N_("Don't look at nanorc files"));
Chris Allegretta's avatar
Chris Allegretta committed
928
929
#endif
#ifndef NANO_SMALL
930
    print1opt("-N", "--noconvert", N_("Don't convert files from DOS/Mac format"));
931
#endif
932
933
934
#ifdef NANO_WIDE
    print1opt("-O", "--noutf8", N_("Don't convert files from UTF-8 format"));
#endif
935
#ifndef DISABLE_JUSTIFY
936
    print1opt(_("-Q [str]"), _("--quotestr=[str]"), N_("Quoting string, default \"> \""));
937
#endif
938
#ifdef HAVE_REGEX_H
939
    print1opt("-R", "--regexp", N_("Do regular expression searches"));
940
#endif
941
#ifndef NANO_SMALL
942
    print1opt("-S", "--smooth", N_("Smooth scrolling"));
943
#endif
944
945
    print1opt(_("-T [#cols]"), _("--tabsize=[#cols]"), N_("Set width of a tab in cols to #cols"));
    print1opt("-V", "--version", N_("Print version information and exit"));
946
#ifdef ENABLE_COLOR
947
    print1opt(_("-Y [str]"), _("--syntax [str]"), N_("Syntax definition to use"));
948
#endif
949
950
    print1opt("-Z", "--restricted", N_("Restricted mode"));
    print1opt("-c", "--const", N_("Constantly show cursor position"));
951
#ifndef NANO_SMALL
952
953
954
    print1opt("-d", "--rebinddelete", N_("Fix Backspace/Delete confusion problem"));
    print1opt("-i", "--autoindent", N_("Automatically indent new lines"));
    print1opt("-k", "--cut", N_("Cut from cursor to end of line"));
955
#endif
956
    print1opt("-l", "--nofollow", N_("Don't follow symbolic links, overwrite"));
957
#ifndef DISABLE_MOUSE
958
    print1opt("-m", "--mouse", N_("Enable mouse"));
Chris Allegretta's avatar
Chris Allegretta committed
959
#endif
960
#ifndef DISABLE_OPERATINGDIR
961
    print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), N_("Set operating directory"));
Chris Allegretta's avatar
Chris Allegretta committed
962
#endif
963
    print1opt("-p", "--preserve", N_("Preserve XON (^Q) and XOFF (^S) keys"));
964
#ifndef DISABLE_WRAPJUSTIFY
965
    print1opt(_("-r [#cols]"), _("--fill=[#cols]"), N_("Set fill cols to (wrap lines at) #cols"));
966
#endif
967
#ifndef DISABLE_SPELLER
968
    print1opt(_("-s [prog]"), _("--speller=[prog]"), N_("Enable alternate speller"));
969
#endif
970
971
    print1opt("-t", "--tempfile", N_("Auto save on exit, don't prompt"));
    print1opt("-v", "--view", N_("View (read only) mode"));
972
#ifndef DISABLE_WRAPPING
973
    print1opt("-w", "--nowrap", N_("Don't wrap long lines"));
Chris Allegretta's avatar
Chris Allegretta committed
974
#endif
975
976
    print1opt("-x", "--nohelp", N_("Don't show help window"));
    print1opt("-z", "--suspend", N_("Enable suspend"));
Chris Allegretta's avatar
Chris Allegretta committed
977

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

Chris Allegretta's avatar
Chris Allegretta committed
981
982
983
    exit(0);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
984
void version(void)
Chris Allegretta's avatar
Chris Allegretta committed
985
{
Chris Allegretta's avatar
Chris Allegretta committed
986
    printf(_(" GNU nano version %s (compiled %s, %s)\n"),
Chris Allegretta's avatar
Chris Allegretta committed
987
	   VERSION, __TIME__, __DATE__);
988
    printf(_
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
989
	   (" Email: nano@nano-editor.org	Web: http://www.nano-editor.org/"));
990
    printf(_("\n Compiled options:"));
991

992
993
994
#ifndef ENABLE_NLS
    printf(" --disable-nls");
#endif
995
996
997
#ifdef DEBUG
    printf(" --enable-debug");
#endif
998
999
#ifdef NANO_EXTRA
    printf(" --enable-extra");
1000
#endif
1001
1002
1003
#ifdef NANO_SMALL
    printf(" --enable-tiny");
#else
1004
#ifdef DISABLE_BROWSER
1005
    printf(" --disable-browser");
1006
#endif
1007
1008
#ifdef DISABLE_HELP
    printf(" --disable-help");
1009
1010
#endif
#ifdef DISABLE_JUSTIFY
1011
    printf(" --disable-justify");
1012
#endif
1013
#ifdef DISABLE_MOUSE
1014
    printf(" --disable-mouse");
1015
#endif
1016
1017
1018
#ifdef DISABLE_OPERATINGDIR
    printf(" --disable-operatingdir");
#endif
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
#ifdef DISABLE_SPELLER
    printf(" --disable-speller");
#endif
#ifdef DISABLE_TABCOMP
    printf(" --disable-tabcomp");
#endif
#endif /* NANO_SMALL */
#ifdef DISABLE_WRAPPING
    printf(" --disable-wrapping");
#endif
1029
1030
1031
#ifdef DISABLE_ROOTWRAP
    printf(" --disable-wrapping-as-root");
#endif
1032
1033
1034
1035
1036
1037
1038
1039
1040
#ifdef ENABLE_COLOR
    printf(" --enable-color");
#endif
#ifdef ENABLE_MULTIBUFFER
    printf(" --enable-multibuffer");
#endif
#ifdef ENABLE_NANORC
    printf(" --enable-nanorc");
#endif
1041
1042
1043
1044
#ifdef USE_SLANG
    printf(" --with-slang");
#endif
    printf("\n");
Chris Allegretta's avatar
Chris Allegretta committed
1045
1046
1047
1048
}

int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
1049
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
1050
1051
}

1052
void nano_disabled_msg(void)
1053
{
Chris Allegretta's avatar
Chris Allegretta committed
1054
    statusbar(_("Sorry, support for this function has been disabled"));
1055
1056
}

Chris Allegretta's avatar
Chris Allegretta committed
1057
#ifndef NANO_SMALL
1058
RETSIGTYPE cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
1059
{
1060
1061
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
1062
1063
}

1064
1065
/* Return TRUE on success. */
bool open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
1066
{
1067
1068
1069
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
1070
1071
			/* Original and temporary handlers for
			 * SIGINT. */
1072
1073
1074
    bool sig_failed = FALSE;
    /* sig_failed means that sigaction() failed without changing the
     * signal handlers.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1075
     *
1076
1077
     * We use this variable since it is important to put things back
     * when we finish, even if we get errors. */
Chris Allegretta's avatar
Chris Allegretta committed
1078

1079
    /* Make our pipes. */
1080

1081
1082
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
1083
	return FALSE;
1084
    }
1085

1086
    /* Fork a child. */
1087

1088
1089
1090
1091
1092
    if ((pid = fork()) == 0) {
	close(fd[0]);
	dup2(fd[1], fileno(stdout));
	dup2(fd[1], fileno(stderr));
	/* If execl() returns at all, there was an error. */
1093

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1094
	execl("/bin/sh", "sh", "-c", command, 0);
1095
1096
	exit(0);
    }
1097

1098
1099
1100
1101
1102
1103
1104
    /* Else continue as parent. */

    close(fd[1]);

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

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

1111
1112
    /* Enable interpretation of the special control keys so that we get
     * SIGINT when Ctrl-C is pressed. */
1113
1114
    enable_signals();

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1115
    if (sigaction(SIGINT, NULL, &newaction) == -1) {
1116
	sig_failed = TRUE;
1117
	nperror("sigaction");
Chris Allegretta's avatar
Chris Allegretta committed
1118
    } else {
1119
	newaction.sa_handler = cancel_fork;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1120
	if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
1121
	    sig_failed = TRUE;
1122
1123
	    nperror("sigaction");
	}
Chris Allegretta's avatar
Chris Allegretta committed
1124
    }
1125
1126
    /* Note that now oldaction is the previous SIGINT signal handler,
     * to be restored later. */
1127

1128
    f = fdopen(fd[0], "rb");
1129
    if (f == NULL)
1130
	nperror("fdopen");
1131

1132
    read_file(f, "stdin");
1133
1134
    /* If multibuffer mode is on, we could be here in view mode.  If so,
     * don't set the modification flag. */
1135
1136
1137
1138
1139
1140
    if (!ISSET(VIEW_MODE))
	set_modified();

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

1141
    if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
1142
1143
	nperror("sigaction");

1144
1145
    /* Disable interpretation of the special control keys so that we can
     * use Ctrl-C for other things. */
1146
1147
    disable_signals();

1148
    return TRUE;
1149
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1150
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1151

1152
void do_verbatim_input(void)
1153
{
1154
1155
    int *kbinput;	/* Used to hold verbatim input. */
    size_t kbinput_len;	/* Length of verbatim input. */
1156
1157

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

1159
1160
    /* Read in all the verbatim characters. */
    kbinput = get_verbatim_kbinput(edit, &kbinput_len);
1161

1162
1163
    /* Display all the verbatim characters at once. */
    do_output(kbinput, kbinput_len);
1164

1165
    free(kbinput);
1166
1167
}

1168
void do_backspace(void)
Chris Allegretta's avatar
Chris Allegretta committed
1169
{
1170
    if (current != fileage || current_x > 0) {
1171
	do_left(FALSE);
1172
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1173
1174
1175
    }
}

1176
void do_delete(void)
Chris Allegretta's avatar
Chris Allegretta committed
1177
{
1178
    bool do_refresh = FALSE;
1179
1180
1181
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1182
1183
    assert(current != NULL && current->data != NULL &&
	current_x <= strlen(current->data));
Chris Allegretta's avatar
Chris Allegretta committed
1184

1185
    placewewant = xplustabs();
1186

1187
1188
    if (current->data[current_x] != '\0') {
	size_t linelen = strlen(current->data + current_x);
1189

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

1192
	/* Let's get dangerous. */
1193
	charmove(&current->data[current_x], &current->data[current_x + 1],
1194
		linelen);
Chris Allegretta's avatar
Chris Allegretta committed
1195

1196
1197
1198
1199
	null_at(&current->data, linelen + current_x - 1);
#ifndef NANO_SMALL
	if (current_x < mark_beginx && mark_beginbuf == current)
	    mark_beginx--;
Chris Allegretta's avatar
Chris Allegretta committed
1200
#endif
1201
1202
    } else if (current != filebot && (current->next != filebot ||
	current->data[0] == '\0')) {
1203
	/* We can delete the line before filebot only if it is blank: it
1204
	 * becomes the new magicline then. */
1205
	filestruct *foo = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1206

1207
	assert(current_x == strlen(current->data));
1208
1209
1210
1211
1212
1213

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

1214
1215
1216
1217
1218
1219
1220
1221
1222
	current->data = charealloc(current->data, current_x +
		strlen(foo->data) + 1);
	strcpy(current->data + current_x, foo->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == current->next) {
	    mark_beginx += current_x;
	    mark_beginbuf = current;
	}
#endif
1223
	if (filebot == foo)
Chris Allegretta's avatar
Chris Allegretta committed
1224
1225
1226
1227
1228
1229
	    filebot = current;

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
1230
#ifndef DISABLE_WRAPPING
1231
	wrap_reset();
1232
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1233
    } else
1234
	return;
Chris Allegretta's avatar
Chris Allegretta committed
1235
1236
1237

    totsize--;
    set_modified();
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249

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

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

1252
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1253
{
1254
1255
    int kbinput = '\t';
    do_output(&kbinput, 1);
Chris Allegretta's avatar
Chris Allegretta committed
1256
1257
}

1258
/* Someone hits return *gasp!* */
1259
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1260
{
1261
1262
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1263

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1266
#ifndef NANO_SMALL
1267
1268
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1269
1270
1271
1272
1273
	/* 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)
1274
1275
	    extra = current_x;
	totsize += extra;
1276
1277
1278
1279
1280
1281
1282
    }
#endif
    newnode->data = charalloc(strlen(current->data + current_x) +
	extra + 1);
    strcpy(&newnode->data[extra], current->data + current_x);
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT))
1283
1284
	strncpy(newnode->data, current->data, extra);
#endif
1285
1286
1287
1288
1289
    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;
1290
    }
1291
1292
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1293

1294
    if (current == filebot)
1295
1296
	filebot = newnode;
    splice_node(current, newnode, current->next);
1297

1298
1299
1300
    totsize++;
    renumber(current);
    current = newnode;
1301
1302

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

1304
1305
1306
    totlines++;
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1307
1308
}

1309
#ifndef NANO_SMALL
1310
void do_next_word(void)
Chris Allegretta's avatar
Chris Allegretta committed
1311
{
1312
    size_t old_pww = placewewant;
1313
    const filestruct *old_current = current;
1314
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1315

1316
1317
    /* Skip letters in this word first. */
    while (current->data[current_x] != '\0' &&
1318
	isalnum(current->data[current_x]))
1319
	current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1320

1321
1322
    for (; current != NULL; current = current->next) {
	while (current->data[current_x] != '\0' &&
1323
		!isalnum(current->data[current_x]))
1324
	    current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1325

1326
1327
	if (current->data[current_x] != '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1328

1329
1330
1331
1332
	current_x = 0;
    }
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1333

1334
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1335

1336
1337
    /* Update the screen. */
    edit_redraw(old_current, old_pww);
1338
}
Chris Allegretta's avatar
Chris Allegretta committed
1339

1340
/* The same thing for backwards. */
1341
void do_prev_word(void)
1342
{
1343
    size_t old_pww = placewewant;
1344
    const filestruct *old_current = current;
1345
    assert(current != NULL && current->data != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1346

1347
1348
    current_x++;

1349
    /* Skip letters in this word first. */
1350
    while (current_x > 0 && isalnum(current->data[current_x - 1]))
1351
	current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1352

1353
    for (; current != NULL; current = current->prev) {
1354
	while (current_x > 0 && !isalnum(current->data[current_x - 1]))
1355
	    current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1356

1357
	if (current_x > 0)
1358
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1359

1360
	if (current->prev != NULL)
1361
	    current_x = strlen(current->prev->data) + 1;
Chris Allegretta's avatar
Chris Allegretta committed
1362
1363
    }

1364
1365
    current_x--;

1366
    if (current == NULL) {
1367
1368
	current = fileage;
	current_x = 0;
1369
    } else {
1370
	while (current_x > 0 && isalnum(current->data[current_x - 1]))
1371
	    current_x--;
1372
    }
Chris Allegretta's avatar
Chris Allegretta committed
1373

1374
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1375

1376
1377
    /* Update the screen. */
    edit_redraw(old_current, old_pww);
1378
}
Chris Allegretta's avatar
Chris Allegretta committed
1379

1380
void do_mark(void)
1381
{
1382
1383
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1384
1385
1386
1387
1388
1389
1390
1391
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1392
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1393

1394
#ifndef DISABLE_WRAPPING
1395
1396
void wrap_reset(void)
{
1397
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1398
}
1399
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1400

1401
#ifndef DISABLE_WRAPPING
1402
1403
1404
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1405
bool do_wrap(filestruct *inptr)
Chris Allegretta's avatar
Chris Allegretta committed
1406
{
1407
1408
1409
1410
    size_t len = strlen(inptr->data);
	/* Length of the line we wrap. */
    size_t i = 0;
	/* Generic loop variable. */
1411
    ssize_t wrap_loc = -1;
1412
	/* Index of inptr->data where we wrap. */
1413
    ssize_t word_back = -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1414
#ifndef NANO_SMALL
1415
    const char *indentation = NULL;
1416
	/* Indentation to prepend to the new line. */
1417
    size_t indent_len = 0;	/* strlen(indentation) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1418
#endif
1419
1420
    const char *after_break;	/* Text after the wrap point. */
    size_t after_break_len;	/* strlen(after_break) */
1421
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1422
    const char *wrap_line = NULL;
1423
	/* The next line, minus indentation. */
1424
1425
1426
    size_t wrap_line_len = 0;	/* strlen(wrap_line) */
    char *newline = NULL;	/* The line we create. */
    size_t new_line_len = 0;	/* Eventual length of newline. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1427

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

1431
/* Step 1, finding where to wrap.  We are going to add a new line
1432
 * after a whitespace character.  In this step, we set wrap_loc as the
1433
1434
 * location of this replacement.
 *
1435
 * Where should we break the line?  We need the last legal wrap point
1436
1437
1438
 * such that the last word before it ended at or before fill.  If there
 * is no such point, we settle for the first legal wrap point.
 *
1439
1440
 * A legal wrap point is a whitespace character that is not followed by
 * whitespace.
1441
1442
1443
1444
1445
1446
1447
 *
 * If there is no legal wrap point or we found the last character of the
 * line, we should return without wrapping.
 *
 * Note that the initial indentation does not count as a legal wrap
 * point if we are going to auto-indent!
 *
1448
1449
 * Note that the code below could be optimized, by not calling
 * strnlenpt() so often. */
1450

1451
1452
1453
1454
1455
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT))
	i = indent_length(inptr->data);
#endif
    wrap_line = inptr->data + i;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1456
    for (; i < len; i++, wrap_line++) {
1457
	/* Record where the last word ended. */
1458
	if (!isblank(*wrap_line))
1459
	    word_back = i;
1460
	/* If we have found a legal wrap point and the current word
1461
	 * extends too far, then we stop. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1462
1463
	if (wrap_loc != -1 &&
		strnlenpt(inptr->data, word_back + 1) > fill)
1464
	    break;
1465
	/* We record the latest legal wrap point. */
1466
	if (word_back != i && !isblank(wrap_line[1]))
1467
	    wrap_loc = i;
1468
    }
1469
1470
    if (i == len)
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1471

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

1476
1477
1478
1479
    /* after_break is the text that will be moved to the next line. */
    after_break = inptr->data + wrap_loc + 1;
    after_break_len = len - wrap_loc - 1;
    assert(after_break_len == strlen(after_break));
Chris Allegretta's avatar
Chris Allegretta committed
1480

1481
1482
1483
    /* new_line_len will later be increased by the lengths of indentation
     * and wrap_line. */
    new_line_len = after_break_len;
Chris Allegretta's avatar
Chris Allegretta committed
1484

1485
1486
1487
    /* We prepend the wrapped text to the next line, if the flag is set,
     * and there is a next line, and prepending would not make the line
     * too long. */
1488
    if (same_line_wrap && inptr->next) {
1489
1490
	wrap_line = inptr->next->data;
	wrap_line_len = strlen(wrap_line);
Chris Allegretta's avatar
Chris Allegretta committed
1491

1492
	/* +1 for the space between after_break and wrap_line. */
1493
	if ((new_line_len + 1 + wrap_line_len) <= fill) {
1494
	    wrapping = TRUE;
1495
1496
1497
	    new_line_len += (1 + wrap_line_len);
	}
    }
1498

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1499
#ifndef NANO_SMALL
1500
    if (ISSET(AUTOINDENT)) {
1501
1502
	/* Indentation comes from the next line if wrapping, else from
	 * this line. */
1503
1504
1505
	indentation = (wrapping ? wrap_line : inptr->data);
	indent_len = indent_length(indentation);
	if (wrapping)
1506
1507
	    /* The wrap_line text should not duplicate indentation.
	     * Note in this case we need not increase new_line_len. */
1508
1509
1510
	    wrap_line += indent_len;
	else
	    new_line_len += indent_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1511
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1512
1513
#endif

1514
1515
    /* Now we allocate the new line and copy into it. */
    newline = charalloc(new_line_len + 1);  /* +1 for \0 */
1516
    new_line_len = 0;
1517
    *newline = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1518

1519
1520
1521
1522
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
	strncpy(newline, indentation, indent_len);
	newline[indent_len] = '\0';
1523
	new_line_len = indent_len;
1524
    }
1525
#endif
1526
    strcat(newline, after_break);
1527
1528
1529
    new_line_len += after_break_len;
    /* We end the old line after wrap_loc.  Note that this does not eat
     * the space. */
1530
1531
    null_at(&inptr->data, wrap_loc + 1);
    totsize++;
1532
    if (wrapping) {
1533
	/* In this case, totsize increases by 1 since we add a space
1534
	 * between after_break and wrap_line.  If the line already ends
1535
1536
	 * in a tab or a space, we don't add a space and decrement
	 * totsize to account for that. */
1537
	if (!isblank(newline[new_line_len - 1]))
1538
1539
1540
	    strcat(newline, " ");
	else
	    totsize--;
1541
1542
1543
1544
1545
	strcat(newline, wrap_line);
	free(inptr->next->data);
	inptr->next->data = newline;
    } else {
	filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct));
1546

1547
1548
	/* In this case, the file size changes by +1 for the new line,
	 * and +indent_len for the new indentation. */
1549
1550
1551
1552
1553
1554
1555
1556
#ifndef NANO_SMALL
	totsize += indent_len;
#endif
	totlines++;
	temp->data = newline;
	temp->prev = inptr;
	temp->next = inptr->next;
	temp->prev->next = temp;
1557
1558
1559
	/* If temp->next is NULL, then temp is the last line of the
	 * file, so we must set filebot. */
	if (temp->next != NULL)
1560
1561
1562
1563
	    temp->next->prev = temp;
	else
	    filebot = temp;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1564

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

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

1571
1572
1573
1574
    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
    if (!wrapping)
	renumber(inptr);
Chris Allegretta's avatar
Chris Allegretta committed
1575

1576
1577
1578
1579
1580
1581
    /* If the cursor was after the break point, we must move it. */
    if (current_x > wrap_loc) {
	current = current->next;
	current_x -=
#ifndef NANO_SMALL
		-indent_len +
1582
#endif
1583
1584
1585
1586
		wrap_loc + 1;
	wrap_reset();
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1587

1588
#ifndef NANO_SMALL
1589
1590
    /* If the mark was on this line after the wrap point, we move it
     * down.  If it was on the next line and we wrapped, we move it
1591
1592
1593
1594
1595
1596
1597
     * right. */
    if (mark_beginbuf == inptr && mark_beginx > wrap_loc) {
	mark_beginbuf = inptr->next;
	mark_beginx -= wrap_loc - indent_len + 1;
    } else if (wrapping && mark_beginbuf == inptr->next)
	mark_beginx += after_break_len;
#endif /* !NANO_SMALL */
1598

1599
    return TRUE;
1600
}
1601
#endif /* !DISABLE_WRAPPING */
1602

1603
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1604
/* A word is misspelled in the file.  Let the user replace it.  We
1605
1606
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1607
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1608
    char *save_search, *save_replace;
1609
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1610
    filestruct *edittop_save = edittop, *current_save = current;
1611
	/* Save where we are. */
1612
    bool canceled = FALSE;
1613
	/* The return value. */
1614
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1615
#ifndef NANO_SMALL
1616
1617
    bool reverse_search_set = ISSET(REVERSE_SEARCH);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1618
#ifdef HAVE_REGEX_H
1619
1620
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1621
1622
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1623
1624
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1625
1626
1627
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1628
    filestruct *top, *bot;
1629
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1630
#endif
1631

1632
    /* Make sure spell-check is case sensitive. */
1633
    SET(CASE_SENSITIVE);
1634

1635
#ifndef NANO_SMALL
1636
1637
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1638
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1639
#ifdef HAVE_REGEX_H
1640
1641
1642
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1643

1644
    /* Save the current search/replace strings. */
1645
1646
1647
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1648

1649
    /* Set the search/replace strings to the misspelled word. */
1650
1651
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1652

1653
1654
#ifndef NANO_SMALL
    if (old_mark_set) {
1655
	/* If the mark is on, partition the filestruct so that it
1656
1657
1658
	 * 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. */
1659
	mark_order((const filestruct **)&top, &top_x,
1660
	    (const filestruct **)&bot, &bot_x, &right_side_up);
1661
	filepart = partition_filestruct(top, top_x, bot, bot_x);
1662
	added_magicline = (filebot->data[0] != '\0');
1663
1664
1665
1666
	UNSET(MARK_ISSET);
    }
#endif

1667
    /* Start from the top of the file. */
1668
    edittop = fileage;
1669
    current = fileage;
1670
    current_x = (size_t)-1;
1671
    placewewant = 0;
1672

1673
    /* Find the first whole-word occurrence of word. */
1674
    findnextstr_wrap_reset();
1675
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) {
1676
1677
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
1678

1679
	    do_replace_highlight(TRUE, word);
1680

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1681
	    /* Allow all instances of the word to be corrected. */
1682
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
1683
#ifndef NANO_SMALL
1684
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
1685
#endif
1686
			 _("Edit a replacement")) == -1);
1687

1688
	    do_replace_highlight(FALSE, word);
1689

1690
	    if (!canceled && strcmp(word, answer) != 0) {
1691
		current_x--;
1692
1693
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
1694
	    }
1695
1696

	    break;
1697
	}
1698
    }
Chris Allegretta's avatar
Chris Allegretta committed
1699

1700
1701
#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1702
1703
	/* If the mark was on and we added a magicline, remove it
	 * now. */
1704
1705
	if (added_magicline)
	    remove_magicline();
1706

1707
1708
1709
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
1710
	    bot_x += top_x;
1711
	if (right_side_up) {
1712
1713
	    mark_beginx = top_x;
	    current_x_save = bot_x;
1714
	} else {
1715
1716
	    current_x_save = top_x;
	    mark_beginx = bot_x;
1717
	}
1718

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1719
1720
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
1721
	unpartition_filestruct(&filepart);
1722
	SET(MARK_ISSET);
1723
1724
    }
#endif
1725

1726
1727
1728
1729
1730
    /* 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
1731

1732
    /* Restore where we were. */
1733
    edittop = edittop_save;
1734
1735
    current = current_save;
    current_x = current_x_save;
1736
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
1737

1738
    /* Restore case sensitivity setting. */
1739
1740
1741
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

1742
#ifndef NANO_SMALL
1743
1744
1745
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
1746
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1747
#ifdef HAVE_REGEX_H
1748
1749
1750
1751
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
1752

1753
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
1754
1755
}

1756
1757
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
1758
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
1759
{
1760
1761
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1762
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1763
1764
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
1765

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

1770
    statusbar(_("Creating misspelled word list, please wait..."));
1771

1772
    /* A new process to run spell in. */
1773
    if ((pid_spell = fork()) == 0) {
1774

1775
	/* Child continues (i.e, future spell process). */
1776

1777
	close(spell_fd[0]);
1778

1779
	/* Replace the standard input with the temp file. */
1780
1781
1782
1783
1784
1785
	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;

1786
	close(tempfile_fd);
1787

1788
	/* Send spell's standard output to the pipe. */
1789
1790
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1791

1792
	close(spell_fd[1]);
1793

1794
	/* Start spell program; we are using PATH. */
1795
	execlp("spell", "spell", NULL);
1796

1797
	/* Should not be reached, if spell is found. */
1798
	exit(1);
1799
    }
Chris Allegretta's avatar
Chris Allegretta committed
1800

1801
    /* Parent continues here. */
1802
1803
    close(spell_fd[1]);

1804
    /* A new process to run sort in. */
1805
1806
    if ((pid_sort = fork()) == 0) {

1807
1808
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
1809
1810
1811
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1812
1813
	close(spell_fd[0]);

1814
	/* Send sort's standard output to the new pipe. */
1815
1816
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1817
1818
1819

	close(sort_fd[1]);

1820
1821
1822
	/* Start sort program.  Use -f to remove mixed case without
	 * having to have ANOTHER pipe for tr.  If this isn't portable,
	 * let me know. */
1823
1824
	execlp("sort", "sort", "-f", NULL);

1825
	/* Should not be reached, if sort is found. */
1826
1827
1828
	exit(1);
    }

1829
    close(spell_fd[0]);
1830
1831
    close(sort_fd[1]);

1832
    /* A new process to run uniq in. */
1833
1834
    if ((pid_uniq = fork()) == 0) {

1835
1836
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
1837
1838
1839
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1840
1841
	close(sort_fd[0]);

1842
	/* Send uniq's standard output to the new pipe. */
1843
1844
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1845
1846
1847

	close(uniq_fd[1]);

1848
	/* Start uniq program; we are using PATH. */
1849
1850
	execlp("uniq", "uniq", NULL);

1851
	/* Should not be reached, if uniq is found. */
1852
1853
1854
	exit(1);
    }

1855
    close(sort_fd[0]);
1856
    close(uniq_fd[1]);
1857

1858
    /* Child process was not forked successfully. */
1859
1860
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
1861
	return _("Could not fork");
1862
    }
1863

1864
    /* Get system pipe buffer size. */
1865
1866
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
1867
	return _("Could not get size of pipe buffer");
1868
    }
Chris Allegretta's avatar
Chris Allegretta committed
1869

1870
    /* Read in the returned spelling errors. */
1871
1872
1873
    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
1874

1875
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
1876
1877
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1878
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
1879
	read_buff_ptr += read_buff_read;
1880

1881
    }
Chris Allegretta's avatar
Chris Allegretta committed
1882

1883
    *read_buff_ptr = (char)NULL;
1884
    close(uniq_fd[0]);
1885

1886
    /* Process the spelling errors. */
1887
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
1888

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

1891
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
1892
	    *read_buff_ptr = (char)NULL;
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
	    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
1903

1904
    /* Special case where last word doesn't end with \n or \r. */
1905
1906
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
1907

1908
1909
    free(read_buff);
    replace_abort();
1910
    edit_refresh();
1911

1912
    /* Process end of spell process. */
1913
1914
1915
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
1916

1917
1918
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
1919

1920
1921
1922
1923
1924
1925
1926
1927
    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;
1928

1929
  close_pipes_and_exit:
1930

1931
    /* Don't leak any handles. */
1932
1933
1934
1935
1936
1937
1938
1939
    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
1940
1941
}

1942
1943
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
1944
const char *do_alt_speller(char *tempfile_name)
1945
{
1946
1947
1948
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
    int current_y_save = current_y;
1949
1950
1951
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
1952
1953
    static char **spellargs = NULL;
    FILE *f;
1954
#ifndef NANO_SMALL
1955
    bool old_mark_set = ISSET(MARK_ISSET);
1956
1957
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1958
1959
1960
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1961
1962
    filestruct *top, *bot;
    size_t top_x, bot_x;
1963
    int mbb_lineno_save = 0;
1964
	/* We're going to close the current file, and open the output of
1965
1966
1967
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
1968
1969
1970
    long old_totsize = totsize;
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
1971

1972
    if (old_mark_set) {
1973
1974
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
1975
	mbb_lineno_save = mark_beginbuf->lineno;
1976
1977
	UNSET(MARK_ISSET);
    }
1978
#endif
1979

1980
    endwin();
1981

1982
    /* Set up an argument list to pass execvp(). */
1983
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1984
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
1985

1986
1987
1988
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1989
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
1990
1991
1992
1993
1994
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
1995

1996
    /* Start a new process for the alternate speller. */
1997
    if ((pid_spell = fork()) == 0) {
1998
	/* Start alternate spell program; we are using PATH. */
1999
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
2000

2001
2002
2003
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
2004

2005
2006
    /* Could not fork?? */
    if (pid_spell < 0)
2007
	return _("Could not fork");
2008

2009
    /* Wait for alternate speller to complete. */
2010
    wait(&alt_spell_status);
2011

2012
2013
2014
2015
2016
2017
2018
2019
2020
    if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0) {
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
2021
2022

    refresh();
2023

2024
2025
2026
    /* Restore the terminal to its previous state. */
    terminal_init();

2027
#ifndef NANO_SMALL
2028
    if (old_mark_set) {
2029
2030
2031
2032
2033
2034
2035
	long part_totsize;

	/* If the mark was on, partition the filestruct so that it
	 * contains only the marked text, and keep track of whether the
	 * temp file (which should contain the spell-checked marked
	 * text) will have a magicline added when it's reloaded. */
	mark_order((const filestruct **)&top, &top_x,
2036
		(const filestruct **)&bot, &bot_x, &right_side_up);
2037
2038
2039
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	added_magicline = (filebot->data[0] != '\0');

2040
2041
2042
2043
	/* 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);
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
	old_totsize -= part_totsize;
    }
#endif

    /* Reinitialize the filestruct. */
    free_filestruct(fileage);
    global_init(TRUE);

    /* Reload the temp file.  Do what load_buffer() would do, except for
     * making a new buffer for the temp file if multibuffer support is
     * available. */
    open_file(tempfile_name, FALSE, &f);
    read_file(f, tempfile_name);
    current = fileage;

#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2061
	filestruct *top_save = fileage;
2062

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2063
2064
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2065
2066
2067
	if (added_magicline)
	    remove_magicline();

2068
2069
2070
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2071
	    bot_x += top_x;
2072
	if (right_side_up) {
2073
2074
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2075
	} else {
2076
2077
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2078
	}
2079

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2080
2081
2082
2083
	/* 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. */
2084
	unpartition_filestruct(&filepart);
2085
2086

	/* Renumber starting with the beginning line of the old
2087
2088
2089
2090
	 * 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
2091
	renumber(top_save);
2092
	totlines = filebot->lineno;
2093
2094
2095
2096
2097
	old_totsize += totsize;
	totsize = old_totsize;

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2098
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2099
	mark_beginbuf = current;
2100
2101
2102
2103

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

2106
2107
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2108
2109
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2110

2111
2112
    /* Go back to the old position, mark the file as modified, and make
     * sure that the titlebar is refreshed. */
2113
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2114
2115
2116
    set_modified();
    clearok(topwin, FALSE);
    titlebar(NULL);
2117

2118
    return NULL;
2119
}
2120

2121
void do_spell(void)
2122
{
2123
    int i;
2124
2125
    char *temp = safe_tempnam(0, "nano.");
    const char *spell_msg;
2126

2127
2128
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2129
	return;
2130
    }
2131

2132
2133
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2134
	i = write_marked(temp, TRUE, FALSE);
2135
2136
    else
#endif
2137
	i = write_file(temp, TRUE, FALSE, FALSE);
2138
2139

    if (i == -1) {
2140
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2141
	free(temp);
2142
	return;
2143
2144
    }

2145
#ifdef ENABLE_MULTIBUFFER
2146
2147
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2148
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2149
#endif
2150

2151
2152
    spell_msg = alt_speller != NULL ? do_alt_speller(temp) :
	do_int_speller(temp);
2153
    unlink(temp);
2154
    free(temp);
2155

2156
    if (spell_msg != NULL)
2157
2158
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2159
    else
2160
	statusbar(_("Finished checking spelling"));
2161
}
2162
#endif /* !DISABLE_SPELLER */
2163

2164
#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
2165
2166
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2167
2168
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2169
    size_t len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2170

Chris Allegretta's avatar
Chris Allegretta committed
2171
    assert(line != NULL);
2172
    while (isblank(*line)) {
Chris Allegretta's avatar
Chris Allegretta committed
2173
2174
	line++;
	len++;
Chris Allegretta's avatar
Chris Allegretta committed
2175
    }
Chris Allegretta's avatar
Chris Allegretta committed
2176
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2177
}
2178
#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
2179

Chris Allegretta's avatar
Chris Allegretta committed
2180
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2181
/* justify_format() replaces Tab by Space and multiple spaces by 1
2182
2183
2184
 * (except it maintains 2 after a non-repeated character in punct
 * followed by a character in brackets).  Note that the terminating \0
 * counts as a space.
Chris Allegretta's avatar
Chris Allegretta committed
2185
 *
2186
2187
 * justify_format() might make line->data shorter, and change the actual
 * pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2188
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2189
 * justify_format() will not look at the first skip characters of line.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2190
2191
 * skip should be at most strlen(line->data).  The character at
 * line[skip + 1] must not be whitespace. */
2192
void justify_format(filestruct *line, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2193
{
Chris Allegretta's avatar
Chris Allegretta committed
2194
2195
2196
2197
2198
    char *back, *front;

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

    back = line->data + skip;
2203
    for (front = back; ; front++) {
2204
	bool remove_space = FALSE;
2205
2206
	    /* Do we want to remove this space? */

2207
	if (*front == '\t')
Chris Allegretta's avatar
Chris Allegretta committed
2208
	    *front = ' ';
2209

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

2215
	    remove_space = TRUE;
2216
	    for (; bob >= line->data + skip; bob--) {
2217
		if (strchr(punct, *bob) != NULL) {
2218
2219
2220
2221
2222
		    /* If this character is in punct, don't remove the
		     * space unless this character and the character
		     * before it are the same. */
		    remove_space = (bob > line->data + skip &&
			*bob == *(bob - 1));
2223
2224
2225
2226
2227
2228
2229
2230
		    break;
		}
		if (strchr(brackets, *bob) == NULL)
		    break;
	    }
	}

	if (remove_space) {
Chris Allegretta's avatar
Chris Allegretta committed
2231
	    /* Now *front is a space we want to remove.  We do that by
2232
	     * simply failing to assign it to *back. */
Chris Allegretta's avatar
Chris Allegretta committed
2233
2234
2235
2236
#ifndef NANO_SMALL
	    if (mark_beginbuf == line && back - line->data < mark_beginx)
		mark_beginx--;
#endif
2237
2238
	    if (*front == '\0')
		*(back - 1) = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2239
2240
2241
2242
	} else {
	    *back = *front;
	    back++;
	}
2243
2244
	if (*front == '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2245
2246
    }

2247
    back--;
2248
    assert(*back == '\0' && *front == '\0');
Chris Allegretta's avatar
Chris Allegretta committed
2249

Chris Allegretta's avatar
Chris Allegretta committed
2250
2251
    /* Now back is the new end of line->data. */
    if (back != front) {
2252
	totsize -= front - back;
Chris Allegretta's avatar
Chris Allegretta committed
2253
2254
2255
2256
2257
	null_at(&line->data, back - line->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == line && back - line->data < mark_beginx)
	    mark_beginx = back - line->data;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2258
    }
Chris Allegretta's avatar
Chris Allegretta committed
2259
}
Chris Allegretta's avatar
Chris Allegretta committed
2260

Chris Allegretta's avatar
Chris Allegretta committed
2261
2262
2263
2264
2265
2266
/* 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. */
2267
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2268
{
2269
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2270
    regmatch_t matches;
2271
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2272

2273
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2274
	return 0;
2275
2276
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2277
2278
2279
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2280

2281
    /* Compute quote depth level. */
2282
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2283
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2284
2285
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2286
}
Chris Allegretta's avatar
Chris Allegretta committed
2287

Chris Allegretta's avatar
Chris Allegretta committed
2288
2289
2290
/* 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. */
2291
2292
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2293
{
Chris Allegretta's avatar
Chris Allegretta committed
2294
    /* Here is the assumption about a_quote: */
2295
2296
    assert(a_quote == quote_length(a_line));
    return a_quote == quote_length(b_line) &&
2297
	strncmp(a_line, b_line, a_quote) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2298
}
2299

2300
2301
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2302
bool indents_match(const char *a_line, size_t a_indent, const char
2303
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2304
{
Chris Allegretta's avatar
Chris Allegretta committed
2305
2306
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2307

2308
2309
    return b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2310
}
Chris Allegretta's avatar
Chris Allegretta committed
2311

2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
/* Is foo the beginning of a paragraph?
 *
 *   A line of text consists of a "quote part", followed by an
 *   "indentation part", followed by text.  The functions quote_length()
 *   and indent_length() calculate these parts.
 *
 *   A line is "part of a paragraph" if it has a part not in the quote
 *   part or the indentation.
 *
 *   A line is "the beginning of a paragraph" if it is part of a
 *   paragraph and
 *	1) it is the top line of the file, or
 *	2) the line above it is not part of a paragraph, or
 *	3) the line above it does not have precisely the same quote
 *	   part, or
 *	4) the indentation of this line is not an initial substring of
 *	   the indentation of the previous line, or
 *	5) this line has no quote part and some indentation, and
 *	   AUTOINDENT is not set.
 *   The reason for number 5) is that if AUTOINDENT is not set, then an
 *   indented line is expected to start a paragraph, like in books.
 *   Thus, nano can justify an indented paragraph only if AUTOINDENT is
 *   turned on. */
bool begpar(const filestruct *const foo)
{
    size_t quote_len;
    size_t indent_len;
    size_t temp_id_len;

    /* Case 1). */
    if (foo->prev == NULL)
	return TRUE;

    quote_len = quote_length(foo->data);
    indent_len = indent_length(foo->data + quote_len);

    /* Not part of a paragraph. */
    if (foo->data[quote_len + indent_len] == '\0')
	return FALSE;

    /* Case 3). */
    if (!quotes_match(foo->data, quote_len, foo->prev->data))
	return TRUE;

    temp_id_len = indent_length(foo->prev->data + quote_len);

    /* Case 2) or 5) or 4). */
    if (foo->prev->data[quote_len + temp_id_len] == '\0' ||
	(quote_len == 0 && indent_len > 0
#ifndef NANO_SMALL
	&& !ISSET(AUTOINDENT)
#endif
	) || !indents_match(foo->prev->data + quote_len, temp_id_len,
	foo->data + quote_len, indent_len))
	return TRUE;

    return FALSE;
}

/* We find the last beginning-of-paragraph line before the current
 * line. */
void do_para_begin(void)
{
    const filestruct *old_current = current;
    const size_t old_pww = placewewant;

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2384
	    current_y--;
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
	} while (!begpar(current));
    }

    edit_redraw(old_current, old_pww);
}

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

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

/* A line is the last line of a paragraph if it is in a paragraph, and
 * the next line isn't, or is the beginning of a paragraph.  We move
 * down to the end of a paragraph, then one line farther. */
void do_para_end(void)
{
    const filestruct *const old_current = current;
    const size_t old_pww = placewewant;

    current_x = 0;
    placewewant = 0;

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

    while (current->next != NULL && inpar(current->next->data) &&
2413
	    !begpar(current->next)) {
2414
	current = current->next;
2415
2416
	current_y++;
    }
2417
2418
2419
2420
2421
2422
2423

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

    edit_redraw(old_current, old_pww);
}

2424
2425
2426
2427
/* 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. */
2428
2429
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2430
{
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
    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;
Chris Allegretta's avatar
Chris Allegretta committed
2444

2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
    if (old_mark_set)
	mbb_lineno_save = mark_beginbuf->lineno;
#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
2461

2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
    /* 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
2474
#ifndef NANO_SMALL
2475
2476
	if (old_mark_set && top->lineno == mbb_lineno_save)
	    mark_beginbuf = top;
Chris Allegretta's avatar
Chris Allegretta committed
2477
#endif
2478
	top = top->prev;
Chris Allegretta's avatar
Chris Allegretta committed
2479
    }
2480
2481
2482
2483
2484
2485
2486

    /* 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
2487
2488
2489
    return first_line;
}

Chris Allegretta's avatar
Chris Allegretta committed
2490
/* Is it possible to break line at or before goal? */
2491
bool breakable(const char *line, ssize_t goal)
Chris Allegretta's avatar
Chris Allegretta committed
2492
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2493
    for (; *line != '\0' && goal >= 0; line++) {
2494
	if (isblank(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2495
2496
	    return TRUE;

2497
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2498
2499
2500
2501
	    goal -= 2;
	else
	    goal -= 1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2502
2503
2504
    /* If goal is not negative, the whole line (one word) was short
     * enough. */
    return goal >= 0;
Chris Allegretta's avatar
Chris Allegretta committed
2505
2506
}

Chris Allegretta's avatar
Chris Allegretta committed
2507
/* We are trying to break a chunk off line.  We find the last space such
2508
 * that the display length to there is at most goal + 1.  If there is no
2509
2510
2511
 * such space, and force is TRUE, then we find the first space.  Anyway,
 * we then take the last space in that group of spaces.  The terminating
 * '\0' counts as a space. */
2512
ssize_t break_line(const char *line, ssize_t goal, bool force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2513
{
2514
    ssize_t space_loc = -1;
Chris Allegretta's avatar
Chris Allegretta committed
2515
2516
	/* Current tentative return value.  Index of the last space we
	 * found with short enough display width.  */
2517
    ssize_t cur_loc = 0;
2518
	/* Current index in line. */
Chris Allegretta's avatar
Chris Allegretta committed
2519
2520

    assert(line != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2521
    for (; *line != '\0' && goal >= 0; line++, cur_loc++) {
Chris Allegretta's avatar
Chris Allegretta committed
2522
2523
2524
2525
	if (*line == ' ')
	    space_loc = cur_loc;
	assert(*line != '\t');

Chris Allegretta's avatar
Chris Allegretta committed
2526
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
	    goal -= 2;
	else
	    goal--;
    }
    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;
    if (space_loc == -1) {
	/* No space found short enough. */
	if (force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2537
	    for (; *line != '\0'; line++, cur_loc++)
2538
		if (*line == ' ' && *(line + 1) != ' ' && *(line + 1) != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
2539
2540
2541
2542
		    return cur_loc;
	return -1;
    }
    /* Perhaps the character after space_loc is a space.  But because
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2543
     * of justify_format(), there can be only two adjacent. */
Chris Allegretta's avatar
Chris Allegretta committed
2544
2545
2546
2547
2548
2549
    if (*(line - cur_loc + space_loc + 1) == ' ' ||
	*(line - cur_loc + space_loc + 1) == '\0')
	space_loc++;
    return space_loc;
}

2550
2551
2552
2553
2554
/* Find the beginning of the current paragraph if we're in one, or the
 * beginning of the next paragraph if we're not.  Afterwards, save the
 * quote length and paragraph length in *quote and *par.  Return FALSE
 * if we found a paragraph, or TRUE if there was an error or we didn't
 * find a paragraph.
Chris Allegretta's avatar
Chris Allegretta committed
2555
 *
2556
2557
2558
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
bool do_para_search(size_t *const quote, size_t *const par)
2559
{
Chris Allegretta's avatar
Chris Allegretta committed
2560
    size_t quote_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2561
	/* Length of the initial quotation of the paragraph we
2562
	 * search. */
Chris Allegretta's avatar
Chris Allegretta committed
2563
2564
    size_t par_len;
	/* Number of lines in that paragraph. */
2565
2566
2567
2568
    size_t indent_len;
	/* Generic indentation length. */
    filestruct *line;
	/* Generic line of text. */
2569

Chris Allegretta's avatar
Chris Allegretta committed
2570
#ifdef HAVE_REGEX_H
2571
2572
2573
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
	return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
2574
    }
Chris Allegretta's avatar
Chris Allegretta committed
2575
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2576

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

2580
2581
    current_x = 0;

2582
    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2583
    indent_len = indent_length(current->data + quote_len);
Robert Siemborski's avatar
Robert Siemborski committed
2584

2585
2586
2587
2588
    /* Here we find the first line of the paragraph to search.  If the
     * current line is in a paragraph, then we move back to the first
     * line of the paragraph.  Otherwise, we move to the first line that
     * is in a paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2589
2590
    if (current->data[quote_len + indent_len] != '\0') {
	/* This line is part of a paragraph.  So we must search back to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2591
2592
	 * the first line of this paragraph.  First we check items 1)
	 * and 3) above. */
2593
2594
	while (current->prev != NULL &&	quotes_match(current->data,
		quote_len, current->prev->data)) {
2595
	    size_t temp_id_len =
2596
		indent_length(current->prev->data + quote_len);
2597
		/* The indentation length of the previous line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2598

2599
	    /* Is this line the beginning of a paragraph, according to
2600
	     * items 2), 5), or 4) above?  If so, stop. */
2601
	    if (current->prev->data[quote_len + temp_id_len] == '\0' ||
2602
		(quote_len == 0 && indent_len > 0
2603
#ifndef NANO_SMALL
2604
		&& !ISSET(AUTOINDENT)
2605
#endif
2606
2607
		) || !indents_match(current->prev->data + quote_len,
		temp_id_len, current->data + quote_len, indent_len))
2608
2609
2610
2611
		break;
	    indent_len = temp_id_len;
	    current = current->prev;
	    current_y--;
Chris Allegretta's avatar
Chris Allegretta committed
2612
	}
Chris Allegretta's avatar
Chris Allegretta committed
2613
    } else {
Chris Allegretta's avatar
Chris Allegretta committed
2614
2615
	/* This line is not part of a paragraph.  Move down until we get
	 * to a non "blank" line. */
Chris Allegretta's avatar
Chris Allegretta committed
2616
	do {
2617
	    /* There is no next paragraph, so nothing to move to. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2618
2619
	    if (current->next == NULL) {
		placewewant = 0;
2620
		return TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2621
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2622
	    current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2623
	    current_y++;
2624
	    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2625
2626
2627
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
    }
2628

2629
2630
    /* Now current is the first line of the paragraph, and quote_len is
     * the quotation length of that line. */
Chris Allegretta's avatar
Chris Allegretta committed
2631

2632
2633
    /* Next step, compute par_len, the number of lines in this
     * paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2634
2635
2636
2637
    line = current;
    par_len = 1;
    indent_len = indent_length(line->data + quote_len);

2638
2639
2640
    while (line->next != NULL &&
	    quotes_match(current->data, quote_len, line->next->data)) {
	size_t temp_id_len = indent_length(line->next->data + quote_len);
2641

2642
	if (!indents_match(line->data + quote_len, indent_len,
2643
		line->next->data + quote_len, temp_id_len) ||
Chris Allegretta's avatar
Chris Allegretta committed
2644
2645
2646
		line->next->data[quote_len + temp_id_len] == '\0' ||
		(quote_len == 0 && temp_id_len > 0
#ifndef NANO_SMALL
2647
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
2648
2649
#endif
		))
2650
2651
2652
2653
	    break;
	indent_len = temp_id_len;
	line = line->next;
	par_len++;
2654
2655
    }

2656
2657
    /* Now par_len is the number of lines in this paragraph.  We should
     * never call quotes_match() or quote_length() again. */
2658

2659
2660
2661
2662
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
    *quote = quote_len;
    *par = par_len;
2663

2664
    return FALSE;
2665
2666
}

2667
2668
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
2669
void do_justify(bool full_justify)
2670
{
2671
2672
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
2673
	 * For restoring after unjustify. */
2674
    filestruct *last_par_line;
2675
2676
	/* Will be the line containing the newline after the last line
	 * of the result.  Also for restoring after unjustify. */
2677
    bool allow_respacing;
2678
	/* Whether we should change the spacing at the end of a line
2679
	 * after justifying it.  This should be TRUE whenever we move
2680
	 * to the next line after justifying the current line. */
2681
2682

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2683
     * unjustifies.  Note that we don't need to save totlines. */
2684
2685
    size_t current_x_save = current_x;
    int current_y_save = current_y;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2686
2687
    long flags_save = flags, totsize_save = totsize;
    filestruct *edittop_save = edittop, *current_save = current;
2688
2689
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
2690
    size_t mark_beginx_save = mark_beginx;
2691
#endif
2692
    int kbinput;
2693
    bool meta_key, func_key, s_or_t;
2694

2695
2696
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
2697
	current = fileage;
2698
2699

    last_par_line = current;
2700
2701

    while (TRUE) {
2702
2703
2704
2705
2706
2707
2708
2709
2710
	size_t quote_len;
	    /* Length of the initial quotation of the paragraph we
	     * justify. */
	size_t par_len;
	    /* Number of lines in that paragraph. */

	/* Find the first line of the paragraph to be justified.  That
	 * is the start of this paragraph if we're in one, or the start
	 * of the next otherwise.  Save the quote length and paragraph
2711
2712
2713
2714
2715
2716
	 * length (number of lines).  Don't refresh the screen yet,
	 * since we'll do that after we justify.  If the search
	 * failed, we're justifying the whole file, and the search
	 * didn't leave us on the last line of the file, set the last
	 * line of the text to be justified to the last line of the file
	 * and break out of the loop.  Otherwise, refresh the screen and
2717
2718
	 * get out. */
	if (do_para_search(&quote_len, &par_len)) {
2719
	    if (full_justify && current != filebot) {
2720
		last_par_line = filebot;
2721
2722
2723
		break;
	    } else {
		edit_refresh();
2724
		return;
2725
2726
	    }
	}
2727

2728
2729
2730
	/* Next step, we loop through the lines of this paragraph,
	 * justifying each one individually. */
	for (; par_len > 0; current_y++, par_len--) {
2731
	    size_t indent_len;	/* Generic indentation length. */
2732
2733
2734
	    size_t line_len;
	    size_t display_len;
		/* The width of current in screen columns. */
2735
	    ssize_t break_pos;
2736
2737
		/* Where we will break the line. */

2738
2739
2740
2741
2742
	    /* We'll be moving to the next line after justifying the
	     * current line in almost all cases, so allow changing the
	     * spacing at the ends of justified lines by default. */
	    allow_respacing = TRUE;

2743
2744
	    indent_len = quote_len + indent_length(current->data +
		quote_len);
2745

2746
	    /* If we haven't already done it, copy the original
2747
	     * paragraph to the justify buffer. */
2748
2749
	    if (first_par_line == NULL)
		first_par_line = backup_lines(current, full_justify ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2750
2751
			filebot->lineno - current->lineno : par_len,
			quote_len);
2752

2753
2754
2755
2756
2757
2758
	    /* Now we call justify_format() on the current line of the
	     * paragraph, which will remove excess spaces from it and
	     * change tabs to spaces. */
	    justify_format(current, quote_len +
		indent_length(current->data + quote_len));

2759
2760
2761
2762
2763
2764
	    line_len = strlen(current->data);
	    display_len = strlenpt(current->data);

	    if (display_len > fill) {
		/* The line is too long.  Try to wrap it to the next. */
	        break_pos = break_line(current->data + indent_len,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2765
2766
2767
2768
			fill - strnlenpt(current->data, indent_len),
			TRUE);
		if (break_pos == -1 || break_pos + indent_len ==
			line_len)
2769
2770
2771
2772
2773
2774
2775
2776
2777
		    /* We can't break the line, or don't need to, so
		     * just go on to the next. */
		    goto continue_loc;
		break_pos += indent_len;
		assert(break_pos < line_len);
		if (par_len == 1) {
		    /* There is no next line in this paragraph.  We make
		     * a new line and copy text after break_pos into
		     * it. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2778
2779
		    splice_node(current, make_new_node(current),
			current->next);
2780
2781
		    /* In a non-quoted paragraph, we copy the indent
		     * only if AUTOINDENT is turned on. */
2782
		    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
2783
#ifndef NANO_SMALL
2784
			&& !ISSET(AUTOINDENT)
2785
#endif
2786
			)
2787
			    indent_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2788
2789
2790
2791
		    current->next->data = charalloc(indent_len +
			line_len - break_pos);
		    strncpy(current->next->data, current->data,
			indent_len);
2792
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2793
			current->data + break_pos + 1);
2794
		    assert(strlen(current->next->data) ==
Chris Allegretta's avatar
Chris Allegretta committed
2795
			indent_len + line_len - break_pos - 1);
2796
2797
2798
2799
2800
		    totlines++;
		    totsize += indent_len;
		    par_len++;
		} else {
		    size_t next_line_len = strlen(current->next->data);
Chris Allegretta's avatar
Chris Allegretta committed
2801

2802
		    indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2803
			indent_length(current->next->data + quote_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2804
2805
2806
		    current->next->data =
			charealloc(current->next->data, next_line_len +
			line_len - break_pos + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2807

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2808
2809
2810
		    charmove(current->next->data + indent_len +
			line_len - break_pos, current->next->data +
			indent_len, next_line_len - indent_len + 1);
2811
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2812
			current->data + break_pos + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2813
2814
		    current->next->data[indent_len + line_len -
			break_pos - 1] = ' ';
Chris Allegretta's avatar
Chris Allegretta committed
2815
#ifndef NANO_SMALL
2816
2817
2818
2819
2820
		    if (mark_beginbuf == current->next) {
			if (mark_beginx < indent_len)
			    mark_beginx = indent_len;
			mark_beginx += line_len - break_pos;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2821
#endif
2822
		}
Chris Allegretta's avatar
Chris Allegretta committed
2823
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2824
2825
		if (mark_beginbuf == current &&
			mark_beginx > break_pos) {
2826
2827
2828
		    mark_beginbuf = current->next;
		    mark_beginx -= break_pos + 1 - indent_len;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2829
#endif
2830
		null_at(&current->data, break_pos);
2831
2832

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

2837
		indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2838
			indent_length(current->next->data + quote_len);
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
		/* If we can't pull a word from the next line up to this
		 * one, just go on. */
		if (!breakable(current->next->data + indent_len,
			fill - display_len - 1))
		    goto continue_loc;

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

		current->data = charealloc(current->data,
			line_len + break_pos + 2);
		current->data[line_len] = ' ';
		strncpy(current->data + line_len + 1,
Chris Allegretta's avatar
Chris Allegretta committed
2853
			current->next->data + indent_len, break_pos);
2854
		current->data[line_len + break_pos + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2855
#ifndef NANO_SMALL
2856
2857
		if (mark_beginbuf == current->next) {
		    if (mark_beginx < indent_len + break_pos) {
2858
			mark_beginbuf = current;
2859
2860
2861
			if (mark_beginx <= indent_len)
			    mark_beginx = line_len + 1;
			else
2862
2863
			    mark_beginx = line_len + 1 + mark_beginx -
				indent_len;
2864
2865
2866
		    } else
			mark_beginx -= break_pos + 1;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2867
#endif
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
		next_line_len = strlen(current->next->data);
		if (indent_len + break_pos == next_line_len) {
		    filestruct *line = current->next;

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

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

2882
2883
		    /* Don't go to the next line.  Accordingly, don't
		     * allow changing the spacing at the end of the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2884
2885
		     * previous justified line, so that we don't end up
		     * doing it more than once on the same line. */
2886
		    allow_respacing = FALSE;
2887
2888
		} else {
		    charmove(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2889
2890
			current->next->data + indent_len + break_pos + 1,
			next_line_len - break_pos - indent_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2891
2892
		    null_at(&current->next->data, next_line_len -
			break_pos);
2893
2894

		    /* Go to the next line. */
2895
2896
2897
		    current = current->next;
		}
	    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2898
  continue_loc:
2899
		/* Go to the next line. */
2900
		current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
2901

2902
	    /* We've moved to the next line after justifying the
2903
2904
2905
2906
2907
2908
2909
	     * current line.  If the justified line was not the last
	     * line of the paragraph, add a space to the end of it to
	     * replace the one removed or left out by justify_format().
	     * If it was the last line of the paragraph, and
	     * justify_format() left a space on the end of it, remove
	     * the space. */
	    if (allow_respacing) {
2910
		size_t prev_line_len = strlen(current->prev->data);
2911
2912
2913

		if (par_len > 1) {
		    current->prev->data = charealloc(current->prev->data,
2914
			prev_line_len + 2);
2915
2916
2917
2918
2919
		    current->prev->data[prev_line_len] = ' ';
		    current->prev->data[prev_line_len + 1] = '\0';
		    totsize++;
		} else if (par_len == 1 &&
			current->prev->data[prev_line_len - 1] == ' ') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2920
2921
		    current->prev->data =
			charealloc(current->prev->data, prev_line_len);
2922
2923
2924
		    current->prev->data[prev_line_len - 1] = '\0';
		    totsize--;
		}
2925
	    }
2926
2927
	}

2928
2929
2930
	/* 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. */
2931
2932
2933
2934
	if (!full_justify)
	    break;

    } /* while (TRUE) */
2935

2936
2937
2938
    /* 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
2939
2940
2941
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
2942
    last_par_line = current;
2943
2944
2945
2946
2947
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
2948

2949
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
2950

2951
    statusbar(_("Can now UnJustify!"));
2952

2953
    /* Display the shortcut list with UnJustify. */
2954
    shortcut_init(TRUE);
2955
    display_main_list();
2956

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2957
2958
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
2959
    kbinput = do_input(&meta_key, &func_key, &s_or_t, FALSE);
2960

2961
2962
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
2963
	/* Restore the justify we just did (ungrateful user!). */
Chris Allegretta's avatar
Chris Allegretta committed
2964
2965
2966
2967
2968
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

2969
	/* Splice the justify buffer back into the file, but only if we
2970
2971
	 * actually justified something. */
	if (first_par_line != NULL) {
2972
	    filestruct *bot_save;
2973

2974
2975
2976
2977
	    /* 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);
2978

2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
	    /* 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
3000
#ifndef NANO_SMALL
3001
3002
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
3003
#endif
3004
3005
3006
3007
3008
3009
3010
3011
3012
	    flags = flags_save;

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

	    if (!ISSET(MODIFIED))
		titlebar(NULL);
	    edit_refresh();
	}
3013
    } else {
3014
	unget_kbinput(kbinput, meta_key, func_key);
3015

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3016
	/* Blow away the text in the justify buffer. */
3017
3018
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
3019
    }
3020

3021
    blank_statusbar();
3022

3023
    /* Display the shortcut list with UnCut. */
3024
    shortcut_init(FALSE);
3025
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
3026
}
3027

3028
void do_justify_void(void)
3029
{
3030
    do_justify(FALSE);
3031
3032
}

3033
void do_full_justify(void)
3034
{
3035
    do_justify(TRUE);
3036
}
3037
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
3038

3039
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
3040
{
3041
3042
    int i;

3043
3044
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
3045
    else if (ISSET(TEMP_FILE))
3046
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3047
    else
3048
3049
3050
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

3051
3052
#ifdef DEBUG
    dump_buffer(fileage);
3053
#endif
3054

3055
    if (i == 0 || (i == 1 && do_writeout(TRUE) > 0)) {
3056
#ifdef ENABLE_MULTIBUFFER
3057
	/* Exit only if there are no more open file buffers. */
3058
	if (!close_open_file())
3059
#endif
3060
	    finish();
3061
    } else if (i != 1)
3062
3063
3064
3065
3066
3067
3068
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
3069
3070
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
3071
3072
3073
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
3074
    sigaction(SIGQUIT, &act, NULL);
3075

3076
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
3077
    act.sa_handler = handle_hupterm;
3078
    sigaction(SIGHUP, &act, NULL);
3079
    sigaction(SIGTERM, &act, NULL);
3080

3081
#ifndef NANO_SMALL
3082
    /* Trap SIGWINCH because we want to handle window resizes. */
3083
3084
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
3085
    allow_pending_sigwinch(FALSE);
3086
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3087

3088
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
3089
3090
3091
3092
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
3093
3094
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
3095
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
3096

3097
3098
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
3099

3100
3101
3102
3103
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
3104

3105
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
3106
RETSIGTYPE handle_hupterm(int signal)
3107
{
3108
    die(_("Received SIGHUP or SIGTERM\n"));
3109
}
3110

3111
/* Handler for SIGTSTP (suspend). */
3112
3113
3114
RETSIGTYPE do_suspend(int signal)
{
    endwin();
3115
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
3116
    fflush(stdout);
3117

3118
    /* Restore the old terminal settings. */
3119
    tcsetattr(0, TCSANOW, &oldterm);
3120

3121
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3122
     * suspended. */
3123
3124
3125
3126
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3127
    /* Do what mutt does: send ourselves a SIGSTOP. */
3128
3129
    kill(0, SIGSTOP);
}
3130

3131
/* Handler for SIGCONT (continue after suspend). */
3132
3133
RETSIGTYPE do_cont(int signal)
{
3134
#ifndef NANO_SMALL
3135
3136
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3137
    handle_sigwinch(0);
3138
#else
3139
3140
    /* Just update the screen. */
    doupdate();
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
#endif
}

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

3152
    if (tty == NULL)
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
	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;
3168
3169
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
3170
3171
3172
3173
3174
3175
	die_too_small();

#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
3176
3177
    if (fill < 0)
	fill = 0;
3178
3179
#endif

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3180
    hblank = charealloc(hblank, COLS + 1);
3181
3182
3183
    memset(hblank, ' ', COLS);
    hblank[COLS] = '\0';

3184
3185
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
3186
	unpartition_filestruct(&filepart);
3187

3188
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3189
3190
    /* If the justify buffer isn't empty, blow away the text in it and
     * display the shortcut list with UnCut. */
3191
3192
3193
3194
3195
3196
3197
    if (jusbuffer != NULL) {
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
	shortcut_init(FALSE);
    }
#endif

3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
#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
3211

3212
3213
3214
    /* Restore the terminal to its previous state. */
    terminal_init();

3215
3216
3217
3218
3219
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

3220
    /* Redraw the contents of the windows that need it. */
3221
    blank_statusbar();
3222
    display_main_list();
3223
3224
    total_refresh();

3225
    /* Turn cursor back on for sure. */
3226
3227
    curs_set(1);

3228
3229
3230
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3231
    /* Jump back to the main loop. */
3232
3233
    siglongjmp(jmpbuf, 1);
}
3234

3235
void allow_pending_sigwinch(bool allow)
3236
3237
3238
3239
3240
3241
3242
3243
3244
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3245
#endif /* !NANO_SMALL */
3246

3247
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3248
void do_toggle(const toggle *which)
3249
{
3250
    bool enabled;
3251

3252
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3253

3254
    switch (which->val) {
3255
3256
3257
	case TOGGLE_SUSPEND_KEY:
	    signal_init();
	    break;
3258
#ifndef DISABLE_MOUSE
3259
3260
3261
	case TOGGLE_MOUSE_KEY:
	    mouse_init();
	    break;
3262
#endif
3263
3264
3265
3266
3267
3268
3269
3270
	case TOGGLE_NOHELP_KEY:
	    blank_statusbar();
	    blank_bottombars();
	    wrefresh(bottomwin);
	    window_init();
	    edit_refresh();
	    display_main_list();
	    break;
3271
#ifdef ENABLE_COLOR
3272
3273
3274
	case TOGGLE_SYNTAX_KEY:
	    edit_refresh();
	    break;
3275
3276
#endif
#ifdef ENABLE_NANORC
3277
	case TOGGLE_WHITESPACE_KEY:
3278
	    titlebar(NULL);
3279
3280
	    edit_refresh();
	    break;
3281
#endif
3282
    }
Chris Allegretta's avatar
Chris Allegretta committed
3283

Chris Allegretta's avatar
Chris Allegretta committed
3284
3285
3286
3287
3288
3289
3290
    /* We are assuming here that shortcut_init() above didn't free and
     * reallocate the toggles. */
    enabled = ISSET(which->flag);
    if (which->val == TOGGLE_NOHELP_KEY || which->val == TOGGLE_WRAP_KEY)
	enabled = !enabled;
    statusbar("%s %s", which->desc,
		enabled ? _("enabled") : _("disabled"));
Chris Allegretta's avatar
Chris Allegretta committed
3291
}
3292
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3293

3294
3295
3296
3297
3298
3299
3300
3301
3302
void disable_extended_input(void)
{
    struct termios term;

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

3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
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);
}

3341
3342
3343
3344
/* 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
3345
3346
3347
3348
 * echoing of characters as they're typed.  Finally, disable extended
 * input processing, disable interpretation of the special control keys,
 * and if we're not in preserve mode, disable interpretation of the flow
 * control characters too. */
3349
3350
3351
3352
3353
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3354
    disable_extended_input();
3355
3356
3357
3358
3359
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
	allow_funcs)
{
    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;

    /* 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. */
    if (allow_funcs && func_key && input == KEY_MOUSE) {
	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) {
	/* If we got a character, and it isn't a shortcut, toggle, or
	 * control character, 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 && !is_cntrl_char(input)) {
	    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
		 * once. */
		do_output(kbinput, kbinput_len);

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

	if (have_shortcut) {
	    switch (input) {
		/* Handle the "universal" edit window shortcuts. */
		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
		/* Handle the normal edit window shortcuts. */
		default:
		    /* Blow away the text in the cutbuffer if we aren't
		     * cutting text. */
		    if (s->func != do_cut_text)
			cutbuffer_reset();

		    /* Run the function associated with this shortcut,
		     * if there is one. */
		if (s->func != NULL) {
		    if (ISSET(VIEW_MODE) && !s->viewok)
			print_view_warning();
		    else
			s->func();
		    }
		    break;
	    }
	}
#ifndef NANO_SMALL
	else if (have_toggle) {
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
	    /* Toggle the flag associated with this shortcut. */
	    if (allow_funcs)
		do_toggle(t);
	}
#endif
	else
	    /* Blow away the text in the cutbuffer, since we aren't
	     * cutting text. */
	    cutbuffer_reset();
    }

    return input;
}

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

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

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

	    /* Subtract out the size of topwin.  Perhaps we need a
	     * constant somewhere? */
	    mouse_y -= 2;

	    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();
	}
    }
    /* FIXME: If we clicked on a location in the statusbar, the cursor
     * should move to the location we clicked on.  This functionality
     * should be in do_statusbar_mouse() when it's written. */

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

/* The user typed kbinput_len characters.  Add them to the edit
 * buffer. */
void do_output(int *kbinput, size_t kbinput_len)
{
    size_t i, current_len = strlen(current->data);
    bool old_constupdate = ISSET(CONSTUPDATE);
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

    char key[
#ifdef NANO_WIDE
	MB_LEN_MAX
#else
	1
#endif
	];		/* The current character we have. */
    int key_len;	/* The length of the current character. */

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

3581
3582
    /* Turn off constant cursor position display. */
    UNSET(CONSTUPDATE);
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592

    for (i = 0; i < kbinput_len; i++) {
#ifdef NANO_WIDE
	/* Change the wide character to its multibyte value.  If it's
	 * invalid, go on to the next character. */
	if (!ISSET(NO_UTF8)) {
	    key_len = wctomb(key, kbinput[i]);

	    if (key_len == -1)
		continue;
3593
	/* Interpret the character as a single-byte sequence. */
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
	} else {
#endif
	    key_len = 1;
	    key[0] = (char)kbinput[i];
#ifdef NANO_WIDE
	}
#endif

	/* Null to newline, if needed. */
	if (key[0] == '\0' && key_len == 1)
	    key[0] = '\n';
	/* Newline to Enter, if needed. */
	else if (key[0] == '\n' && key_len == 1) {
	    do_enter();
	    continue;
	}

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

	/* More dangerousness fun =) */
	current->data = charealloc(current->data,
		current_len + key_len + 1);
	assert(current_x <= current_len);
	charmove(&current->data[current_x + key_len],
		&current->data[current_x],
		current_len - current_x + key_len);
	charcpy(&current->data[current_x], key, key_len);
	current_len += key_len;
	/* FIXME: Should totsize be the number of single-byte characters
	 * or the number of multibyte characters?  Assume for former for
	 * now. */
	totsize += key_len;
	set_modified();

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

	{
	    int j;
	    for (j = 0; j < key_len; j++)
		do_right(FALSE);
	}

#ifndef DISABLE_WRAPPING
	/* If we're wrapping text, we need to call edit_refresh(). */
	if (!ISSET(NO_WRAP) && (key[0] != '\t' || key_len != 1)) {
	    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(). */
	if (ISSET(COLOR_SYNTAX))
	    do_refresh = TRUE;
#endif
    }

3665
3666
    /* Turn constant cursor position display back on if it was on
     * before. */
3667
3668
3669
3670
3671
3672
3673
3674
3675
    if (old_constupdate)
	SET(CONSTUPDATE);

    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
}

3676
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
3677
3678
{
    int optchr;
3679
3680
    int startline = 0;
	/* Line to try and start at. */
3681
#ifndef DISABLE_WRAPJUSTIFY
3682
3683
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
3684
#endif
3685
3686
3687
3688
3689
#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
3690
#ifdef HAVE_GETOPT_LONG
3691
    const struct option long_options[] = {
3692
3693
3694
	{"help", 0, 0, 'h'},
#ifdef ENABLE_MULTIBUFFER
	{"multibuffer", 0, 0, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
3695
3696
#endif
#ifdef ENABLE_NANORC
3697
#ifndef NANO_SMALL
3698
	{"historylog", 0, 0, 'H'},
3699
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3700
	{"ignorercfiles", 0, 0, 'I'},
3701
#endif
3702
3703
3704
#ifdef NANO_WIDE
	{"noutf8", 0, 0, 'O'},
#endif
3705
3706
3707
#ifndef DISABLE_JUSTIFY
	{"quotestr", 1, 0, 'Q'},
#endif
3708
#ifdef HAVE_REGEX_H
3709
	{"regexp", 0, 0, 'R'},
3710
#endif
3711
	{"tabsize", 1, 0, 'T'},
Chris Allegretta's avatar
Chris Allegretta committed
3712
	{"version", 0, 0, 'V'},
3713
3714
#ifdef ENABLE_COLOR
	{"syntax", 1, 0, 'Y'},
3715
#endif
3716
	{"const", 0, 0, 'c'},
3717
	{"rebinddelete", 0, 0, 'd'},
3718
	{"nofollow", 0, 0, 'l'},
3719
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3720
	{"mouse", 0, 0, 'm'},
3721
#endif
3722
3723
3724
#ifndef DISABLE_OPERATINGDIR
	{"operatingdir", 1, 0, 'o'},
#endif
3725
	{"preserve", 0, 0, 'p'},
3726
3727
3728
3729
3730
#ifndef DISABLE_WRAPJUSTIFY
	{"fill", 1, 0, 'r'},
#endif
#ifndef DISABLE_SPELLER
	{"speller", 1, 0, 's'},
3731
#endif
3732
3733
	{"tempfile", 0, 0, 't'},
	{"view", 0, 0, 'v'},
3734
#ifndef DISABLE_WRAPPING
3735
	{"nowrap", 0, 0, 'w'},
3736
#endif
3737
3738
	{"nohelp", 0, 0, 'x'},
	{"suspend", 0, 0, 'z'},
3739
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3740
	{"smarthome", 0, 0, 'A'},
3741
	{"backup", 0, 0, 'B'},
3742
	{"backupdir", 1, 0, 'E'},
3743
	{"noconvert", 0, 0, 'N'},
3744
	{"smooth", 0, 0, 'S'},
3745
	{"restricted", 0, 0, 'Z'},
3746
3747
	{"autoindent", 0, 0, 'i'},
	{"cut", 0, 0, 'k'},
3748
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3749
3750
3751
3752
3753
	{0, 0, 0, 0}
    };
#endif

    setlocale(LC_ALL, "");
3754
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
3755
3756
3757
3758
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3759
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3760
3761
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
3762
    if (geteuid() == NANO_ROOT_UID)
3763
3764
	SET(NO_WRAP);
#endif
3765

3766
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
3767
#ifdef HAVE_GETOPT_LONG
3768
	getopt_long(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz", long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
3769
#else
3770
	getopt(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
3771
#endif
3772
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3773
3774

	switch (optchr) {
3775
3776
3777
3778
3779
3780
3781
3782
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
3783
#ifndef NANO_SMALL
3784
3785
3786
3787
3788
3789
3790
3791
3792
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
	    case 'E':
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
3793
#endif
3794
#ifdef ENABLE_MULTIBUFFER
3795
3796
3797
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
3798
3799
#endif
#ifdef ENABLE_NANORC
3800
#ifndef NANO_SMALL
3801
3802
3803
	    case 'H':
		SET(HISTORYLOG);
		break;
3804
#endif
3805
3806
3807
	    case 'I':
		SET(NO_RCFILE);
		break;
3808
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3809
#ifndef NANO_SMALL
3810
3811
3812
	    case 'N':
		SET(NO_CONVERT);
		break;
3813
#endif
3814
3815
3816
	    case 'O':
		SET(NO_UTF8);
		break;
3817
#ifndef DISABLE_JUSTIFY
3818
3819
3820
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
3821
#endif
3822
#ifdef HAVE_REGEX_H
3823
3824
3825
	    case 'R':
		SET(USE_REGEXP);
		break;
3826
3827
#endif
#ifndef NANO_SMALL
3828
3829
3830
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
3831
#endif
3832
3833
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
3834
3835
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3836
3837
3838
3839
3840
3841
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
3842
#ifdef ENABLE_COLOR
3843
3844
3845
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
3846
#endif
3847
3848
3849
3850
3851
3852
3853
3854
3855
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
3856
#ifndef NANO_SMALL
3857
3858
3859
3860
3861
3862
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
3863
#endif
3864
3865
3866
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
3867
#ifndef DISABLE_MOUSE
3868
3869
3870
	    case 'm':
		SET(USE_MOUSE);
		break;
3871
#endif
3872
#ifndef DISABLE_OPERATINGDIR
3873
3874
3875
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
3876
#endif
3877
3878
3879
	    case 'p':
		SET(PRESERVE);
		break;
3880
#ifndef DISABLE_WRAPJUSTIFY
3881
3882
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
3883
3884
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3885
3886
3887
3888
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
3889
#endif
3890
#ifndef DISABLE_SPELLER
3891
3892
3893
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
3894
#endif
3895
3896
3897
3898
3899
3900
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
3901
#ifndef DISABLE_WRAPPING
3902
3903
3904
	    case 'w':
		SET(NO_WRAP);
		break;
3905
#endif
3906
3907
3908
3909
3910
3911
3912
3913
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
3914
3915
3916
	}
    }

3917
3918
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
3919
3920
3921
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

3922
3923
3924
    /* 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. */
3925
3926
3927
3928
3929
3930
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
3931
/* We've read through the command line options.  Now back up the flags
3932
3933
 * 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
3934
3935
3936
3937
3938
#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
3939
#ifndef DISABLE_WRAPJUSTIFY
3940
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
3941
#endif
3942
3943
3944
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3945
3946
3947
3948
3949
3950
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
3951
	ssize_t tabsize_cpy = tabsize;
Chris Allegretta's avatar
Chris Allegretta committed
3952
3953
	long flags_cpy = flags;

3954
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3955
	operating_dir = NULL;
3956
#endif
3957
3958
3959
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
3960
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3961
	quotestr = NULL;
3962
3963
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
3964
	alt_speller = NULL;
3965
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3966
3967
3968
3969
3970
3971
3972
3973
3974

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
3975
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3976
3977
3978
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
3979
3980
3981
3982
3983
3984
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
#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
3997
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
3998
3999
4000
4001
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4002
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
4003
4004
4005
4006
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

4007
4008
4009
4010
4011
4012
4013
4014
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4015
#ifndef NANO_SMALL
4016
    /* Set up the backup directory (unless we're using restricted mode,
4017
4018
4019
4020
     * 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. */
4021
4022
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4023
4024
#endif

4025
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4026
    /* Set up the operating directory.  This entails chdir()ing there,
4027
     * so that file reads and writes will be based there. */
4028
4029
4030
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4031
#ifndef DISABLE_JUSTIFY
4032
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4033
	punct = mallocstrcpy(punct, ".?!");
4034
4035

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

Chris Allegretta's avatar
Chris Allegretta committed
4038
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4039
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
4040
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4041
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
4042
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4043
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
4044
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4045
		);
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
#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
4062
#endif /* !DISABLE_JUSTIFY */
4063

4064
4065
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
4066
     * command line and/or rcfile(s), check $SPELL for one, as Pico
4067
     * does (unless we're using restricted mode, in which case spell
4068
4069
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
4070
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
4071
4072
4073
4074
4075
4076
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

4077
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4078
    /* If whitespace wasn't specified, set its default value. */
4079
4080
4081
4082
    if (whitespace == NULL)
	whitespace = mallocstrcpy(NULL, "  ");
#endif

4083
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4084
    if (tabsize == -1)
4085
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4086

4087
    /* Back up the old terminal settings so that they can be restored. */
4088
    tcgetattr(0, &oldterm);
4089

4090
4091
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4092
    initscr();
4093
    terminal_init();
4094

4095
    /* Set up the global variables and the shortcuts. */
4096
4097
    global_init(FALSE);
    shortcut_init(FALSE);
4098
4099

    /* Set up the signal handlers. */
4100
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4101
4102

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

4106
    window_init();
4107
#ifndef DISABLE_MOUSE
4108
    mouse_init();
4109
#endif
4110

Chris Allegretta's avatar
Chris Allegretta committed
4111
#ifdef DEBUG
4112
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4113
#endif
4114

4115
4116
4117
4118
4119
4120
4121
4122
    /* If there's a +LINE flag here, it is the first non-option
     * argument, and it is followed by at least one other argument, the
     * filename it applies to. */
    if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') {
	startline = atoi(&argv[optind][1]);
	optind++;
    }

Chris Allegretta's avatar
Chris Allegretta committed
4123
#ifdef ENABLE_MULTIBUFFER
4124
4125
4126
4127
4128
4129
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

    /* Read all the files after the first one on the command line into
     * new buffers. */
    {
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
	int i = optind + 1, iline = 0;
	for (; i < argc; i++) {
	    /* If there's a +LINE flag here, it is followed by at least
	     * one other argument, the filename it applies to. */
	    if (i < argc - 1 && argv[i][0] == '+' && iline == 0) {
		iline = atoi(&argv[i][1]);
	    } else {
		load_buffer(argv[i]);
		if (iline > 0) {
		    do_gotoline(iline, FALSE);
		    iline = 0;
		}
	    }
	}
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
    }
#endif

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

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

	/* Add this new entry to the open_files structure if we have
        * multibuffer support, or to the main filestruct if we don't. */
	load_file();
Chris Allegretta's avatar
Chris Allegretta committed
4165
    }
4166
4167
4168
4169

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

4172
4173
4174
4175
4176
4177
4178
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

Chris Allegretta's avatar
Chris Allegretta committed
4179
    if (startline > 0)
4180
	do_gotoline(startline, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4181

4182
4183
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4184
    sigsetjmp(jmpbuf, 1);
4185
#endif
4186

Robert Siemborski's avatar
Robert Siemborski committed
4187
4188
    edit_refresh();

4189
    while (TRUE) {
4190
4191
4192
4193
4194
4195
4196
4197
	bool meta_key;
		/* Whether we got a meta key sequence. */
	bool func_key;
		/* Whether we got a function key. */
	bool s_or_t;
		/* Whether we got a shortcut or toggle. */

	/* Make sure the cursor is in the edit window. */
4198
	reset_cursor();
4199
4200
4201

	/* If constant cursor position display is on, display the cursor
	 * position. */
4202
	if (ISSET(CONSTUPDATE))
4203
	    do_cursorpos(TRUE);
4204

4205
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
4206
	currshortcut = main_list;
4207
#endif
4208

4209
4210
	/* Read in and interpret characters. */
	do_input(&meta_key, &func_key, &s_or_t, TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4211
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4212
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4213
}