nano.c 118 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   nano.c                                                               *
 *                                                                        *
5
 *   Copyright (C) 1999-2005 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
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
#include "proto.h"
#include "nano.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

118
119
120
    endwin();
    curses_ended = TRUE;

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

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

128
    /* Save the current file buffer if it's been modified. */
129
130
    if (ISSET(MODIFIED))
	die_save_file(filename);
131

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

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

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

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

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

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

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

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

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

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

184
/* Die with an error message that the screen was too small if, well, the
Chris Allegretta's avatar
Chris Allegretta committed
185
 * screen is too small. */
186
void check_die_too_small(void)
187
{
188
189
190
    editwinrows = LINES - 5 + no_more_space() + no_help();
    if (editwinrows < MIN_EDITOR_ROWS)
	die(_("Window size is too small for nano...\n"));
191
192
}

193
194
195
/* Reassign variables that depend on the window size.  That is, fill and
 * hblank. */
void resize_variables(void)
Chris Allegretta's avatar
Chris Allegretta committed
196
{
197
198
199
200
201
202
203
204
205
206
207
#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
    if (fill < 0)
	fill = 0;
#endif

    hblank = charealloc(hblank, COLS + 1);
    memset(hblank, ' ', COLS);
    hblank[COLS] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
208
209
}

210
/* Initialize global variables -- no better way for now.  If
211
212
 * save_cutbuffer is TRUE, don't set cutbuffer to NULL. */
void global_init(bool save_cutbuffer)
Chris Allegretta's avatar
Chris Allegretta committed
213
{
214
215
    check_die_too_small();
    resize_variables();
216

Chris Allegretta's avatar
Chris Allegretta committed
217
    fileage = NULL;
218
219
    edittop = NULL;
    current = NULL;
220
221
    if (!save_cutbuffer)
	cutbuffer = NULL;
222
223
224
    current_x = 0;
    placewewant = 0;
    current_y = 0;
Chris Allegretta's avatar
Chris Allegretta committed
225
    totlines = 0;
226
    totsize = 0;
Chris Allegretta's avatar
Chris Allegretta committed
227
228
}

229
230
void window_init(void)
{
231
    check_die_too_small();
232

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

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

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
	  "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 "
379
380
381
	  "will enter the character with the corresponding value.  The "
	  "following keystrokes are available in the main editor window.  "
	  "Alternative keys are shown in parentheses:\n\n");
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
/* 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;
704
    size_t part_totsize;
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
    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;
803
    size_t part_totsize;
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
    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
    print1opt("-O", "--morespace", N_("Use more space for editing"));
933
#ifndef DISABLE_JUSTIFY
934
    print1opt(_("-Q [str]"), _("--quotestr=[str]"), N_("Quoting string, default \"> \""));
935
#endif
936
#ifdef HAVE_REGEX_H
937
    print1opt("-R", "--regexp", N_("Do regular expression searches"));
938
#endif
939
#ifndef NANO_SMALL
940
    print1opt("-S", "--smooth", N_("Smooth scrolling"));
941
#endif
942
943
    print1opt(_("-T [#cols]"), _("--tabsize=[#cols]"), N_("Set width of a tab in cols to #cols"));
    print1opt("-V", "--version", N_("Print version information and exit"));
944
#ifdef ENABLE_COLOR
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
945
    print1opt(_("-Y [str]"), _("--syntax=[str]"), N_("Syntax definition to use"));
946
#endif
947
948
    print1opt("-Z", "--restricted", N_("Restricted mode"));
    print1opt("-c", "--const", N_("Constantly show cursor position"));
949
#ifndef NANO_SMALL
950
951
952
    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"));
953
#endif
954
    print1opt("-l", "--nofollow", N_("Don't follow symbolic links, overwrite"));
955
#ifndef DISABLE_MOUSE
956
    print1opt("-m", "--mouse", N_("Enable mouse"));
Chris Allegretta's avatar
Chris Allegretta committed
957
#endif
958
#ifndef DISABLE_OPERATINGDIR
959
    print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), N_("Set operating directory"));
Chris Allegretta's avatar
Chris Allegretta committed
960
#endif
961
    print1opt("-p", "--preserve", N_("Preserve XON (^Q) and XOFF (^S) keys"));
962
#ifndef DISABLE_WRAPJUSTIFY
963
    print1opt(_("-r [#cols]"), _("--fill=[#cols]"), N_("Set fill cols to (wrap lines at) #cols"));
964
#endif
965
#ifndef DISABLE_SPELLER
966
    print1opt(_("-s [prog]"), _("--speller=[prog]"), N_("Enable alternate speller"));
967
#endif
968
969
    print1opt("-t", "--tempfile", N_("Auto save on exit, don't prompt"));
    print1opt("-v", "--view", N_("View (read only) mode"));
970
#ifndef DISABLE_WRAPPING
971
    print1opt("-w", "--nowrap", N_("Don't wrap long lines"));
Chris Allegretta's avatar
Chris Allegretta committed
972
#endif
973
974
    print1opt("-x", "--nohelp", N_("Don't show help window"));
    print1opt("-z", "--suspend", N_("Enable suspend"));
Chris Allegretta's avatar
Chris Allegretta committed
975

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

Chris Allegretta's avatar
Chris Allegretta committed
979
980
981
    exit(0);
}

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

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

1045
1046
1047
1048
1049
int no_more_space(void)
{
    return ISSET(MORE_SPACE) ? 1 : 0;
}

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

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

Chris Allegretta's avatar
Chris Allegretta committed
1060
#ifndef NANO_SMALL
1061
void cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
1062
{
1063
1064
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
1065
1066
}

1067
1068
/* Return TRUE on success. */
bool open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
1069
{
1070
1071
1072
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
1073
1074
			/* Original and temporary handlers for
			 * SIGINT. */
1075
1076
1077
    bool sig_failed = FALSE;
    /* sig_failed means that sigaction() failed without changing the
     * signal handlers.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1078
     *
1079
1080
     * 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
1081

1082
    /* Make our pipes. */
1083

1084
1085
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
1086
	return FALSE;
1087
    }
1088

1089
    /* Fork a child. */
1090

1091
1092
1093
1094
1095
    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. */
1096

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1097
	execl("/bin/sh", "sh", "-c", command, NULL);
1098
1099
	exit(0);
    }
1100

1101
1102
1103
1104
1105
1106
1107
    /* Else continue as parent. */

    close(fd[1]);

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

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

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

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

1131
    f = fdopen(fd[0], "rb");
1132
    if (f == NULL)
1133
	nperror("fdopen");
1134

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

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

1144
    if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
1145
1146
	nperror("sigaction");

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

1151
    return TRUE;
1152
}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1153
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1154

1155
void do_verbatim_input(void)
1156
{
1157
1158
1159
    int *kbinput;
    size_t kbinput_len, i;
    char *output;
1160
1161

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

1163
1164
    /* Read in all the verbatim characters. */
    kbinput = get_verbatim_kbinput(edit, &kbinput_len);
1165

1166
    /* Display all the verbatim characters at once. */
1167
    output = charalloc(kbinput_len + 1);
1168

1169
1170
1171
1172
1173
1174
1175
    for (i = 0; i < kbinput_len; i++)
	output[i] = (char)kbinput[i];
    output[i] = '\0';

    do_output(output, kbinput_len);

    free(output);
1176
1177
}

1178
void do_backspace(void)
Chris Allegretta's avatar
Chris Allegretta committed
1179
{
1180
    if (current != fileage || current_x > 0) {
1181
	do_left(FALSE);
1182
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1183
1184
1185
    }
}

1186
void do_delete(void)
Chris Allegretta's avatar
Chris Allegretta committed
1187
{
1188
    bool do_refresh = FALSE;
1189
1190
1191
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

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

1195
    placewewant = xplustabs();
1196

1197
    if (current->data[current_x] != '\0') {
1198
1199
	int char_buf_len = parse_mbchar(current->data + current_x, NULL,
		NULL, NULL);
1200
	size_t line_len = strlen(current->data + current_x);
1201

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

1204
	/* Let's get dangerous. */
1205
	charmove(&current->data[current_x],
1206
1207
		&current->data[current_x + char_buf_len],
		line_len - char_buf_len + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1208

1209
	null_at(&current->data, current_x + line_len - char_buf_len);
1210
1211
#ifndef NANO_SMALL
	if (current_x < mark_beginx && mark_beginbuf == current)
1212
	    mark_beginx -= char_buf_len;
Chris Allegretta's avatar
Chris Allegretta committed
1213
#endif
1214
	totsize--;
1215
1216
    } else if (current != filebot && (current->next != filebot ||
	current->data[0] == '\0')) {
1217
	/* We can delete the line before filebot only if it is blank: it
1218
	 * becomes the new magicline then. */
1219
	filestruct *foo = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1220

1221
	assert(current_x == strlen(current->data));
1222
1223
1224
1225
1226
1227

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

1228
1229
	current->data = charealloc(current->data,
		current_x + strlen(foo->data) + 1);
1230
1231
1232
1233
1234
1235
1236
	strcpy(current->data + current_x, foo->data);
#ifndef NANO_SMALL
	if (mark_beginbuf == current->next) {
	    mark_beginx += current_x;
	    mark_beginbuf = current;
	}
#endif
1237
	if (filebot == foo)
Chris Allegretta's avatar
Chris Allegretta committed
1238
1239
1240
1241
1242
1243
	    filebot = current;

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
1244
	totsize--;
1245
#ifndef DISABLE_WRAPPING
1246
	wrap_reset();
1247
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1248
    } else
1249
	return;
Chris Allegretta's avatar
Chris Allegretta committed
1250
1251

    set_modified();
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263

#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
1264
1265
}

1266
void do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1267
{
1268
1269
1270
    char *kbinput = "\t";

    do_output(kbinput, 1);
Chris Allegretta's avatar
Chris Allegretta committed
1271
1272
}

1273
/* Someone hits return *gasp!* */
1274
void do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1275
{
1276
1277
    filestruct *newnode = make_new_node(current);
    size_t extra = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1278

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1281
#ifndef NANO_SMALL
1282
1283
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
1284
1285
1286
1287
1288
	/* 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)
1289
1290
	    extra = current_x;
	totsize += extra;
1291
1292
1293
1294
1295
1296
1297
    }
#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))
1298
1299
	strncpy(newnode->data, current->data, extra);
#endif
1300
1301
1302
1303
1304
    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;
1305
    }
1306
1307
#endif
    current_x = extra;
Chris Allegretta's avatar
Chris Allegretta committed
1308

1309
    if (current == filebot)
1310
1311
	filebot = newnode;
    splice_node(current, newnode, current->next);
1312

1313
1314
    renumber(current);
    current = newnode;
1315
1316

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

1318
    totlines++;
1319
    totsize++;
1320
1321
    set_modified();
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1322
1323
}

1324
#ifndef NANO_SMALL
1325
/* Move to the next word. */
1326
void do_next_word(void)
Chris Allegretta's avatar
Chris Allegretta committed
1327
{
1328
1329
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1330
    char *char_mb;
1331
    int char_mb_len;
1332

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

1335
1336
    char_mb = charalloc(mb_cur_max());

1337
1338
    /* Move forward until we find the character after the last letter of
     * the current word. */
1339
    while (current->data[current_x] != '\0') {
1340
1341
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1342

1343
1344
	/* If we've found it, stop moving forward through the current
	 * line. */
1345
1346
1347
	if (!is_alnum_mbchar(char_mb))
	    break;

1348
	current_x += char_mb_len;
1349
    }
Chris Allegretta's avatar
Chris Allegretta committed
1350

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

1355
    for (; current != NULL; current = current->next) {
1356
	while (current->data[current_x] != '\0') {
1357
	    char_mb_len = parse_mbchar(current->data + current_x,
1358
		char_mb, NULL, NULL);
1359

1360
1361
	    /* If we've found it, stop moving forward through the
	     * current line. */
1362
1363
1364
	    if (is_alnum_mbchar(char_mb))
		break;

1365
	    current_x += char_mb_len;
1366
	}
Chris Allegretta's avatar
Chris Allegretta committed
1367

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

1373
1374
	current_x = 0;
    }
1375

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1376
1377
    free(char_mb);

1378
1379
    /* If we haven't found it, leave the cursor at the end of the
     * file. */
1380
1381
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1382

1383
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1384

1385
    /* Update the screen. */
1386
    edit_redraw(current_save, pww_save);
1387
}
Chris Allegretta's avatar
Chris Allegretta committed
1388

1389
/* Move to the previous word. */
1390
void do_prev_word(void)
1391
{
1392
1393
    size_t pww_save = placewewant;
    const filestruct *current_save = current;
1394
    char *char_mb;
1395
1396
    int char_mb_len;
    bool begin_line = FALSE;
1397

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

1400
1401
    char_mb = charalloc(mb_cur_max());

1402
1403
1404
    /* Move backward until we find the character before the first letter
     * of the current word. */
    while (!begin_line) {
1405
1406
	char_mb_len = parse_mbchar(current->data + current_x, char_mb,
		NULL, NULL);
1407

1408
1409
1410
1411
	/* If we've found it, stop moving backward through the current
	 * line. */
	if (!is_alnum_mbchar(char_mb))
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1412

1413
1414
1415
1416
1417
1418
1419
1420
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);
    }

    /* Move backward until we find the last letter of the previous
     * word. */
1421
1422
1423
1424
1425
    if (current_x == 0)
	begin_line = TRUE;
    else
	current_x = move_mbleft(current->data, current_x);

1426
    for (; current != NULL; current = current->prev) {
1427
1428
	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1429
		char_mb, NULL, NULL);
1430
1431
1432
1433
1434

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

1436
1437
1438
1439
1440
1441
1442
1443
1444
	    if (current_x == 0)
		begin_line = TRUE;
	    else
		current_x = move_mbleft(current->data, current_x);
	}

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

1447
1448
1449
1450
	if (current->prev != NULL) {
	    begin_line = FALSE;
	    current_x = strlen(current->prev->data);
	}
Chris Allegretta's avatar
Chris Allegretta committed
1451
1452
    }

1453
1454
    /* If we haven't found it, leave the cursor at the beginning of the
     * file. */
1455
    if (current == NULL) {
1456
1457
	current = fileage;
	current_x = 0;
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
    /* If we've found it, move backward until we find the character
     * before the first letter of the previous word. */
    } else if (!begin_line) {
	if (current_x == 0)
	    begin_line = TRUE;
	else
	    current_x = move_mbleft(current->data, current_x);

	while (!begin_line) {
	    char_mb_len = parse_mbchar(current->data + current_x,
1468
		char_mb, NULL, NULL);
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484

	    /* If we've found it, stop moving backward through the
	     * current line. */
	    if (!is_alnum_mbchar(char_mb))
		break;

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

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

1487
1488
    free(char_mb);

1489
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1490

1491
    /* Update the screen. */
1492
    edit_redraw(current_save, pww_save);
1493
}
Chris Allegretta's avatar
Chris Allegretta committed
1494

1495
void do_mark(void)
1496
{
1497
1498
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1499
1500
1501
1502
1503
1504
1505
1506
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
}
1507
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1508

1509
#ifndef DISABLE_WRAPPING
1510
1511
void wrap_reset(void)
{
1512
    same_line_wrap = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1513
}
1514
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1515

1516
#ifndef DISABLE_WRAPPING
1517
1518
1519
/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
1520
bool do_wrap(filestruct *inptr)
Chris Allegretta's avatar
Chris Allegretta committed
1521
{
1522
1523
1524
1525
    size_t len = strlen(inptr->data);
	/* Length of the line we wrap. */
    size_t i = 0;
	/* Generic loop variable. */
1526
    ssize_t wrap_loc = -1;
1527
	/* Index of inptr->data where we wrap. */
1528
    ssize_t word_back = -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1529
#ifndef NANO_SMALL
1530
    const char *indentation = NULL;
1531
	/* Indentation to prepend to the new line. */
1532
    size_t indent_len = 0;	/* strlen(indentation) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1533
#endif
1534
1535
    const char *after_break;	/* Text after the wrap point. */
    size_t after_break_len;	/* strlen(after_break) */
1536
    bool wrapping = FALSE;	/* Do we prepend to the next line? */
1537
    const char *wrap_line = NULL;
1538
	/* The next line, minus indentation. */
1539
1540
1541
    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
1542

1543
1544
/* 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
1545

1546
/* Step 1, finding where to wrap.  We are going to add a new line
1547
 * after a whitespace character.  In this step, we set wrap_loc as the
1548
1549
 * location of this replacement.
 *
1550
 * Where should we break the line?  We need the last legal wrap point
1551
1552
1553
 * 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.
 *
1554
1555
 * A legal wrap point is a whitespace character that is not followed by
 * whitespace.
1556
1557
1558
1559
1560
1561
1562
 *
 * 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!
 *
1563
1564
 * Note that the code below could be optimized, by not calling
 * strnlenpt() so often. */
1565

1566
1567
1568
1569
1570
#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
1571
    for (; i < len; i++, wrap_line++) {
1572
	/* Record where the last word ended. */
1573
	if (!is_blank_char(*wrap_line))
1574
	    word_back = i;
1575
	/* If we have found a legal wrap point and the current word
1576
	 * extends too far, then we stop. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1577
1578
	if (wrap_loc != -1 &&
		strnlenpt(inptr->data, word_back + 1) > fill)
1579
	    break;
1580
	/* We record the latest legal wrap point. */
1581
	if (word_back != i && !is_blank_char(wrap_line[1]))
1582
	    wrap_loc = i;
1583
    }
1584
1585
    if (i == len)
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1586

1587
1588
1589
    /* 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
1590

1591
1592
1593
    /* 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;
1594

1595
    assert(after_break_len == strlen(after_break));
Chris Allegretta's avatar
Chris Allegretta committed
1596

1597
1598
1599
    /* 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
1600

1601
1602
1603
    /* 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. */
1604
    if (same_line_wrap && inptr->next) {
1605
1606
	wrap_line = inptr->next->data;
	wrap_line_len = strlen(wrap_line);
Chris Allegretta's avatar
Chris Allegretta committed
1607

1608
	/* +1 for the space between after_break and wrap_line. */
1609
	if ((new_line_len + 1 + wrap_line_len) <= fill) {
1610
	    wrapping = TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1611
	    new_line_len += 1 + wrap_line_len;
1612
1613
	}
    }
1614

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1615
#ifndef NANO_SMALL
1616
    if (ISSET(AUTOINDENT)) {
1617
1618
	/* Indentation comes from the next line if wrapping, else from
	 * this line. */
1619
	indentation = wrapping ? wrap_line : inptr->data;
1620
1621
	indent_len = indent_length(indentation);
	if (wrapping)
1622
1623
	    /* The wrap_line text should not duplicate indentation.
	     * Note in this case we need not increase new_line_len. */
1624
1625
1626
	    wrap_line += indent_len;
	else
	    new_line_len += indent_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1627
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1628
1629
#endif

1630
1631
    /* Now we allocate the new line and copy into it. */
    newline = charalloc(new_line_len + 1);  /* +1 for \0 */
1632
    new_line_len = 0;
1633
    *newline = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1634

1635
1636
1637
1638
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
	strncpy(newline, indentation, indent_len);
	newline[indent_len] = '\0';
1639
	new_line_len = indent_len;
1640
    }
1641
#endif
1642
    strcat(newline, after_break);
1643
    new_line_len += after_break_len;
1644

1645
1646
    /* We end the old line after wrap_loc.  Note that this does not eat
     * the space. */
1647
1648
    null_at(&inptr->data, wrap_loc + 1);
    totsize++;
1649
    if (wrapping) {
1650
	/* In this case, totsize increases by 1 since we add a space
1651
	 * between after_break and wrap_line.  If the line already ends
1652
1653
	 * in a tab or a space, we don't add a space and decrement
	 * totsize to account for that. */
1654
	if (!is_blank_char(newline[new_line_len - 1]))
1655
1656
1657
	    strcat(newline, " ");
	else
	    totsize--;
1658
1659
1660
1661
1662
	strcat(newline, wrap_line);
	free(inptr->next->data);
	inptr->next->data = newline;
    } else {
	filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct));
1663

1664
1665
	/* In this case, the file size changes by +1 for the new line,
	 * and +indent_len for the new indentation. */
1666
1667
1668
1669
1670
1671
1672
1673
#ifndef NANO_SMALL
	totsize += indent_len;
#endif
	totlines++;
	temp->data = newline;
	temp->prev = inptr;
	temp->next = inptr->next;
	temp->prev->next = temp;
1674

1675
1676
1677
	/* If temp->next is NULL, then temp is the last line of the
	 * file, so we must set filebot. */
	if (temp->next != NULL)
1678
1679
1680
1681
	    temp->next->prev = temp;
	else
	    filebot = temp;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1682

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

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

1689
1690
1691
1692
    /* 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
1693

1694
1695
1696
1697
1698
1699
    /* 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 +
1700
#endif
1701
1702
1703
1704
		wrap_loc + 1;
	wrap_reset();
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1705

1706
#ifndef NANO_SMALL
1707
1708
    /* 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
1709
1710
1711
1712
1713
1714
1715
     * 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 */
1716

1717
    return TRUE;
1718
}
1719
#endif /* !DISABLE_WRAPPING */
1720

1721
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1722
/* A word is misspelled in the file.  Let the user replace it.  We
1723
1724
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1725
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1726
    char *save_search, *save_replace;
1727
    size_t current_x_save = current_x, pww_save = placewewant;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1728
    filestruct *edittop_save = edittop, *current_save = current;
1729
	/* Save where we are. */
1730
    bool canceled = FALSE;
1731
	/* The return value. */
1732
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1733
#ifndef NANO_SMALL
1734
1735
    bool reverse_search_set = ISSET(REVERSE_SEARCH);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1736
#ifdef HAVE_REGEX_H
1737
1738
    bool regexp_set = ISSET(USE_REGEXP);
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1739
1740
#ifndef NANO_SMALL
    bool old_mark_set = ISSET(MARK_ISSET);
1741
1742
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
1743
1744
1745
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
1746
    filestruct *top, *bot;
1747
    size_t top_x, bot_x;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1748
#endif
1749

1750
    /* Make sure spell-check is case sensitive. */
1751
    SET(CASE_SENSITIVE);
1752

1753
#ifndef NANO_SMALL
1754
1755
    /* Make sure spell-check goes forward only. */
    UNSET(REVERSE_SEARCH);
1756
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1757
#ifdef HAVE_REGEX_H
1758
1759
1760
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif
1761

1762
    /* Save the current search/replace strings. */
1763
1764
1765
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1766

1767
    /* Set the search/replace strings to the misspelled word. */
1768
1769
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1770

1771
1772
#ifndef NANO_SMALL
    if (old_mark_set) {
1773
	/* If the mark is on, partition the filestruct so that it
1774
1775
1776
	 * 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. */
1777
	mark_order((const filestruct **)&top, &top_x,
1778
	    (const filestruct **)&bot, &bot_x, &right_side_up);
1779
	filepart = partition_filestruct(top, top_x, bot, bot_x);
1780
	added_magicline = (filebot->data[0] != '\0');
1781
1782
1783
1784
	UNSET(MARK_ISSET);
    }
#endif

1785
    /* Start from the top of the file. */
1786
    edittop = fileage;
1787
    current = fileage;
1788
    current_x = (size_t)-1;
1789
    placewewant = 0;
1790

1791
    /* Find the first whole-word occurrence of word. */
1792
    findnextstr_wrap_reset();
1793
    while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) {
1794
1795
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
1796

1797
	    do_replace_highlight(TRUE, word);
1798

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1799
	    /* Allow all instances of the word to be corrected. */
1800
	    canceled = (statusq(FALSE, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
1801
#ifndef NANO_SMALL
1802
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
1803
#endif
1804
			 _("Edit a replacement")) == -1);
1805

1806
	    do_replace_highlight(FALSE, word);
1807

1808
	    if (!canceled && strcmp(word, answer) != 0) {
1809
		current_x--;
1810
1811
		do_replace_loop(word, current, &current_x, TRUE,
			&canceled);
1812
	    }
1813
1814

	    break;
1815
	}
1816
    }
Chris Allegretta's avatar
Chris Allegretta committed
1817

1818
1819
#ifndef NANO_SMALL
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1820
1821
	/* If the mark was on and we added a magicline, remove it
	 * now. */
1822
1823
	if (added_magicline)
	    remove_magicline();
1824

1825
1826
1827
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
1828
	    bot_x += top_x;
1829
	if (right_side_up) {
1830
1831
	    mark_beginx = top_x;
	    current_x_save = bot_x;
1832
	} else {
1833
1834
	    current_x_save = top_x;
	    mark_beginx = bot_x;
1835
	}
1836

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1837
1838
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
1839
	unpartition_filestruct(&filepart);
1840
	SET(MARK_ISSET);
1841
1842
    }
#endif
1843

1844
1845
1846
1847
1848
    /* 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
1849

1850
    /* Restore where we were. */
1851
    edittop = edittop_save;
1852
1853
    current = current_save;
    current_x = current_x_save;
1854
    placewewant = pww_save;
Chris Allegretta's avatar
Chris Allegretta committed
1855

1856
    /* Restore case sensitivity setting. */
1857
1858
1859
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

1860
#ifndef NANO_SMALL
1861
1862
1863
    /* Restore search/replace direction. */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
1864
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1865
#ifdef HAVE_REGEX_H
1866
1867
1868
1869
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
1870

1871
    return !canceled;
Chris Allegretta's avatar
Chris Allegretta committed
1872
1873
}

1874
1875
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
1876
const char *do_int_speller(const char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
1877
{
1878
1879
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1880
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1881
1882
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
1883

1884
    /* Create all three pipes up front. */
1885
1886
    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
1887

1888
    statusbar(_("Creating misspelled word list, please wait..."));
1889

1890
    /* A new process to run spell in. */
1891
    if ((pid_spell = fork()) == 0) {
1892

1893
	/* Child continues (i.e, future spell process). */
1894

1895
	close(spell_fd[0]);
1896

1897
	/* Replace the standard input with the temp file. */
1898
1899
1900
1901
1902
1903
	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;

1904
	close(tempfile_fd);
1905

1906
	/* Send spell's standard output to the pipe. */
1907
1908
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1909

1910
	close(spell_fd[1]);
1911

1912
	/* Start spell program; we are using PATH. */
1913
	execlp("spell", "spell", NULL);
1914

1915
	/* Should not be reached, if spell is found. */
1916
	exit(1);
1917
    }
Chris Allegretta's avatar
Chris Allegretta committed
1918

1919
    /* Parent continues here. */
1920
1921
    close(spell_fd[1]);

1922
    /* A new process to run sort in. */
1923
1924
    if ((pid_sort = fork()) == 0) {

1925
1926
	/* Child continues (i.e, future spell process).  Replace the
	 * standard input with the standard output of the old pipe. */
1927
1928
1929
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1930
1931
	close(spell_fd[0]);

1932
	/* Send sort's standard output to the new pipe. */
1933
1934
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1935
1936
1937

	close(sort_fd[1]);

1938
1939
1940
	/* 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. */
1941
1942
	execlp("sort", "sort", "-f", NULL);

1943
	/* Should not be reached, if sort is found. */
1944
1945
1946
	exit(1);
    }

1947
    close(spell_fd[0]);
1948
1949
    close(sort_fd[1]);

1950
    /* A new process to run uniq in. */
1951
1952
    if ((pid_uniq = fork()) == 0) {

1953
1954
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
1955
1956
1957
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1958
1959
	close(sort_fd[0]);

1960
	/* Send uniq's standard output to the new pipe. */
1961
1962
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1963
1964
1965

	close(uniq_fd[1]);

1966
	/* Start uniq program; we are using PATH. */
1967
1968
	execlp("uniq", "uniq", NULL);

1969
	/* Should not be reached, if uniq is found. */
1970
1971
1972
	exit(1);
    }

1973
    close(sort_fd[0]);
1974
    close(uniq_fd[1]);
1975

1976
    /* Child process was not forked successfully. */
1977
1978
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
1979
	return _("Could not fork");
1980
    }
1981

1982
    /* Get system pipe buffer size. */
1983
1984
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
1985
	return _("Could not get size of pipe buffer");
1986
    }
Chris Allegretta's avatar
Chris Allegretta committed
1987

1988
    /* Read in the returned spelling errors. */
1989
1990
1991
    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
1992

1993
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
1994
1995
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1996
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
1997
	read_buff_ptr += read_buff_read;
1998

1999
    }
Chris Allegretta's avatar
Chris Allegretta committed
2000

2001
    *read_buff_ptr = (char)NULL;
2002
    close(uniq_fd[0]);
2003

2004
    /* Process the spelling errors. */
2005
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
2006

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

2009
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
2010
	    *read_buff_ptr = (char)NULL;
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
	    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
2021

2022
    /* Special case where last word doesn't end with \n or \r. */
2023
2024
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
2025

2026
2027
    free(read_buff);
    replace_abort();
2028
    edit_refresh();
2029

2030
    /* Process end of spell process. */
2031
2032
2033
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
2034

2035
2036
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
2037

2038
2039
2040
2041
2042
2043
2044
2045
    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;
2046

2047
2048
  close_pipes_and_exit:
    /* Don't leak any handles. */
2049
2050
2051
2052
2053
2054
2055
2056
    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
2057
2058
}

2059
2060
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
2061
const char *do_alt_speller(char *tempfile_name)
2062
{
2063
2064
2065
    int alt_spell_status, lineno_save = current->lineno;
    size_t current_x_save = current_x, pww_save = placewewant;
    int current_y_save = current_y;
2066
2067
2068
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
2069
2070
    static char **spellargs = NULL;
    FILE *f;
2071
#ifndef NANO_SMALL
2072
    bool old_mark_set = ISSET(MARK_ISSET);
2073
2074
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
2075
2076
2077
    bool right_side_up = FALSE;
	/* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark,
	 * FALSE if (current, current_x) is. */
2078
2079
    filestruct *top, *bot;
    size_t top_x, bot_x;
2080
    int mbb_lineno_save = 0;
2081
	/* We're going to close the current file, and open the output of
2082
2083
2084
	 * the alternate spell command.  The line that mark_beginbuf
	 * points to will be freed, so we save the line number and
	 * restore afterwards. */
2085
    size_t totsize_save = totsize;
2086
2087
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
2088

2089
    if (old_mark_set) {
2090
2091
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
2092
	mbb_lineno_save = mark_beginbuf->lineno;
2093
2094
	UNSET(MARK_ISSET);
    }
2095
#endif
2096

2097
    endwin();
2098

2099
    /* Set up an argument list to pass execvp(). */
2100
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2101
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
2102

2103
2104
2105
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2106
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
2107
2108
2109
2110
2111
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
2112

2113
    /* Start a new process for the alternate speller. */
2114
    if ((pid_spell = fork()) == 0) {
2115
	/* Start alternate spell program; we are using PATH. */
2116
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
2117

2118
2119
2120
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
2121

2122
2123
    /* Could not fork?? */
    if (pid_spell < 0)
2124
	return _("Could not fork");
2125

2126
    /* Wait for alternate speller to complete. */
2127
    wait(&alt_spell_status);
2128

2129
2130
    if (!WIFEXITED(alt_spell_status) ||
		WEXITSTATUS(alt_spell_status) != 0) {
2131
2132
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
2133
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;
2134
2135
2136
2137
2138

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
2139
2140

    refresh();
2141

2142
2143
2144
    /* Restore the terminal to its previous state. */
    terminal_init();

2145
#ifndef NANO_SMALL
2146
    if (old_mark_set) {
2147
	size_t part_totsize;
2148
2149
2150
2151
2152
2153

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

2158
2159
2160
2161
	/* 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);
2162
	totsize_save -= part_totsize;
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
    }
#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
2179
	filestruct *top_save = fileage;
2180

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2181
2182
	/* If the mark was on and we added a magicline, remove it
	 * now. */
2183
2184
2185
	if (added_magicline)
	    remove_magicline();

2186
2187
2188
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (fileage == filebot)
2189
	    bot_x += top_x;
2190
	if (right_side_up) {
2191
2192
	    mark_beginx = top_x;
	    current_x_save = bot_x;
2193
	} else {
2194
2195
	    current_x_save = top_x;
	    mark_beginx = bot_x;
2196
	}
2197

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2198
2199
2200
2201
	/* 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. */
2202
	unpartition_filestruct(&filepart);
2203
2204

	/* Renumber starting with the beginning line of the old
2205
2206
2207
2208
	 * 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
2209
	renumber(top_save);
2210
	totlines = filebot->lineno;
2211
2212
	totsize_save += totsize;
	totsize = totsize_save;
2213
2214
2215

	/* Assign mark_beginbuf to the line where the mark began
	 * before. */
2216
	do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0);
2217
	mark_beginbuf = current;
2218
2219
2220
2221

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

2224
2225
	/* Turn the mark back on. */
	SET(MARK_ISSET);
2226
2227
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2228

2229
2230
    /* Go back to the old position, mark the file as modified, and make
     * sure that the titlebar is refreshed. */
2231
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
2232
2233
2234
    set_modified();
    clearok(topwin, FALSE);
    titlebar(NULL);
2235

2236
    return NULL;
2237
}
2238

2239
void do_spell(void)
2240
{
2241
    int i;
2242
    char *temp = safe_tempnam();
2243
    const char *spell_msg;
2244

2245
2246
    if (temp == NULL) {
	statusbar(_("Could not create temp file: %s"), strerror(errno));
2247
	return;
2248
    }
2249

2250
2251
#ifndef NANO_SMALL
    if (ISSET(MARK_ISSET))
2252
	i = write_marked(temp, TRUE, FALSE);
2253
2254
    else
#endif
2255
	i = write_file(temp, TRUE, FALSE, FALSE);
2256
2257

    if (i == -1) {
2258
	statusbar(_("Error writing temp file: %s"), strerror(errno));
2259
	free(temp);
2260
	return;
2261
2262
    }

2263
#ifdef ENABLE_MULTIBUFFER
2264
2265
    /* Update the current open_files entry before spell-checking, in
     * case any problems occur. */
2266
    add_open_file(TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
2267
#endif
2268

2269
2270
    spell_msg = alt_speller != NULL ? do_alt_speller(temp) :
	do_int_speller(temp);
2271
    unlink(temp);
2272
    free(temp);
2273

2274
    if (spell_msg != NULL)
2275
2276
	statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
2277
    else
2278
	statusbar(_("Finished checking spelling"));
2279
}
2280
#endif /* !DISABLE_SPELLER */
2281

2282
#ifndef NANO_SMALL
2283
2284
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
2285
2286
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2287
    size_t len = 0;
2288
2289
    char *blank_mb;
    int blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2290

Chris Allegretta's avatar
Chris Allegretta committed
2291
    assert(line != NULL);
2292
2293
2294
2295

    blank_mb = charalloc(mb_cur_max());

    while (*line != '\0') {
2296
	blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL);
2297
2298
2299
2300
2301
2302

	if (!is_blank_mbchar(blank_mb))
	    break;

	line += blank_mb_len;
	len += blank_mb_len;
Chris Allegretta's avatar
Chris Allegretta committed
2303
    }
2304
2305
2306

    free(blank_mb);

Chris Allegretta's avatar
Chris Allegretta committed
2307
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
2308
}
2309
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
2310

Chris Allegretta's avatar
Chris Allegretta committed
2311
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2312
/* justify_format() replaces Tab by Space and multiple spaces by 1
2313
2314
2315
 * (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
2316
 *
2317
2318
 * justify_format() might make line->data shorter, and change the actual
 * pointer with null_at().
Chris Allegretta's avatar
Chris Allegretta committed
2319
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2320
 * justify_format() will not look at the first skip characters of line.
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2321
2322
 * skip should be at most strlen(line->data).  The character at
 * line[skip + 1] must not be whitespace. */
2323
void justify_format(filestruct *line, size_t skip)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2324
{
Chris Allegretta's avatar
Chris Allegretta committed
2325
2326
2327
2328
2329
    char *back, *front;

    /* These four asserts are assumptions about the input data. */
    assert(line != NULL);
    assert(line->data != NULL);
2330
    assert(skip < strlen(line->data));
2331
    assert(!is_blank_char(line->data[skip]));
Chris Allegretta's avatar
Chris Allegretta committed
2332
2333

    back = line->data + skip;
2334
    for (front = back; ; front++) {
2335
	bool remove_space = FALSE;
2336
2337
	    /* Do we want to remove this space? */

2338
	if (*front == '\t')
Chris Allegretta's avatar
Chris Allegretta committed
2339
	    *front = ' ';
2340

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

2346
	    remove_space = TRUE;
2347
	    for (; bob >= line->data + skip; bob--) {
2348
		if (strchr(punct, *bob) != NULL) {
2349
2350
2351
2352
2353
		    /* 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));
2354
2355
2356
2357
2358
2359
2360
2361
		    break;
		}
		if (strchr(brackets, *bob) == NULL)
		    break;
	    }
	}

	if (remove_space) {
Chris Allegretta's avatar
Chris Allegretta committed
2362
	    /* Now *front is a space we want to remove.  We do that by
2363
	     * simply failing to assign it to *back. */
Chris Allegretta's avatar
Chris Allegretta committed
2364
2365
2366
2367
#ifndef NANO_SMALL
	    if (mark_beginbuf == line && back - line->data < mark_beginx)
		mark_beginx--;
#endif
2368
2369
	    if (*front == '\0')
		*(back - 1) = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2370
2371
2372
2373
	} else {
	    *back = *front;
	    back++;
	}
2374
2375
	if (*front == '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2376
2377
    }

2378
    back--;
2379
    assert(*back == '\0' && *front == '\0');
Chris Allegretta's avatar
Chris Allegretta committed
2380

Chris Allegretta's avatar
Chris Allegretta committed
2381
2382
    /* Now back is the new end of line->data. */
    if (back != front) {
2383
	totsize -= front - back;
Chris Allegretta's avatar
Chris Allegretta committed
2384
2385
2386
2387
2388
	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
2389
    }
Chris Allegretta's avatar
Chris Allegretta committed
2390
}
Chris Allegretta's avatar
Chris Allegretta committed
2391

Chris Allegretta's avatar
Chris Allegretta committed
2392
2393
2394
2395
2396
2397
/* 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. */
2398
size_t quote_length(const char *line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2399
{
2400
#ifdef HAVE_REGEX_H
Chris Allegretta's avatar
Chris Allegretta committed
2401
    regmatch_t matches;
2402
    int rc = regexec(&quotereg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2403

2404
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
Chris Allegretta's avatar
Chris Allegretta committed
2405
	return 0;
2406
2407
    /* matches.rm_so should be 0, since the quote string should start
     * with the caret ^. */
Chris Allegretta's avatar
Chris Allegretta committed
2408
2409
2410
    return matches.rm_eo;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
2411

2412
    /* Compute quote depth level. */
2413
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
2414
	qdepth += quotelen;
Chris Allegretta's avatar
Chris Allegretta committed
2415
2416
    return qdepth;
#endif	/* !HAVE_REGEX_H */
2417
}
Chris Allegretta's avatar
Chris Allegretta committed
2418

Chris Allegretta's avatar
Chris Allegretta committed
2419
2420
2421
/* 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. */
2422
2423
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2424
{
2425
    /* Here is the assumption about a_quote. */
2426
    assert(a_quote == quote_length(a_line));
2427

2428
    return a_quote == quote_length(b_line) &&
2429
	strncmp(a_line, b_line, a_quote) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2430
}
2431

2432
2433
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
2434
bool indents_match(const char *a_line, size_t a_indent, const char
2435
	*b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2436
{
Chris Allegretta's avatar
Chris Allegretta committed
2437
2438
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2439

2440
2441
    return b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0;
Chris Allegretta's avatar
Chris Allegretta committed
2442
}
Chris Allegretta's avatar
Chris Allegretta committed
2443

2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
/* 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)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2507
2508
    const filestruct *current_save = current;
    const size_t pww_save = placewewant;
2509
2510
2511
2512
2513
2514
2515

    current_x = 0;
    placewewant = 0;

    if (current->prev != NULL) {
	do {
	    current = current->prev;
2516
	    current_y--;
2517
2518
2519
	} while (!begpar(current));
    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2520
    edit_redraw(current_save, pww_save);
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
}

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)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2535
2536
    const filestruct *const current_save = current;
    const size_t pww_save = placewewant;
2537
2538
2539
2540
2541
2542
2543
2544

    current_x = 0;
    placewewant = 0;

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

    while (current->next != NULL && inpar(current->next->data) &&
2545
	    !begpar(current->next)) {
2546
	current = current->next;
2547
2548
	current_y++;
    }
2549
2550
2551
2552

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2553
    edit_redraw(current_save, pww_save);
2554
2555
}

2556
2557
2558
2559
/* 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. */
2560
2561
filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
	quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2562
{
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
    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
2576

2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
    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
2593

2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
    /* 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
2606
#ifndef NANO_SMALL
2607
2608
	if (old_mark_set && top->lineno == mbb_lineno_save)
	    mark_beginbuf = top;
Chris Allegretta's avatar
Chris Allegretta committed
2609
#endif
2610
	top = top->prev;
Chris Allegretta's avatar
Chris Allegretta committed
2611
    }
2612
2613
2614
2615
2616
2617
2618

    /* 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
2619
2620
2621
    return first_line;
}

Chris Allegretta's avatar
Chris Allegretta committed
2622
/* Is it possible to break line at or before goal? */
2623
bool breakable(const char *line, ssize_t goal)
Chris Allegretta's avatar
Chris Allegretta committed
2624
{
2625
2626
2627
    while (*line != '\0' && goal >= 0) {
	size_t pos = 0;

2628
	if (is_blank_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2629
2630
	    return TRUE;

2631
	line += parse_mbchar(line, NULL, NULL, &pos);
2632
2633

	goal -= pos;
Chris Allegretta's avatar
Chris Allegretta committed
2634
    }
2635

Chris Allegretta's avatar
Chris Allegretta committed
2636
2637
    /* If goal is not negative, the whole line (one word) was short
     * enough. */
2638
    return (goal >= 0);
Chris Allegretta's avatar
Chris Allegretta committed
2639
2640
}

Chris Allegretta's avatar
Chris Allegretta committed
2641
/* We are trying to break a chunk off line.  We find the last space such
2642
 * that the display length to there is at most goal + 1.  If there is no
2643
2644
2645
 * 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. */
2646
ssize_t break_line(const char *line, ssize_t goal, bool force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2647
{
2648
    ssize_t space_loc = -1;
Chris Allegretta's avatar
Chris Allegretta committed
2649
2650
	/* Current tentative return value.  Index of the last space we
	 * found with short enough display width.  */
2651
    ssize_t cur_loc = 0;
2652
	/* Current index in line. */
Chris Allegretta's avatar
Chris Allegretta committed
2653
2654

    assert(line != NULL);
2655
2656
2657
2658
2659

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

Chris Allegretta's avatar
Chris Allegretta committed
2660
2661
	if (*line == ' ')
	    space_loc = cur_loc;
2662

Chris Allegretta's avatar
Chris Allegretta committed
2663
2664
	assert(*line != '\t');

2665
	line_len = parse_mbchar(line, NULL, NULL, &pos);
2666
2667
2668
2669

	goal -= pos;
	line += line_len;
	cur_loc += line_len;
Chris Allegretta's avatar
Chris Allegretta committed
2670
    }
2671

Chris Allegretta's avatar
Chris Allegretta committed
2672
2673
2674
    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;
2675

Chris Allegretta's avatar
Chris Allegretta committed
2676
2677
    if (space_loc == -1) {
	/* No space found short enough. */
2678
2679
2680
2681
	if (force) {
	    for (; *line != '\0'; line++, cur_loc++) {
		if (*line == ' ' && *(line + 1) != ' ' &&
			*(line + 1) != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
2682
		    return cur_loc;
2683
2684
2685
	    }
	    return -1;
	}
Chris Allegretta's avatar
Chris Allegretta committed
2686
    }
2687

Chris Allegretta's avatar
Chris Allegretta committed
2688
    /* Perhaps the character after space_loc is a space.  But because
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2689
     * of justify_format(), there can be only two adjacent. */
Chris Allegretta's avatar
Chris Allegretta committed
2690
2691
2692
    if (*(line - cur_loc + space_loc + 1) == ' ' ||
	*(line - cur_loc + space_loc + 1) == '\0')
	space_loc++;
2693

Chris Allegretta's avatar
Chris Allegretta committed
2694
2695
2696
    return space_loc;
}

2697
2698
2699
2700
2701
/* 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
2702
 *
2703
2704
2705
 * 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)
2706
{
Chris Allegretta's avatar
Chris Allegretta committed
2707
    size_t quote_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2708
	/* Length of the initial quotation of the paragraph we
2709
	 * search. */
Chris Allegretta's avatar
Chris Allegretta committed
2710
2711
    size_t par_len;
	/* Number of lines in that paragraph. */
2712
2713
2714
2715
    size_t indent_len;
	/* Generic indentation length. */
    filestruct *line;
	/* Generic line of text. */
2716

Chris Allegretta's avatar
Chris Allegretta committed
2717
#ifdef HAVE_REGEX_H
2718
2719
2720
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
	return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
2721
    }
Chris Allegretta's avatar
Chris Allegretta committed
2722
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2723

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

2727
2728
    current_x = 0;

2729
    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2730
    indent_len = indent_length(current->data + quote_len);
Robert Siemborski's avatar
Robert Siemborski committed
2731

2732
2733
2734
2735
    /* 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
2736
2737
    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
2738
2739
	 * the first line of this paragraph.  First we check items 1)
	 * and 3) above. */
2740
2741
	while (current->prev != NULL &&	quotes_match(current->data,
		quote_len, current->prev->data)) {
2742
	    size_t temp_id_len =
2743
		indent_length(current->prev->data + quote_len);
2744
		/* The indentation length of the previous line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2745

2746
	    /* Is this line the beginning of a paragraph, according to
2747
	     * items 2), 5), or 4) above?  If so, stop. */
2748
	    if (current->prev->data[quote_len + temp_id_len] == '\0' ||
2749
		(quote_len == 0 && indent_len > 0
2750
#ifndef NANO_SMALL
2751
		&& !ISSET(AUTOINDENT)
2752
#endif
2753
2754
		) || !indents_match(current->prev->data + quote_len,
		temp_id_len, current->data + quote_len, indent_len))
2755
2756
2757
2758
		break;
	    indent_len = temp_id_len;
	    current = current->prev;
	    current_y--;
Chris Allegretta's avatar
Chris Allegretta committed
2759
	}
Chris Allegretta's avatar
Chris Allegretta committed
2760
    } else {
Chris Allegretta's avatar
Chris Allegretta committed
2761
2762
	/* 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
2763
	do {
2764
	    /* There is no next paragraph, so nothing to move to. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2765
2766
	    if (current->next == NULL) {
		placewewant = 0;
2767
		return TRUE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2768
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2769
	    current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2770
	    current_y++;
2771
	    quote_len = quote_length(current->data);
Chris Allegretta's avatar
Chris Allegretta committed
2772
2773
2774
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
    }
2775

2776
2777
    /* 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
2778

2779
2780
    /* Next step, compute par_len, the number of lines in this
     * paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2781
2782
2783
2784
    line = current;
    par_len = 1;
    indent_len = indent_length(line->data + quote_len);

2785
2786
2787
    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);
2788

2789
	if (!indents_match(line->data + quote_len, indent_len,
2790
		line->next->data + quote_len, temp_id_len) ||
Chris Allegretta's avatar
Chris Allegretta committed
2791
2792
2793
		line->next->data[quote_len + temp_id_len] == '\0' ||
		(quote_len == 0 && temp_id_len > 0
#ifndef NANO_SMALL
2794
		&& !ISSET(AUTOINDENT)
Chris Allegretta's avatar
Chris Allegretta committed
2795
2796
#endif
		))
2797
2798
2799
2800
	    break;
	indent_len = temp_id_len;
	line = line->next;
	par_len++;
2801
2802
    }

2803
2804
    /* Now par_len is the number of lines in this paragraph.  We should
     * never call quotes_match() or quote_length() again. */
2805

2806
2807
2808
2809
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
    *quote = quote_len;
    *par = par_len;
2810

2811
    return FALSE;
2812
2813
}

2814
2815
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
2816
void do_justify(bool full_justify)
2817
{
2818
2819
    filestruct *first_par_line = NULL;
	/* Will be the first line of the resulting justified paragraph.
2820
	 * For restoring after unjustify. */
2821
    filestruct *last_par_line;
2822
2823
	/* Will be the line containing the newline after the last line
	 * of the result.  Also for restoring after unjustify. */
2824
    bool allow_respacing;
2825
	/* Whether we should change the spacing at the end of a line
2826
	 * after justifying it.  This should be TRUE whenever we move
2827
	 * to the next line after justifying the current line. */
2828
2829

    /* We save these global variables to be restored if the user
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2830
     * unjustifies.  Note that we don't need to save totlines. */
2831
2832
    size_t current_x_save = current_x;
    int current_y_save = current_y;
2833
2834
    unsigned long flags_save = flags;
    size_t totsize_save = totsize;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2835
    filestruct *edittop_save = edittop, *current_save = current;
2836
2837
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
2838
    size_t mark_beginx_save = mark_beginx;
2839
#endif
2840
    int kbinput;
2841
    bool meta_key, func_key, s_or_t, ran_func, finished;
2842

2843
2844
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
2845
	current = fileage;
2846
2847

    last_par_line = current;
2848
2849

    while (TRUE) {
2850
2851
2852
2853
2854
2855
2856
2857
2858
	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
2859
2860
2861
2862
2863
2864
	 * 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
2865
2866
	 * get out. */
	if (do_para_search(&quote_len, &par_len)) {
2867
	    if (full_justify && first_par_line != filebot) {
2868
		last_par_line = filebot;
2869
2870
2871
		break;
	    } else {
		edit_refresh();
2872
		return;
2873
2874
	    }
	}
2875

2876
2877
2878
	/* Next step, we loop through the lines of this paragraph,
	 * justifying each one individually. */
	for (; par_len > 0; current_y++, par_len--) {
2879
	    size_t indent_len;	/* Generic indentation length. */
2880
2881
2882
	    size_t line_len;
	    size_t display_len;
		/* The width of current in screen columns. */
2883
	    ssize_t break_pos;
2884
2885
		/* Where we will break the line. */

2886
2887
2888
2889
2890
	    /* 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;

2891
2892
	    indent_len = quote_len + indent_length(current->data +
		quote_len);
2893

2894
	    /* If we haven't already done it, copy the original
2895
	     * paragraph to the justify buffer. */
2896
2897
	    if (first_par_line == NULL)
		first_par_line = backup_lines(current, full_justify ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2898
2899
			filebot->lineno - current->lineno : par_len,
			quote_len);
2900

2901
2902
2903
2904
2905
2906
	    /* 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));

2907
2908
2909
2910
2911
2912
	    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
2913
2914
			fill - strnlenpt(current->data, indent_len),
			TRUE);
2915
2916
		if (break_pos == -1 ||
			break_pos + indent_len == line_len)
2917
2918
2919
2920
2921
2922
2923
2924
2925
		    /* 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
2926
2927
		    splice_node(current, make_new_node(current),
			current->next);
2928
2929
		    /* In a non-quoted paragraph, we copy the indent
		     * only if AUTOINDENT is turned on. */
2930
		    if (quote_len == 0
Chris Allegretta's avatar
Chris Allegretta committed
2931
#ifndef NANO_SMALL
2932
			&& !ISSET(AUTOINDENT)
2933
#endif
2934
			)
2935
			    indent_len = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2936
2937
2938
2939
		    current->next->data = charalloc(indent_len +
			line_len - break_pos);
		    strncpy(current->next->data, current->data,
			indent_len);
2940
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2941
			current->data + break_pos + 1);
2942
		    assert(strlen(current->next->data) ==
Chris Allegretta's avatar
Chris Allegretta committed
2943
			indent_len + line_len - break_pos - 1);
2944
2945
2946
2947
2948
		    totlines++;
		    totsize += indent_len;
		    par_len++;
		} else {
		    size_t next_line_len = strlen(current->next->data);
Chris Allegretta's avatar
Chris Allegretta committed
2949

2950
		    indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2951
			indent_length(current->next->data + quote_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2952
2953
2954
		    current->next->data =
			charealloc(current->next->data, next_line_len +
			line_len - break_pos + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2955

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2956
2957
2958
		    charmove(current->next->data + indent_len +
			line_len - break_pos, current->next->data +
			indent_len, next_line_len - indent_len + 1);
2959
		    strcpy(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2960
			current->data + break_pos + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2961
2962
		    current->next->data[indent_len + line_len -
			break_pos - 1] = ' ';
Chris Allegretta's avatar
Chris Allegretta committed
2963
#ifndef NANO_SMALL
2964
2965
2966
2967
2968
		    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
2969
#endif
2970
		}
Chris Allegretta's avatar
Chris Allegretta committed
2971
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2972
2973
		if (mark_beginbuf == current &&
			mark_beginx > break_pos) {
2974
2975
2976
		    mark_beginbuf = current->next;
		    mark_beginx -= break_pos + 1 - indent_len;
		}
Chris Allegretta's avatar
Chris Allegretta committed
2977
#endif
2978
		null_at(&current->data, break_pos);
2979
2980

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

2985
		indent_len = quote_len +
Chris Allegretta's avatar
Chris Allegretta committed
2986
			indent_length(current->next->data + quote_len);
2987
2988
2989
2990
2991
2992
2993
2994
		/* 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);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2995

2996
2997
2998
2999
3000
3001
		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
3002
			current->next->data + indent_len, break_pos);
3003
		current->data[line_len + break_pos + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
3004
#ifndef NANO_SMALL
3005
3006
		if (mark_beginbuf == current->next) {
		    if (mark_beginx < indent_len + break_pos) {
3007
			mark_beginbuf = current;
3008
3009
3010
			if (mark_beginx <= indent_len)
			    mark_beginx = line_len + 1;
			else
3011
3012
			    mark_beginx = line_len + 1 + mark_beginx -
				indent_len;
3013
3014
3015
		    } else
			mark_beginx -= break_pos + 1;
		}
Chris Allegretta's avatar
Chris Allegretta committed
3016
#endif
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
		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--;
3030

3031
3032
		    /* 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
3033
3034
		     * previous justified line, so that we don't end up
		     * doing it more than once on the same line. */
3035
		    allow_respacing = FALSE;
3036
3037
		} else {
		    charmove(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
3038
3039
			current->next->data + indent_len + break_pos + 1,
			next_line_len - break_pos - indent_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3040
3041
		    null_at(&current->next->data, next_line_len -
			break_pos);
3042
3043

		    /* Go to the next line. */
3044
3045
3046
		    current = current->next;
		}
	    } else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3047
  continue_loc:
3048
		/* Go to the next line. */
3049
		current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
3050

3051
	    /* We've moved to the next line after justifying the
3052
3053
3054
3055
3056
3057
3058
	     * 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) {
3059
		size_t prev_line_len = strlen(current->prev->data);
3060
3061
3062

		if (par_len > 1) {
		    current->prev->data = charealloc(current->prev->data,
3063
			prev_line_len + 2);
3064
3065
3066
3067
3068
		    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
3069
3070
		    current->prev->data =
			charealloc(current->prev->data, prev_line_len);
3071
3072
3073
		    current->prev->data[prev_line_len - 1] = '\0';
		    totsize--;
		}
3074
	    }
3075
3076
	}

3077
3078
3079
	/* 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. */
3080
3081
3082
3083
	if (!full_justify)
	    break;

    } /* while (TRUE) */
3084

3085
3086
3087
    /* 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
3088
3089
3090
     * fileage, and renumber() since edit_refresh() needs the line
     * numbers to be right (but only do the last two if we actually
     * justified something). */
3091
    last_par_line = current;
3092
3093
3094
3095
3096
    if (first_par_line != NULL) {
	if (first_par_line->prev == NULL)
	    fileage = first_par_line;
	renumber(first_par_line);
    }
3097

3098
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
3099

3100
    statusbar(_("Can now UnJustify!"));
3101

3102
    /* Display the shortcut list with UnJustify. */
3103
    shortcut_init(TRUE);
3104
    display_main_list();
3105

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3106
3107
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
3108
3109
    kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
	&finished, FALSE);
3110

3111
3112
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
3113
	/* Restore the justify we just did (ungrateful user!). */
Chris Allegretta's avatar
Chris Allegretta committed
3114
3115
3116
3117
3118
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;

3119
	/* Splice the justify buffer back into the file, but only if we
3120
3121
	 * actually justified something. */
	if (first_par_line != NULL) {
3122
	    filestruct *bot_save;
3123

3124
3125
3126
3127
	    /* 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);
3128

3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
	    /* 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
3150
#ifndef NANO_SMALL
3151
3152
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
Chris Allegretta's avatar
Chris Allegretta committed
3153
#endif
3154
3155
3156
3157
3158
3159
3160
3161
3162
	    flags = flags_save;

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

	    if (!ISSET(MODIFIED))
		titlebar(NULL);
	    edit_refresh();
	}
3163
    } else {
3164
	unget_kbinput(kbinput, meta_key, func_key);
3165

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3166
	/* Blow away the text in the justify buffer. */
3167
3168
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
3169
    }
3170

3171
    blank_statusbar();
3172

3173
    /* Display the shortcut list with UnCut. */
3174
    shortcut_init(FALSE);
3175
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
3176
}
3177

3178
void do_justify_void(void)
3179
{
3180
    do_justify(FALSE);
3181
3182
}

3183
void do_full_justify(void)
3184
{
3185
    do_justify(TRUE);
3186
}
3187
#endif /* !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
3188

3189
void do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
3190
{
3191
3192
    int i;

3193
3194
    if (!ISSET(MODIFIED))
	i = 0;		/* Pretend the user chose not to save. */
3195
    else if (ISSET(TEMP_FILE))
3196
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3197
    else
3198
3199
3200
	i = do_yesno(FALSE,
		_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));

3201
3202
#ifdef DEBUG
    dump_buffer(fileage);
3203
#endif
3204

3205
    if (i == 0 || (i == 1 && do_writeout(TRUE) > 0)) {
3206
#ifdef ENABLE_MULTIBUFFER
3207
	/* Exit only if there are no more open file buffers. */
3208
	if (!close_open_file())
3209
#endif
3210
	    finish();
3211
    } else if (i != 1)
3212
3213
3214
3215
3216
3217
3218
	statusbar(_("Cancelled"));

    display_main_list();
}

void signal_init(void)
{
3219
3220
    /* Trap SIGINT and SIGQUIT because we want them to do useful
     * things. */
3221
3222
3223
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);
3224
    sigaction(SIGQUIT, &act, NULL);
3225

3226
    /* Trap SIGHUP and SIGTERM because we want to write the file out. */
3227
    act.sa_handler = handle_hupterm;
3228
    sigaction(SIGHUP, &act, NULL);
3229
    sigaction(SIGTERM, &act, NULL);
3230

3231
#ifndef NANO_SMALL
3232
    /* Trap SIGWINCH because we want to handle window resizes. */
3233
3234
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
3235
    allow_pending_sigwinch(FALSE);
3236
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3237

3238
    /* Trap normal suspend (^Z) so we can handle it ourselves. */
3239
3240
3241
3242
    if (!ISSET(SUSPEND)) {
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
    } else {
3243
3244
	/* Block all other signals in the suspend and continue handlers.
	 * If we don't do this, other stuff interrupts them! */
3245
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
3246

3247
3248
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
3249

3250
3251
3252
3253
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }
}
3254

3255
/* Handler for SIGHUP (hangup) and SIGTERM (terminate). */
3256
void handle_hupterm(int signal)
3257
{
3258
    die(_("Received SIGHUP or SIGTERM\n"));
3259
}
3260

3261
/* Handler for SIGTSTP (suspend). */
3262
void do_suspend(int signal)
3263
3264
{
    endwin();
3265
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
3266
    fflush(stdout);
3267

3268
    /* Restore the old terminal settings. */
3269
    tcsetattr(0, TCSANOW, &oldterm);
3270

3271
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
3272
     * suspended. */
3273
3274
3275
3276
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

3277
    /* Do what mutt does: send ourselves a SIGSTOP. */
3278
3279
    kill(0, SIGSTOP);
}
3280

3281
/* Handler for SIGCONT (continue after suspend). */
3282
void do_cont(int signal)
3283
{
3284
#ifndef NANO_SMALL
3285
3286
    /* Perhaps the user resized the window while we slept.  Handle it
     * and update the screen in the process. */
3287
    handle_sigwinch(0);
3288
#else
3289
3290
    /* Just update the screen. */
    doupdate();
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
#endif
}

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

3302
    if (tty == NULL)
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
	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;

3319
3320
    check_die_too_small();
    resize_variables();
3321

3322
3323
    /* If we've partitioned the filestruct, unpartition it now. */
    if (filepart != NULL)
3324
	unpartition_filestruct(&filepart);
3325

3326
#ifndef DISABLE_JUSTIFY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3327
3328
    /* If the justify buffer isn't empty, blow away the text in it and
     * display the shortcut list with UnCut. */
3329
3330
3331
3332
3333
3334
3335
    if (jusbuffer != NULL) {
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
	shortcut_init(FALSE);
    }
#endif

3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
#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
3349

3350
3351
3352
    /* Restore the terminal to its previous state. */
    terminal_init();

3353
3354
3355
3356
3357
    /* Do the equivalent of what both mutt and Minimum Profit do:
     * Reinitialize all the windows based on the new screen
     * dimensions. */
    window_init();

3358
    /* Redraw the contents of the windows that need it. */
3359
    blank_statusbar();
3360
    currshortcut = main_list;
3361
3362
    total_refresh();

3363
    /* Turn the cursor back on for sure. */
3364
3365
    curs_set(1);

3366
3367
3368
    /* Reset all the input routines that rely on character sequences. */
    reset_kbinput();

3369
    /* Jump back to the main loop. */
3370
3371
    siglongjmp(jmpbuf, 1);
}
3372

3373
void allow_pending_sigwinch(bool allow)
3374
3375
3376
3377
3378
3379
3380
3381
3382
{
    sigset_t winch;
    sigemptyset(&winch);
    sigaddset(&winch, SIGWINCH);
    if (allow)
	sigprocmask(SIG_UNBLOCK, &winch, NULL);
    else
	sigprocmask(SIG_BLOCK, &winch, NULL);
}
3383
#endif /* !NANO_SMALL */
3384

3385
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3386
void do_toggle(const toggle *which)
3387
{
3388
    bool enabled;
3389

3390
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3391

3392
    switch (which->val) {
3393
#ifndef DISABLE_MOUSE
3394
3395
3396
	case TOGGLE_MOUSE_KEY:
	    mouse_init();
	    break;
3397
#endif
3398
	case TOGGLE_MORESPACE_KEY:
3399
3400
	case TOGGLE_NOHELP_KEY:
	    window_init();
3401
	    total_refresh();
3402
	    break;
3403
3404
	case TOGGLE_SUSPEND_KEY:
	    signal_init();
3405
	    break;
3406
#ifdef ENABLE_NANORC
3407
	case TOGGLE_WHITESPACE_KEY:
3408
	    titlebar(NULL);
3409
3410
	    edit_refresh();
	    break;
3411
3412
3413
3414
3415
#endif
#ifdef ENABLE_COLOR
	case TOGGLE_SYNTAX_KEY:
	    edit_refresh();
	    break;
3416
#endif
3417
    }
Chris Allegretta's avatar
Chris Allegretta committed
3418

Chris Allegretta's avatar
Chris Allegretta committed
3419
3420
3421
3422
3423
3424
3425
    /* 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
3426
}
3427
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3428

3429
3430
3431
3432
3433
3434
3435
3436
3437
void disable_extended_input(void)
{
    struct termios term;

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

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

3476
3477
3478
3479
/* 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
3480
3481
3482
3483
 * 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. */
3484
3485
3486
3487
3488
void terminal_init(void)
{
    cbreak();
    nonl();
    noecho();
3489
    disable_extended_input();
3490
3491
3492
3493
3494
    disable_signals();
    if (!ISSET(PRESERVE))
	disable_flow_control();
}

3495
int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool
3496
	*ran_func, bool *finished, bool allow_funcs)
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
{
    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;
3512
    *ran_func = FALSE;
3513
    *finished = FALSE;
3514
3515
3516
3517
3518
3519
3520

    /* 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. */
3521
    if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) {
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
	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. */
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
		char *output = charalloc(kbinput_len + 1);
		size_t i;

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

		do_output(output, kbinput_len);

		free(output);
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597

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

	if (have_shortcut) {
	    switch (input) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3598
		/* Handle the "universal" statusbar prompt shortcuts. */
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
		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
3611
		/* Handle the normal edit window shortcuts, setting
3612
3613
3614
		 * ran_func to TRUE if we try to run their associated
		 * functions and setting finished to TRUE to indicate
		 * that we're done after trying to run their associated
3615
		 * functions. */
3616
3617
3618
3619
3620
3621
		default:
		    /* Blow away the text in the cutbuffer if we aren't
		     * cutting text. */
		    if (s->func != do_cut_text)
			cutbuffer_reset();

3622
		    if (s->func != NULL) {
3623
			*ran_func = TRUE;
3624
3625
3626
3627
			if (ISSET(VIEW_MODE) && !s->viewok)
			    print_view_warning();
			else
			    s->func();
3628
		    }
3629
		    *finished = TRUE;
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
3665
3666
3667
3668
3669
3670
		    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? */
3671
	    mouse_y -= (2 - no_more_space());
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706

	    sameline = (mouse_y == current_y);

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

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

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

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

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

3707
3708
3709
/* The user typed kbinput_len multibyte characters.  Add them to the
 * edit buffer. */
void do_output(char *output, size_t output_len)
3710
{
3711
    size_t current_len = strlen(current->data), i = 0;
3712
3713
3714
3715
3716
    bool old_constupdate = ISSET(CONSTUPDATE);
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

3717
3718
    char *char_buf = charalloc(mb_cur_max());
    int char_buf_len;
3719
3720
3721

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

3722
3723
    /* Turn off constant cursor position display. */
    UNSET(CONSTUPDATE);
3724

3725
    while (i < output_len) {
3726
	/* Null to newline, if needed. */
3727
3728
	if (output[i] == '\0')
	    output[i] = '\n';
3729
	/* Newline to Enter, if needed. */
3730
	else if (output[i] == '\n') {
3731
	    do_enter();
3732
	    i++;
3733
3734
3735
	    continue;
	}

3736
3737
3738
	/* Interpret the next multibyte character.  If it's an invalid
	 * multibyte character, interpret it as though it's a byte
	 * character. */
3739
	char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL);
3740
3741

	i += char_buf_len;
3742
3743
3744
3745
3746
3747
3748

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

	/* More dangerousness fun =) */
3749
3750
	current->data = charealloc(current->data, current_len +
		(char_buf_len * 2));
3751

3752
	assert(current_x <= current_len);
3753

3754
	charmove(&current->data[current_x + char_buf_len],
3755
		&current->data[current_x],
3756
3757
3758
		current_len - current_x + char_buf_len);
	charcpy(&current->data[current_x], char_buf, char_buf_len);
	current_len += char_buf_len;
3759
	totsize++;
3760
3761
3762
3763
3764
	set_modified();

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

3768
	do_right(FALSE);
3769
3770
3771

#ifndef DISABLE_WRAPPING
	/* If we're wrapping text, we need to call edit_refresh(). */
3772
	if (!ISSET(NO_WRAP) && output[i] != '\t') {
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
	    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
    }

3792
3793
    /* Turn constant cursor position display back on if it was on
     * before. */
3794
3795
3796
    if (old_constupdate)
	SET(CONSTUPDATE);

3797
    free(char_buf);
3798

3799
3800
3801
3802
3803
3804
    if (do_refresh)
	edit_refresh();
    else
	update_line(current, current_x);
}

3805
int main(int argc, char **argv)
Chris Allegretta's avatar
Chris Allegretta committed
3806
3807
{
    int optchr;
3808
3809
    int startline = 0;
	/* Line to try and start at. */
3810
#ifndef DISABLE_WRAPJUSTIFY
3811
3812
    bool fill_flag_used = FALSE;
	/* Was the fill option used? */
3813
#endif
3814
3815
3816
3817
3818
#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
3819
#ifdef HAVE_GETOPT_LONG
3820
    const struct option long_options[] = {
3821
3822
3823
	{"help", 0, 0, 'h'},
#ifdef ENABLE_MULTIBUFFER
	{"multibuffer", 0, 0, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
3824
3825
#endif
#ifdef ENABLE_NANORC
3826
#ifndef NANO_SMALL
3827
	{"historylog", 0, 0, 'H'},
3828
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3829
	{"ignorercfiles", 0, 0, 'I'},
3830
#endif
3831
	{"morespace", 0, 0, 'O'},
3832
3833
3834
#ifndef DISABLE_JUSTIFY
	{"quotestr", 1, 0, 'Q'},
#endif
3835
#ifdef HAVE_REGEX_H
3836
	{"regexp", 0, 0, 'R'},
3837
#endif
3838
	{"tabsize", 1, 0, 'T'},
Chris Allegretta's avatar
Chris Allegretta committed
3839
	{"version", 0, 0, 'V'},
3840
3841
#ifdef ENABLE_COLOR
	{"syntax", 1, 0, 'Y'},
3842
#endif
3843
	{"const", 0, 0, 'c'},
3844
	{"rebinddelete", 0, 0, 'd'},
3845
	{"nofollow", 0, 0, 'l'},
3846
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3847
	{"mouse", 0, 0, 'm'},
3848
#endif
3849
3850
3851
#ifndef DISABLE_OPERATINGDIR
	{"operatingdir", 1, 0, 'o'},
#endif
3852
	{"preserve", 0, 0, 'p'},
3853
3854
3855
3856
3857
#ifndef DISABLE_WRAPJUSTIFY
	{"fill", 1, 0, 'r'},
#endif
#ifndef DISABLE_SPELLER
	{"speller", 1, 0, 's'},
3858
#endif
3859
3860
	{"tempfile", 0, 0, 't'},
	{"view", 0, 0, 'v'},
3861
#ifndef DISABLE_WRAPPING
3862
	{"nowrap", 0, 0, 'w'},
3863
#endif
3864
3865
	{"nohelp", 0, 0, 'x'},
	{"suspend", 0, 0, 'z'},
3866
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3867
	{"smarthome", 0, 0, 'A'},
3868
	{"backup", 0, 0, 'B'},
3869
	{"backupdir", 1, 0, 'E'},
3870
	{"noconvert", 0, 0, 'N'},
3871
	{"smooth", 0, 0, 'S'},
3872
	{"restricted", 0, 0, 'Z'},
3873
3874
	{"autoindent", 0, 0, 'i'},
	{"cut", 0, 0, 'k'},
3875
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3876
3877
3878
3879
	{0, 0, 0, 0}
    };
#endif

3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
#ifdef NANO_WIDE
    {
	/* If the locale set doesn't exist, or it exists but doesn't
	 * include the string "UTF-8", we shouldn't use UTF-8
	 * support. */
	char *locale = setlocale(LC_ALL, "");

	if (locale == NULL || (locale != NULL &&
		strstr(locale, "UTF-8") == NULL))
	    SET(NO_UTF8);
3890
3891
3892
3893
3894

#ifdef USE_SLANG
	if (!ISSET(NO_UTF8))
	    SLsmg_utf8_enable(TRUE);
#endif
3895
3896
    }
#else
Chris Allegretta's avatar
Chris Allegretta committed
3897
    setlocale(LC_ALL, "");
3898
3899
#endif

3900
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
3901
3902
3903
3904
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3905
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3906
3907
    /* If we don't have rcfile support, we're root, and
     * --disable-wrapping-as-root is used, turn wrapping off. */
3908
    if (geteuid() == NANO_ROOT_UID)
3909
3910
	SET(NO_WRAP);
#endif
3911

3912
    while ((optchr =
Chris Allegretta's avatar
Chris Allegretta committed
3913
#ifdef HAVE_GETOPT_LONG
3914
	getopt_long(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz", long_options, NULL)
Chris Allegretta's avatar
Chris Allegretta committed
3915
#else
3916
	getopt(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz")
Chris Allegretta's avatar
Chris Allegretta committed
3917
#endif
3918
		) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3919
3920

	switch (optchr) {
3921
3922
3923
3924
3925
3926
3927
3928
	    case 'a':
	    case 'b':
	    case 'e':
	    case 'f':
	    case 'g':
	    case 'j':
		/* Pico compatibility flags. */
		break;
3929
#ifndef NANO_SMALL
3930
3931
3932
3933
3934
3935
3936
3937
3938
	    case 'A':
		SET(SMART_HOME);
		break;
	    case 'B':
		SET(BACKUP_FILE);
		break;
	    case 'E':
		backup_dir = mallocstrcpy(backup_dir, optarg);
		break;
3939
#endif
3940
#ifdef ENABLE_MULTIBUFFER
3941
3942
3943
	    case 'F':
		SET(MULTIBUFFER);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
3944
3945
#endif
#ifdef ENABLE_NANORC
3946
#ifndef NANO_SMALL
3947
3948
3949
	    case 'H':
		SET(HISTORYLOG);
		break;
3950
#endif
3951
3952
3953
	    case 'I':
		SET(NO_RCFILE);
		break;
3954
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3955
#ifndef NANO_SMALL
3956
3957
3958
	    case 'N':
		SET(NO_CONVERT);
		break;
3959
#endif
3960
3961
3962
	    case 'O':
		SET(MORE_SPACE);
		break;
3963
#ifndef DISABLE_JUSTIFY
3964
3965
3966
	    case 'Q':
		quotestr = mallocstrcpy(quotestr, optarg);
		break;
3967
#endif
3968
#ifdef HAVE_REGEX_H
3969
3970
3971
	    case 'R':
		SET(USE_REGEXP);
		break;
3972
3973
#endif
#ifndef NANO_SMALL
3974
3975
3976
	    case 'S':
		SET(SMOOTHSCROLL);
		break;
3977
#endif
3978
3979
	    case 'T':
		if (!parse_num(optarg, &tabsize) || tabsize <= 0) {
3980
3981
		    fprintf(stderr, _("Requested tab size %s invalid"), optarg);
		    fprintf(stderr, "\n");
3982
3983
3984
3985
3986
3987
		    exit(1);
		}
		break;
	    case 'V':
		version();
		exit(0);
3988
#ifdef ENABLE_COLOR
3989
3990
3991
	    case 'Y':
		syntaxstr = mallocstrcpy(syntaxstr, optarg);
		break;
3992
#endif
3993
3994
3995
3996
3997
3998
3999
4000
4001
	    case 'Z':
		SET(RESTRICTED);
		break;
	    case 'c':
		SET(CONSTUPDATE);
		break;
	    case 'd':
		SET(REBIND_DELETE);
		break;
4002
#ifndef NANO_SMALL
4003
4004
4005
4006
4007
4008
	    case 'i':
		SET(AUTOINDENT);
		break;
	    case 'k':
		SET(CUT_TO_END);
		break;
4009
#endif
4010
4011
4012
	    case 'l':
		SET(NOFOLLOW_SYMLINKS);
		break;
4013
#ifndef DISABLE_MOUSE
4014
4015
4016
	    case 'm':
		SET(USE_MOUSE);
		break;
4017
#endif
4018
#ifndef DISABLE_OPERATINGDIR
4019
4020
4021
	    case 'o':
		operating_dir = mallocstrcpy(operating_dir, optarg);
		break;
4022
#endif
4023
4024
4025
	    case 'p':
		SET(PRESERVE);
		break;
4026
#ifndef DISABLE_WRAPJUSTIFY
4027
4028
	    case 'r':
		if (!parse_num(optarg, &wrap_at)) {
4029
4030
		    fprintf(stderr, _("Requested fill size %s invalid"), optarg);
		    fprintf(stderr, "\n");
4031
4032
4033
4034
		    exit(1);
		}
		fill_flag_used = TRUE;
		break;
4035
#endif
4036
#ifndef DISABLE_SPELLER
4037
4038
4039
	    case 's':
		alt_speller = mallocstrcpy(alt_speller, optarg);
		break;
4040
#endif
4041
4042
4043
4044
4045
4046
	    case 't':
		SET(TEMP_FILE);
		break;
	    case 'v':
		SET(VIEW_MODE);
		break;
4047
#ifndef DISABLE_WRAPPING
4048
4049
4050
	    case 'w':
		SET(NO_WRAP);
		break;
4051
#endif
4052
4053
4054
4055
4056
4057
4058
4059
	    case 'x':
		SET(NO_HELP);
		break;
	    case 'z':
		SET(SUSPEND);
		break;
	    default:
		usage();
Chris Allegretta's avatar
Chris Allegretta committed
4060
4061
4062
	}
    }

4063
4064
    /* If the executable filename starts with 'r', we use restricted
     * mode. */
4065
4066
4067
    if (*(tail(argv[0])) == 'r')
	SET(RESTRICTED);

4068
4069
4070
    /* 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. */
4071
4072
4073
4074
4075
4076
    if (ISSET(RESTRICTED)) {
	UNSET(SUSPEND);
	UNSET(BACKUP_FILE);
	SET(NO_RCFILE);
    }

Chris Allegretta's avatar
Chris Allegretta committed
4077
/* We've read through the command line options.  Now back up the flags
4078
4079
 * 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
4080
4081
4082
4083
4084
#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
4085
#ifndef DISABLE_WRAPJUSTIFY
4086
	ssize_t wrap_at_cpy = wrap_at;
Chris Allegretta's avatar
Chris Allegretta committed
4087
#endif
4088
4089
4090
#ifndef NANO_SMALL
	char *backup_dir_cpy = backup_dir;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4091
4092
4093
4094
4095
4096
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
4097
	ssize_t tabsize_cpy = tabsize;
4098
	unsigned long flags_cpy = flags;
Chris Allegretta's avatar
Chris Allegretta committed
4099

4100
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4101
	operating_dir = NULL;
4102
#endif
4103
4104
4105
#ifndef NANO_SMALL
	backup_dir = NULL;
#endif
4106
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4107
	quotestr = NULL;
4108
4109
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
4110
	alt_speller = NULL;
4111
#endif
Chris Allegretta's avatar
Chris Allegretta committed
4112
4113
4114
4115
4116
4117
4118
4119
4120

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
4121
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
4122
4123
4124
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
4125
4126
4127
4128
4129
4130
#ifndef NANO_SMALL
	if (backup_dir_cpy != NULL) {
	    free(backup_dir);
	    backup_dir = backup_dir_cpy;
	}
#endif	
Chris Allegretta's avatar
Chris Allegretta committed
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
#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
4143
	if (tabsize_cpy != -1)
Chris Allegretta's avatar
Chris Allegretta committed
4144
4145
4146
4147
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
4148
    else if (geteuid() == NANO_ROOT_UID)
Chris Allegretta's avatar
Chris Allegretta committed
4149
4150
4151
4152
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

4153
4154
4155
4156
4157
4158
4159
4160
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

4161
#ifndef NANO_SMALL
4162
    /* Set up the backup directory (unless we're using restricted mode,
4163
4164
4165
4166
     * 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. */
4167
4168
    if (!ISSET(RESTRICTED))
	init_backup_dir();
4169
4170
#endif

4171
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
4172
    /* Set up the operating directory.  This entails chdir()ing there,
4173
     * so that file reads and writes will be based there. */
4174
4175
4176
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
4177
#ifndef DISABLE_JUSTIFY
4178
    if (punct == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4179
	punct = mallocstrcpy(punct, ".?!");
4180
4181

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

Chris Allegretta's avatar
Chris Allegretta committed
4184
    if (quotestr == NULL)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4185
	quotestr = mallocstrcpy(NULL,
Chris Allegretta's avatar
Chris Allegretta committed
4186
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4187
		"^([ \t]*[|>:}#])+"
Chris Allegretta's avatar
Chris Allegretta committed
4188
#else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4189
		"> "
Chris Allegretta's avatar
Chris Allegretta committed
4190
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4191
		);
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
#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
4208
#endif /* !DISABLE_JUSTIFY */
4209

4210
4211
#ifndef DISABLE_SPELLER
    /* If we don't have an alternative spell checker after reading the
4212
     * command line and/or rcfile(s), check $SPELL for one, as Pico
4213
     * does (unless we're using restricted mode, in which case spell
4214
4215
     * checking is disabled, since it would allow reading from or
     * writing to files not specified on the command line). */
4216
    if (!ISSET(RESTRICTED) && alt_speller == NULL) {
4217
4218
4219
4220
4221
4222
	char *spellenv = getenv("SPELL");
	if (spellenv != NULL)
	    alt_speller = mallocstrcpy(NULL, spellenv);
    }
#endif

4223
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
4224
    /* If whitespace wasn't specified, set its default value. */
4225
4226
4227
4228
    if (whitespace == NULL)
	whitespace = mallocstrcpy(NULL, "  ");
#endif

4229
    /* If tabsize wasn't specified, set its default value. */
Chris Allegretta's avatar
Chris Allegretta committed
4230
    if (tabsize == -1)
4231
	tabsize = WIDTH_OF_TAB;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4232

4233
    /* Back up the old terminal settings so that they can be restored. */
4234
    tcgetattr(0, &oldterm);
4235

4236
4237
    /* Curses initialization stuff: Start curses and set up the
     * terminal state. */
Chris Allegretta's avatar
Chris Allegretta committed
4238
    initscr();
4239
    terminal_init();
4240

4241
    /* Set up the global variables and the shortcuts. */
4242
4243
    global_init(FALSE);
    shortcut_init(FALSE);
4244
4245

    /* Set up the signal handlers. */
4246
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
4247
4248

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

4252
    window_init();
4253
#ifndef DISABLE_MOUSE
4254
    mouse_init();
4255
#endif
4256

Chris Allegretta's avatar
Chris Allegretta committed
4257
#ifdef DEBUG
4258
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
4259
#endif
4260

4261
4262
4263
4264
4265
4266
4267
4268
    /* 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
4269
#ifdef ENABLE_MULTIBUFFER
4270
4271
4272
4273
4274
4275
    old_multibuffer = ISSET(MULTIBUFFER);
    SET(MULTIBUFFER);

    /* Read all the files after the first one on the command line into
     * new buffers. */
    {
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
	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;
		}
	    }
	}
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
    }
#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
4311
    }
4312
4313
4314
4315

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

4318
4319
4320
4321
4322
4323
4324
#ifdef DEBUG
    fprintf(stderr, "Main: top and bottom win\n");
#endif

    titlebar(NULL);
    display_main_list();

Chris Allegretta's avatar
Chris Allegretta committed
4325
    if (startline > 0)
4326
	do_gotoline(startline, FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4327

4328
4329
#ifndef NANO_SMALL
    /* Return here after a SIGWINCH. */
4330
    sigsetjmp(jmpbuf, 1);
4331
#endif
4332

Robert Siemborski's avatar
Robert Siemborski committed
4333
4334
    edit_refresh();

4335
    while (TRUE) {
4336
	bool meta_key, func_key, s_or_t, ran_func, finished;
4337
4338

	/* Make sure the cursor is in the edit window. */
4339
	reset_cursor();
4340
4341
4342

	/* If constant cursor position display is on, display the cursor
	 * position. */
4343
	if (ISSET(CONSTUPDATE))
4344
	    do_cursorpos(TRUE);
4345

4346
4347
	currshortcut = main_list;

4348
	/* Read in and interpret characters. */
4349
4350
	do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished,
		TRUE);
Chris Allegretta's avatar
Chris Allegretta committed
4351
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
4352
    assert(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
4353
}