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

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
28
#include <setjmp.h>
Chris Allegretta's avatar
Chris Allegretta committed
29
30
31
32
33
34
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/param.h>
Chris Allegretta's avatar
Chris Allegretta committed
35
36
#include <sys/types.h>
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
38
39
40
#include <errno.h>
#include <ctype.h>
#include <locale.h>
#include <limits.h>
41
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "proto.h"
#include "nano.h"

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

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

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

57
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
58
static int fill = 0;	/* Fill - where to wrap lines, basically */
59
#endif
60
61
62
63
#ifndef DISABLE_WRAPPING
static int same_line_wrap = 0;	/* Whether wrapped text should be
				   prepended to the next line */
#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
69
static sigjmp_buf jmpbuf;	/* Used to return to mainloop after SIGWINCH */

Chris Allegretta's avatar
Chris Allegretta committed
70
/* What we do when we're all set to exit */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
71
RETSIGTYPE finish(int sigage)
Chris Allegretta's avatar
Chris Allegretta committed
72
73
74
75
{
    if (!ISSET(NO_HELP)) {
	mvwaddstr(bottomwin, 1, 0, hblank);
	mvwaddstr(bottomwin, 2, 0, hblank);
Chris Allegretta's avatar
Chris Allegretta committed
76
    } else
Chris Allegretta's avatar
Chris Allegretta committed
77
	mvwaddstr(bottomwin, 0, 0, hblank);
78

Chris Allegretta's avatar
Chris Allegretta committed
79
80
81
82
    wrefresh(bottomwin);
    endwin();

    /* Restore the old term settings */
Chris Allegretta's avatar
Chris Allegretta committed
83
    tcsetattr(0, TCSANOW, &oldterm);
Chris Allegretta's avatar
Chris Allegretta committed
84

85
86
87
88
89
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	save_history();
#endif

90
#ifdef DEBUG
91
    thanks_for_all_the_fish();
92
#endif
93

Chris Allegretta's avatar
Chris Allegretta committed
94
95
96
97
    exit(sigage);
}

/* Die (gracefully?) */
Chris Allegretta's avatar
Chris Allegretta committed
98
void die(const char *msg, ...)
Chris Allegretta's avatar
Chris Allegretta committed
99
100
101
{
    va_list ap;

102
103
104
    endwin();
    curses_ended = TRUE;

105
106
107
    /* Restore the old term settings */
    tcsetattr(0, TCSANOW, &oldterm);

Chris Allegretta's avatar
Chris Allegretta committed
108
109
110
    va_start(ap, msg);
    vfprintf(stderr, msg, ap);
    va_end(ap);
111

112
113
114
    /* save the currently loaded file if it's been modified */
    if (ISSET(MODIFIED))
	die_save_file(filename);
115

116
#ifdef ENABLE_MULTIBUFFER
117
    /* then save all of the other modified loaded files, if any */
118
    if (open_files != NULL) {
119
	openfilestruct *tmp;
120
121
122

	tmp = open_files;

123
	while (open_files->prev != NULL)
124
125
	    open_files = open_files->prev;

126
	while (open_files->next != NULL) {
127
128
129
130

	    /* if we already saved the file above (i. e. if it was the
	       currently loaded file), don't save it again */
	    if (tmp != open_files) {
Chris Allegretta's avatar
Chris Allegretta committed
131
132
133
134
		/* make sure open_files->fileage and fileage, and
		   open_files->filebot and filebot, are in sync; they
		   might not be if lines have been cut from the top or
		   bottom of the file */
135
		fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
136
		filebot = open_files->filebot;
137
		/* save the file if it's been modified */
Chris Allegretta's avatar
Chris Allegretta committed
138
		if (open_files->file_flags & MODIFIED)
139
		    die_save_file(open_files->filename);
140
141
142
143
144
145
	    }
	    open_files = open_files->next;
	}
    }
#endif

Chris Allegretta's avatar
Chris Allegretta committed
146
    exit(1); /* We have a problem: exit w/ errorlevel(1) */
147
148
}

Chris Allegretta's avatar
Chris Allegretta committed
149
void die_save_file(const char *die_filename)
150
{
Chris Allegretta's avatar
Chris Allegretta committed
151
    char *ret;
152
    int i = -1;
153

Chris Allegretta's avatar
Chris Allegretta committed
154
155
156
157
    /* If we can't save, we have REAL bad problems, but we might as well
       TRY. */
    if (die_filename[0] == '\0')
	ret = get_next_filename("nano.save");
158
    else {
159
	char *buf = charalloc(strlen(die_filename) + 6);
Chris Allegretta's avatar
Chris Allegretta committed
160

161
	strcpy(buf, die_filename);
162
	strcat(buf, ".save");
163
	ret = get_next_filename(buf);
Chris Allegretta's avatar
Chris Allegretta committed
164
	free(buf);
165
    }
Chris Allegretta's avatar
Chris Allegretta committed
166
167
    if (ret[0] != '\0')
	i = write_file(ret, 1, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
168

169
    if (i != -1)
Chris Allegretta's avatar
Chris Allegretta committed
170
	fprintf(stderr, _("\nBuffer written to %s\n"), ret);
171
    else
Chris Allegretta's avatar
Chris Allegretta committed
172
	fprintf(stderr, _("\nNo %s written (too many backup files?)\n"), ret);
173
174

    free(ret);
Chris Allegretta's avatar
Chris Allegretta committed
175
176
}

177
/* Die with an error message that the screen was too small if, well, the
Chris Allegretta's avatar
Chris Allegretta committed
178
 * screen is too small. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
179
void die_too_small(void)
180
{
Chris Allegretta's avatar
Chris Allegretta committed
181
    die(_("Window size is too small for nano...\n"));
182
183
}

Chris Allegretta's avatar
Chris Allegretta committed
184
185
186
187
188
void print_view_warning(void)
{
    statusbar(_("Key illegal in VIEW mode"));
}

189
/* Initialize global variables -- no better way for now.  If
Chris Allegretta's avatar
Chris Allegretta committed
190
 * save_cutbuffer is nonzero, don't set cutbuffer to NULL. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
191
void global_init(int save_cutbuffer)
Chris Allegretta's avatar
Chris Allegretta committed
192
193
194
{
    current_x = 0;
    current_y = 0;
195

196
197
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
198
199
	die_too_small();

Chris Allegretta's avatar
Chris Allegretta committed
200
    fileage = NULL;
201
202
    if (!save_cutbuffer)
	cutbuffer = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
203
204
205
206
    current = NULL;
    edittop = NULL;
    editbot = NULL;
    totlines = 0;
207
    totsize = 0;
Chris Allegretta's avatar
Chris Allegretta committed
208
    placewewant = 0;
209

210
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
211
    fill = wrap_at;
212
    if (fill <= 0)
Chris Allegretta's avatar
Chris Allegretta committed
213
	fill += COLS;
214
215
    if (fill < 0)
	fill = 0;
216
#endif
217

218
    hblank = charalloc(COLS + 1);
219
    memset(hblank, ' ', COLS);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220
    hblank[COLS] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
221
222
}

223
224
void window_init(void)
{
225
226
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS)
227
228
	die_too_small();

229
230
231
232
233
234
235
    if (edit != NULL)
	delwin(edit);
    if (topwin != NULL)
	delwin(topwin);
    if (bottomwin != NULL)
	delwin(bottomwin);

236
    /* Set up the main text window. */
237
238
    edit = newwin(editwinrows, COLS, 2, 0);

239
    /* And the other windows. */
240
241
242
    topwin = newwin(2, COLS, 0, 0);
    bottomwin = newwin(3 - no_help(), COLS, LINES - 3 + no_help(), 0);

243
    /* This is so the keypad still works after a Meta-X, for example. */
244
245
246
247
    keypad(edit, TRUE);
    keypad(bottomwin, TRUE);
}

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

#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)
{
264
    size_t allocsize = 1;	/* space needed for help_text */
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    char *ptr = NULL;
#ifndef NANO_SMALL
    const toggle *t;
#endif
    const shortcut *s;

    /* First set up the initial help text for the current function */
    if (currshortcut == whereis_list || currshortcut == replace_list
	     || currshortcut == replace_list_2)
	ptr = _("Search Command Help Text\n\n "
		"Enter the words or characters you would like to search "
		"for, then hit enter.  If there is a match for the text you "
		"entered, the screen will be updated to the location of the "
		"nearest match for the search string.\n\n "
279
280
281
282
		"The previous search string will be shown in brackets after "
		"the Search: prompt.  Hitting Enter without entering any text "
		"will perform the previous search.\n\n The following function "
		"keys are available in Search mode:\n\n");
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    else if (currshortcut == goto_list)
	ptr = _("Go To Line Help Text\n\n "
		"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)
	ptr = _("Insert File Help Text\n\n "
		"Type in the name of a file to be inserted into the current "
		"file buffer at the current cursor location.\n\n "
		"If you have compiled nano with multiple file buffer "
		"support, and enable multiple buffers with the -F "
		"or --multibuffer command line flags, the Meta-F toggle, or "
		"a nanorc file, inserting a file will cause it to be "
		"loaded into a separate buffer (use Meta-< and > to switch "
		"between file buffers).\n\n If you need another blank "
		"buffer, do not enter any filename, or type in a "
		"nonexistent filename at the prompt and press "
		"Enter.\n\n The following function keys are "
		"available in Insert File mode:\n\n");
    else if (currshortcut == writefile_list)
	ptr = _("Write File Help Text\n\n "
		"Type the name that you wish to save the current file "
		"as and hit Enter to save the file.\n\n If you have "
		"selected text with Ctrl-^, you will be prompted to "
		"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)
	ptr = _("File Browser Help Text\n\n "
		"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)
	ptr = _("Browser Go To Directory Help Text\n\n "
		"Enter the name of the directory you would like to "
		"browse to.\n\n If tab completion has not been disabled, "
		"you can use the TAB key to (attempt to) automatically "
		"complete the directory name.\n\n The following function "
		"keys are available in Browser Go To Directory mode:\n\n");
#endif
334
#ifndef DISABLE_SPELLER
335
336
337
338
339
340
341
342
343
    else if (currshortcut == spell_list)
	ptr = _("Spell Check Help Text\n\n "
		"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 "
		"current file.\n\n The following other functions are "
		"available in Spell Check mode:\n\n");
344
#endif
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
#ifndef NANO_SMALL
    else if (currshortcut == extcmd_list)
	ptr = _("External Command Help Text\n\n "
		"This menu allows you to insert the output of a command "
		"run by the shell into the current buffer (or a new "
		"buffer in multibuffer mode).\n\n The following keys are "
		"available in this mode:\n\n");
#endif
    else /* Default to the main help list */
	ptr = _(" nano help text\n\n "
	  "The nano editor is designed to emulate the functionality and "
	  "ease-of-use of the UW Pico text editor.  There are four main "
	  "sections of the editor: The top line shows the program "
	  "version, the current filename being edited, and whether "
	  "or not the file has been modified.  Next is the main editor "
	  "window showing the file being edited.  The status line is "
	  "the third line from the bottom and shows important messages. "
	  "The bottom two lines show the most 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
365
366
367
368
369
370
371
372
373
	  "sequences are notated with a caret (^) symbol and can be "
	  "entered either by using the Control (Ctrl) key or pressing the "
	  "Esc key twice.  Escape-key sequences are notated with the Meta "
	  "(M) symbol and can be entered using either the Esc, Alt or "
	  "Meta key depending on your keyboard setup.  Also, pressing Esc "
	  "twice and then typing a three-digit number from 000 to 255 "
	  "will enter the character with the corresponding ASCII code.  "
	  "The following keystrokes are available in the main editor "
	  "window.  Alternative keys are shown in parentheses:\n\n");
374

375
    allocsize += strlen(ptr);
376
377
378

    /* The space needed for the shortcut lists, at most COLS characters,
     * plus '\n'. */
379
    allocsize += (COLS + 1) * length_of_list(currshortcut);
380
381
382
383
384

#ifndef NANO_SMALL
    /* If we're on the main list, we also count the toggle help text. 
     * Each line has "M-%c\t\t\t", which fills 24 columns, plus at most
     * COLS - 24 characters, plus '\n'.*/
385
386
387
    if (currshortcut == main_list) {
	size_t endislen = strlen(_("enable/disable"));

388
	for (t = toggles; t != NULL; t = t->next)
389
390
	    allocsize += 8 + strlen(t->desc) + endislen;
    }
391
392
393
394
395
396
397
#endif /* !NANO_SMALL */

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

    /* Allocate space for the help text */
398
    help_text = charalloc(allocsize);
399
400
401
402
403
404
405

    /* Now add the text we want */
    strcpy(help_text, ptr);
    ptr = help_text + strlen(help_text);

    /* Now add our shortcut info */
    for (s = currshortcut; s != NULL; s = s->next) {
406
	/* true if the character in s->metaval is shown in first column */
407
408
	int meta_shortcut = 0;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
409
	if (s->val != NANO_NO_KEY) {
410
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
411
412
413
	    if (s->val == NANO_HISTORY_KEY)
		ptr += sprintf(ptr, "%.2s", _("Up"));
	    else
414
415
#endif
	    if (s->val == NANO_CONTROL_SPACE)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
416
		ptr += sprintf(ptr, "^%.5s", _("Space"));
417
418
419
420
421
422
	    else if (s->val == NANO_CONTROL_8)
		ptr += sprintf(ptr, "^?");
	    else
		ptr += sprintf(ptr, "^%c", s->val + 64);
	}
#ifndef NANO_SMALL
423
	else if (s->metaval != NANO_NO_KEY) {
424
	    meta_shortcut = 1;
425
	    if (s->metaval == NANO_ALT_SPACE)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
426
427
		ptr += snprintf(ptr, 8, "M-%.5s", _("Space"));
	    else
428
		ptr += sprintf(ptr, "M-%c", toupper(s->metaval));
429
	}
430
431
432
433
#endif

	*(ptr++) = '\t';

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
434
	if (s->func_key != NANO_NO_KEY)
435
	    ptr += sprintf(ptr, "(F%d)", s->func_key - KEY_F0);
436
437
438

	*(ptr++) = '\t';

439
440
	if (!meta_shortcut && s->metaval != NANO_NO_KEY)
	    ptr += sprintf(ptr, "(M-%c)", toupper(s->metaval));
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
441
	else if (meta_shortcut && s->misc != NANO_NO_KEY)
442
	    ptr += sprintf(ptr, "(M-%c)", toupper(s->misc));
443
444
445
446

	*(ptr++) = '\t';

	assert(s->help != NULL);
447
	ptr += sprintf(ptr, "%.*s\n", COLS > 24 ? COLS - 24 : 0, s->help);
448
449
450
451
452
453
454
    }

#ifndef NANO_SMALL
    /* And the toggles... */
    if (currshortcut == main_list)
	for (t = toggles; t != NULL; t = t->next) {
	    assert(t->desc != NULL);
455
	    ptr += sprintf(ptr, "M-%c\t\t\t%s %s\n", toupper(t->val), t->desc,
456
				_("enable/disable"));
457
458
459
460
461
	}
#endif /* !NANO_SMALL */

    /* If all went well, we didn't overwrite the allocated space for
       help_text. */
462
    assert(strlen(help_text) < allocsize);
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
}
#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;
    newnode->lineno = prevnode != NULL ? prevnode->lineno + 1 : 1;

    return newnode;
}

480
/* Make a copy of a node to a pointer (space will be malloc()ed). */
Chris Allegretta's avatar
Chris Allegretta committed
481
filestruct *copy_node(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
482
{
Chris Allegretta's avatar
Chris Allegretta committed
483
    filestruct *dst = (filestruct *)nmalloc(sizeof(filestruct));
Chris Allegretta's avatar
Chris Allegretta committed
484

Chris Allegretta's avatar
Chris Allegretta committed
485
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
486

Chris Allegretta's avatar
Chris Allegretta committed
487
    dst->data = charalloc(strlen(src->data) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
488
489
490
491
492
493
494
495
    dst->next = src->next;
    dst->prev = src->prev;
    strcpy(dst->data, src->data);
    dst->lineno = src->lineno;

    return dst;
}

496
497
498
499
500
501
502
503
504
505
506
507
508
/* Splice a node into an existing filestruct. */
void splice_node(filestruct *begin, filestruct *newnode, filestruct *end)
{
    if (newnode != NULL) {
	newnode->next = end;
	newnode->prev = begin;
    }
    if (begin != NULL)
	begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

509
/* Unlink a node from the rest of the filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
510
void unlink_node(const filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
511
{
Chris Allegretta's avatar
Chris Allegretta committed
512
    assert(fileptr != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
513

514
515
516
517
518
519
520
521
    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
522
void delete_node(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
523
{
Chris Allegretta's avatar
Chris Allegretta committed
524
525
526
527
528
    if (fileptr != NULL) {
	if (fileptr->data != NULL)
	    free(fileptr->data);
	free(fileptr);
    }
529
530
531
}

/* Okay, now let's duplicate a whole struct! */
Chris Allegretta's avatar
Chris Allegretta committed
532
filestruct *copy_filestruct(const filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
533
{
Chris Allegretta's avatar
Chris Allegretta committed
534
535
    filestruct *head;	/* copy of src, top of the copied list */
    filestruct *prev;	/* temp that traverses the list */
Chris Allegretta's avatar
Chris Allegretta committed
536

Chris Allegretta's avatar
Chris Allegretta committed
537
    assert(src != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
538

Chris Allegretta's avatar
Chris Allegretta committed
539
540
541
542
543
544
545
546
    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
547

Chris Allegretta's avatar
Chris Allegretta committed
548
	src = src->next;
Chris Allegretta's avatar
Chris Allegretta committed
549
550
    }

Chris Allegretta's avatar
Chris Allegretta committed
551
    prev->next = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
552
553
554
    return head;
}

555
/* Frees a filestruct. */
Chris Allegretta's avatar
Chris Allegretta committed
556
void free_filestruct(filestruct *src)
Chris Allegretta's avatar
Chris Allegretta committed
557
{
Chris Allegretta's avatar
Chris Allegretta committed
558
559
560
561
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_node(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
562
#ifdef DEBUG
563
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_node()");
Chris Allegretta's avatar
Chris Allegretta committed
564
#endif
Chris Allegretta's avatar
Chris Allegretta committed
565
566
	}
	delete_node(src);
567
#ifdef DEBUG
568
	fprintf(stderr, "%s: free'd last node.\n", "delete_node()");
569
570
571
572
#endif
    }
}

Chris Allegretta's avatar
Chris Allegretta committed
573
void renumber_all(void)
Chris Allegretta's avatar
Chris Allegretta committed
574
575
{
    filestruct *temp;
576
    int i = 1;
Chris Allegretta's avatar
Chris Allegretta committed
577

Chris Allegretta's avatar
Chris Allegretta committed
578
579
    assert(fileage == NULL || fileage != fileage->next);
    for (temp = fileage; temp != NULL; temp = temp->next)
Chris Allegretta's avatar
Chris Allegretta committed
580
581
582
	temp->lineno = i++;
}

Chris Allegretta's avatar
Chris Allegretta committed
583
void renumber(filestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
584
{
Chris Allegretta's avatar
Chris Allegretta committed
585
    if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage)
Chris Allegretta's avatar
Chris Allegretta committed
586
	renumber_all();
Chris Allegretta's avatar
Chris Allegretta committed
587
588
    else {
	int lineno = fileptr->prev->lineno;
589

Chris Allegretta's avatar
Chris Allegretta committed
590
591
592
593
	assert(fileptr != fileptr->next);
	for (; fileptr != NULL; fileptr = fileptr->next)
	    fileptr->lineno = ++lineno;
    }
Chris Allegretta's avatar
Chris Allegretta committed
594
595
}

596
/* Print one usage string to the screen, removes lots of duplicate 
Chris Allegretta's avatar
Chris Allegretta committed
597
598
 * strings to translate and takes out the parts that shouldn't be 
 * translatable (the flag names). */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
599
void print1opt(const char *shortflag, const char *longflag,
600
		const char *desc)
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
{
    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

    printf("%s\n", desc);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
617
void usage(void)
Chris Allegretta's avatar
Chris Allegretta committed
618
619
{
#ifdef HAVE_GETOPT_LONG
Chris Allegretta's avatar
Chris Allegretta committed
620
621
    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
622
#else
Chris Allegretta's avatar
Chris Allegretta committed
623
624
    printf(_("Usage: nano [+LINE] [option] [file]\n\n"));
    printf(_("Option\t\tMeaning\n"));
625
626
#endif /* HAVE_GETOPT_LONG */

627
    print1opt("-h, -?", "--help", _("Show this message"));
Chris Allegretta's avatar
Chris Allegretta committed
628
    print1opt(_("+LINE"), "", _("Start at line number LINE"));
629
#ifndef NANO_SMALL
630
    print1opt("-B", "--backup", _("Backup existing files on save"));
631
    print1opt("-D", "--dos", _("Write file in DOS format"));
632
#endif
633
#ifdef ENABLE_MULTIBUFFER
634
    print1opt("-F", "--multibuffer", _("Enable multiple file buffers"));
Chris Allegretta's avatar
Chris Allegretta committed
635
636
#endif
#ifdef ENABLE_NANORC
637
#ifndef NANO_SMALL
638
    print1opt("-H", "--historylog", _("Log & read search/replace string history"));
639
#endif
Chris Allegretta's avatar
Chris Allegretta committed
640
    print1opt("-I", "--ignorercfiles", _("Don't look at nanorc files"));
Chris Allegretta's avatar
Chris Allegretta committed
641
642
#endif
#ifndef NANO_SMALL
643
644
    print1opt("-M", "--mac", _("Write file in Mac format"));
    print1opt("-N", "--noconvert", _("Don't convert files from DOS/Mac format"));
645
646
#endif
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
647
    print1opt(_("-Q [str]"), _("--quotestr=[str]"), _("Quoting string, default \"> \""));
648
#endif
649
650
651
#ifdef HAVE_REGEX_H
    print1opt("-R", "--regexp", _("Do regular expression searches"));
#endif
652
#ifndef NANO_SMALL
653
    print1opt("-S", "--smooth", _("Smooth scrolling"));
654
#endif
655
    print1opt(_("-T [num]"), _("--tabsize=[num]"), _("Set width of a tab to num"));
656
    print1opt("-V", "--version", _("Print version information and exit"));
657
658
659
#ifdef ENABLE_COLOR
    print1opt(_("-Y [str]"), _("--syntax [str]"), _("Syntax definition to use"));
#endif
660
    print1opt("-c", "--const", _("Constantly show cursor position"));
661
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
662
    print1opt("-d", "--rebinddelete", _("Fix Backspace/Delete confusion problem"));
663
664
    print1opt("-i", "--autoindent", _("Automatically indent new lines"));
    print1opt("-k", "--cut", _("Let ^K cut from cursor to end of line"));
665
#endif
666
    print1opt("-l", "--nofollow", _("Don't follow symbolic links, overwrite"));
667
#ifndef DISABLE_MOUSE
668
    print1opt("-m", "--mouse", _("Enable mouse"));
Chris Allegretta's avatar
Chris Allegretta committed
669
#endif
670
#ifndef DISABLE_OPERATINGDIR
671
    print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), _("Set operating directory"));
Chris Allegretta's avatar
Chris Allegretta committed
672
#endif
Jordi Mallach's avatar
Jordi Mallach committed
673
    print1opt("-p", "--preserve", _("Preserve XON (^Q) and XOFF (^S) keys"));
674
#ifndef DISABLE_WRAPJUSTIFY
675
    print1opt(_("-r [#cols]"), _("--fill=[#cols]"), _("Set fill cols to (wrap lines at) #cols"));
676
#endif
677
#ifndef DISABLE_SPELLER
678
    print1opt(_("-s [prog]"), _("--speller=[prog]"), _("Enable alternate speller"));
679
#endif
680
681
    print1opt("-t", "--tempfile", _("Auto save on exit, don't prompt"));
    print1opt("-v", "--view", _("View (read only) mode"));
682
#ifndef DISABLE_WRAPPING
683
    print1opt("-w", "--nowrap", _("Don't wrap long lines"));
Chris Allegretta's avatar
Chris Allegretta committed
684
#endif
685
686
    print1opt("-x", "--nohelp", _("Don't show help window"));
    print1opt("-z", "--suspend", _("Enable suspend"));
Chris Allegretta's avatar
Chris Allegretta committed
687
688
689

    /* this is a special case */
    printf(" %s\t\t\t%s\n","-a, -b, -e, -f, -g, -j", _("(ignored, for Pico compatibility)"));
690

Chris Allegretta's avatar
Chris Allegretta committed
691
692
693
    exit(0);
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
694
void version(void)
Chris Allegretta's avatar
Chris Allegretta committed
695
{
Chris Allegretta's avatar
Chris Allegretta committed
696
    printf(_(" GNU nano version %s (compiled %s, %s)\n"),
Chris Allegretta's avatar
Chris Allegretta committed
697
	   VERSION, __TIME__, __DATE__);
698
    printf(_
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
699
	   (" Email: nano@nano-editor.org	Web: http://www.nano-editor.org/"));
700
    printf(_("\n Compiled options:"));
701

702
703
704
#ifndef ENABLE_NLS
    printf(" --disable-nls");
#endif
705
706
707
#ifdef DEBUG
    printf(" --enable-debug");
#endif
708
709
#ifdef NANO_EXTRA
    printf(" --enable-extra");
710
#endif
711
712
713
#ifdef NANO_SMALL
    printf(" --enable-tiny");
#else
714
#ifdef DISABLE_BROWSER
715
    printf(" --disable-browser");
716
#endif
717
718
#ifdef DISABLE_HELP
    printf(" --disable-help");
719
720
#endif
#ifdef DISABLE_JUSTIFY
721
    printf(" --disable-justify");
722
#endif
723
#ifdef DISABLE_MOUSE
724
    printf(" --disable-mouse");
725
#endif
726
727
728
#ifdef DISABLE_OPERATINGDIR
    printf(" --disable-operatingdir");
#endif
729
730
731
732
733
734
735
736
737
738
#ifdef DISABLE_SPELLER
    printf(" --disable-speller");
#endif
#ifdef DISABLE_TABCOMP
    printf(" --disable-tabcomp");
#endif
#endif /* NANO_SMALL */
#ifdef DISABLE_WRAPPING
    printf(" --disable-wrapping");
#endif
739
740
741
#ifdef DISABLE_ROOTWRAP
    printf(" --disable-wrapping-as-root");
#endif
742
743
744
745
746
747
748
749
750
#ifdef ENABLE_COLOR
    printf(" --enable-color");
#endif
#ifdef ENABLE_MULTIBUFFER
    printf(" --enable-multibuffer");
#endif
#ifdef ENABLE_NANORC
    printf(" --enable-nanorc");
#endif
751
752
753
754
#ifdef USE_SLANG
    printf(" --with-slang");
#endif
    printf("\n");
Chris Allegretta's avatar
Chris Allegretta committed
755
756
}

757
758
759
/* Stuff we do when we abort from programs and want to clean up the
 * screen.  This doesn't do much right now. */
void do_early_abort(void)
Chris Allegretta's avatar
Chris Allegretta committed
760
{
761
    blank_statusbar_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
762
763
764
765
}

int no_help(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
766
    return ISSET(NO_HELP) ? 2 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
767
768
}

Chris Allegretta's avatar
Chris Allegretta committed
769
#if defined(DISABLE_JUSTIFY) || defined(DISABLE_SPELLER) || defined(DISABLE_HELP) || defined(NANO_SMALL)
770
771
void nano_disabled_msg(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
772
    statusbar(_("Sorry, support for this function has been disabled"));
773
}
774
#endif
775

Chris Allegretta's avatar
Chris Allegretta committed
776
#ifndef NANO_SMALL
777
static int pid;		/* This is the PID of the newly forked process
778
779
780
			 * below.  It must be global since the signal
			 * handler needs it. */
RETSIGTYPE cancel_fork(int signal)
Chris Allegretta's avatar
Chris Allegretta committed
781
{
782
783
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
Chris Allegretta's avatar
Chris Allegretta committed
784
785
}

786
int open_pipe(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
787
{
788
789
790
791
792
793
    int fd[2];
    FILE *f;
    struct sigaction oldaction, newaction;
			/* original and temporary handlers for SIGINT */
#ifdef _POSIX_VDISABLE
    struct termios term, newterm;
794
#endif /* _POSIX_VDISABLE */
795
    int cancel_sigs = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
796
797
798
799
    /* cancel_sigs == 1 means that sigaction() failed without changing
     * the signal handlers.  cancel_sigs == 2 means the signal handler
     * was changed, but the tcsetattr didn't succeed.
     *
800
801
     * I 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
802

803
    /* Make our pipes. */
804

805
806
807
    if (pipe(fd) == -1) {
	statusbar(_("Could not pipe"));
	return 1;
808
    }
809

810
    /* Fork a child. */
811

812
813
814
815
816
817
    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. */
      
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
818
	execl("/bin/sh", "sh", "-c", command, 0);
819
820
	exit(0);
    }
821

822
823
824
825
826
827
828
829
    /* Else continue as parent. */

    close(fd[1]);

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

832
833
    /* Before we start reading the forked command's output, we set
     * things up so that ^C will cancel the new process. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
834
    if (sigaction(SIGINT, NULL, &newaction) == -1) {
835
836
	cancel_sigs = 1;
	nperror("sigaction");
Chris Allegretta's avatar
Chris Allegretta committed
837
    } else {
838
	newaction.sa_handler = cancel_fork;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
839
	if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
840
841
842
	    cancel_sigs = 1;
	    nperror("sigaction");
	}
Chris Allegretta's avatar
Chris Allegretta committed
843
    }
844
845
    /* Note that now oldaction is the previous SIGINT signal handler,
     * to be restored later. */
846

847
848
849
    /* See if the platform supports disabling individual control
     * characters. */
#ifdef _POSIX_VDISABLE
850
    if (cancel_sigs == 0 && tcgetattr(0, &term) == -1) {
851
852
	cancel_sigs = 2;
	nperror("tcgetattr");
Chris Allegretta's avatar
Chris Allegretta committed
853
    }
854
    if (cancel_sigs == 0) {
855
856
857
858
859
860
	newterm = term;
	/* Grab oldterm's VINTR key :-) */
	newterm.c_cc[VINTR] = oldterm.c_cc[VINTR];
	if (tcsetattr(0, TCSANOW, &newterm) == -1) {
	    cancel_sigs = 2;
	    nperror("tcsetattr");
Chris Allegretta's avatar
Chris Allegretta committed
861
	}
862
    }
863
864
865
#endif   /* _POSIX_VDISABLE */

    f = fdopen(fd[0], "rb");
866
    if (f == NULL)
867
868
869
870
871
872
873
874
875
876
877
878
      nperror("fdopen");
    
    read_file(f, "stdin", 0);
    /* if multibuffer mode is on, we could be here in view mode; if so,
       don't set the modification flag */
    if (!ISSET(VIEW_MODE))
	set_modified();

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

#ifdef _POSIX_VDISABLE
879
    if (cancel_sigs == 0 && tcsetattr(0, TCSANOW, &term) == -1)
880
881
882
	nperror("tcsetattr");
#endif   /* _POSIX_VDISABLE */

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
883
    if (cancel_sigs != 1 && sigaction(SIGINT, &oldaction, NULL) == -1)
884
885
	nperror("sigaction");

886
887
    return 0;
}
888
#endif /* NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
889

890
#ifndef DISABLE_MOUSE
891
void do_mouse(void)
Chris Allegretta's avatar
Chris Allegretta committed
892
{
893
    int mouse_x, mouse_y;
Chris Allegretta's avatar
Chris Allegretta committed
894

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
895
    if (get_mouseinput(&mouse_x, &mouse_y, 1) == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
896

897
898
899
900
901
902
903
904
	/* Click in the edit window to move the cursor, but only when
	   we're not in a subfunction. */
	if (wenclose(edit, mouse_y, mouse_x) && currshortcut == main_list) {
	    int 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. */
905

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

910
	    sameline = (mouse_y == current_y);
911

912
913
914
915
916
	    /* 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;
917

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

921
922
923
924
925
926
927
928
929
	    /* Selecting where the cursor is toggles the mark.  As does
	       selecting 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;
		}
		do_mark();
930
	    }
Chris Allegretta's avatar
Chris Allegretta committed
931

932
933
934
935
	    current_x = xcur;
	    placewewant = xplustabs();
	    edit_refresh();
	}
Chris Allegretta's avatar
Chris Allegretta committed
936
    }
937
938
    /* FIXME: If we clicked on a location in the statusbar, the cursor
       should move to the location we clicked on. */
939
}
Chris Allegretta's avatar
Chris Allegretta committed
940
#endif
941

942
/* The user typed a character; add it to the edit buffer. */
943
944
945
946
947
948
949
950
void do_char(char ch)
{
    size_t current_len = strlen(current->data);
#if !defined(DISABLE_WRAPPING) || defined(ENABLE_COLOR)
    int refresh = 0;
	/* Do we have to run edit_refresh(), or can we get away with
	 * update_line()? */
#endif
951

952
953
954
955
956
957
958
959
960
961
962
963
    if (ch == '\0')		/* Null to newline, if needed. */
	ch = '\n';
    else if (ch == '\n') {	/* Newline to Enter, if needed. */
	do_enter();
	return;
    }

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

    /* When a character is inserted on the current magicline, it means
     * we need a new one! */
    if (filebot == current) {
964
965
966
	new_magicline();
	fix_editbot();
    }
Chris Allegretta's avatar
Chris Allegretta committed
967

968
    /* More dangerousness fun =) */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
969
    current->data = charealloc(current->data, current_len + 2);
970
    assert(current_x <= current_len);
971
972
    charmove(&current->data[current_x + 1], &current->data[current_x],
	current_len - current_x + 1);
973
974
975
    current->data[current_x] = ch;
    totsize++;
    set_modified();
976

Chris Allegretta's avatar
Chris Allegretta committed
977
#ifndef NANO_SMALL
978
    /* Note that current_x has not yet been incremented. */
979
980
    if (current == mark_beginbuf && current_x < mark_beginx)
	mark_beginx++;
Chris Allegretta's avatar
Chris Allegretta committed
981
#endif
982

983
    do_right();
984

985
986
987
988
#ifndef DISABLE_WRAPPING
    if (!ISSET(NO_WRAP) && ch != '\t')
	refresh = do_wrap(current);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
989

990
991
992
#ifdef ENABLE_COLOR
    refresh = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
993

994
995
996
997
#if !defined(DISABLE_WRAPPING) || defined(ENABLE_COLOR)
    if (refresh)
	edit_refresh();
#endif
Chris Allegretta's avatar
Chris Allegretta committed
998
999
}

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
int do_verbatim_input(void)
{
    char *verbatim_kbinput;	/* Used to hold verbatim input */
    int verbatim_len;		/* Length of verbatim input */
    int old_preserve = ISSET(PRESERVE), old_suspend = ISSET(SUSPEND);
    int i;

    /* Turn off Ctrl-Q (XON), Ctrl-S (XOFF), and Ctrl-Z (suspend) if
     * they're on, so that we can use them to insert ^Q, ^S, and ^Z
     * verbatim. */
    if (old_preserve)
	UNSET(PRESERVE);
    if (old_suspend)
	UNSET(SUSPEND);
    if (old_preserve || old_suspend)
	signal_init();

    statusbar(_("Verbatim input"));
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1018
    verbatim_kbinput = get_verbatim_kbinput(edit, &verbatim_len, 1);
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041

    /* Turn on DISABLE_CURPOS while inserting character(s) and turn it
     * off afterwards, so that if constant cursor position display is
     * on, it will be updated properly. */
    SET(DISABLE_CURPOS);
    for (i = 0; i < verbatim_len; i++)
	do_char(verbatim_kbinput[i]);
    UNSET(DISABLE_CURPOS);

    free(verbatim_kbinput);

    /* Turn Ctrl-Q, Ctrl-S, and Ctrl-Z back on if they were on
     * before. */
    if (old_preserve)
	SET(PRESERVE);
    if (old_suspend)
	SET(SUSPEND);
    if (old_preserve || old_suspend)
	signal_init();

    return 1;
}

Chris Allegretta's avatar
Chris Allegretta committed
1042
1043
int do_backspace(void)
{
1044
    if (current != fileage || current_x > 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1045
	do_left();
1046
	do_delete();
Chris Allegretta's avatar
Chris Allegretta committed
1047
1048
1049
1050
1051
1052
    }
    return 1;
}

int do_delete(void)
{
Chris Allegretta's avatar
Chris Allegretta committed
1053
1054
    int refresh = 0;

1055
1056
1057
    /* blbf -> blank line before filebot (see below) */
    int blbf = 0;

Chris Allegretta's avatar
Chris Allegretta committed
1058
    if (current->next == filebot && current->data[0] == '\0')
1059
1060
	blbf = 1;

Chris Allegretta's avatar
Chris Allegretta committed
1061
1062
    placewewant = xplustabs();

Chris Allegretta's avatar
Chris Allegretta committed
1063
1064
    if (current_x != strlen(current->data)) {
	/* Let's get dangerous */
1065
	charmove(&current->data[current_x], &current->data[current_x + 1],
Chris Allegretta's avatar
Chris Allegretta committed
1066
1067
1068
		strlen(current->data) - current_x);

	align(&current->data);
Chris Allegretta's avatar
Chris Allegretta committed
1069
1070
1071
#ifdef ENABLE_COLOR
	refresh = 1;
#endif
1072
    } else if (current->next != NULL && (current->next != filebot || blbf)) {
1073
	/* We can delete the line before filebot only if it is blank: it
Chris Allegretta's avatar
Chris Allegretta committed
1074
	   becomes the new magic line then. */
1075
1076
1077

	filestruct *foo;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1078
	current->data = charealloc(current->data,
Chris Allegretta's avatar
Chris Allegretta committed
1079
1080
1081
1082
1083
				 strlen(current->data) +
				 strlen(current->next->data) + 1);
	strcat(current->data, current->next->data);

	foo = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
1084
	if (filebot == foo) {
Chris Allegretta's avatar
Chris Allegretta committed
1085
1086
1087
1088
1089
1090
1091
1092
	    filebot = current;
	    editbot = current;
	}

	unlink_node(foo);
	delete_node(foo);
	renumber(current);
	totlines--;
Chris Allegretta's avatar
Chris Allegretta committed
1093
	refresh = 1;
Chris Allegretta's avatar
Chris Allegretta committed
1094
1095
1096
1097
1098
    } else
	return 0;

    totsize--;
    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
1099
1100
1101
    update_line(current, current_x);
    if (refresh)
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1102
1103
1104
    return 1;
}

1105
int do_tab(void)
Chris Allegretta's avatar
Chris Allegretta committed
1106
{
1107
1108
    do_char('\t');
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
1109
1110
}

1111
1112
/* Someone hits return *gasp!* */
int do_enter(void)
Chris Allegretta's avatar
Chris Allegretta committed
1113
{
1114
1115
    filestruct *newnode;
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
1116

1117
1118
1119
    newnode = make_new_node(current);
    assert(current != NULL && current->data != NULL);
    tmp = &current->data[current_x];
1120

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1121
#ifndef NANO_SMALL
1122
1123
1124
1125
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
	int extra = 0;
	const char *spc = current->data;
1126

1127
1128
1129
	while (*spc == ' ' || *spc == '\t') {
	    extra++;
	    spc++;
1130
	}
1131
1132
1133
1134
1135
1136
1137
1138
	/* If current_x < extra, then we are breaking the line in the
	 * indentation.  Autoindenting should add only current_x
	 * characters of indentation. */
	if (current_x < extra)
	    extra = current_x;
	else
	    current_x = extra;
	totsize += extra;
Chris Allegretta's avatar
Chris Allegretta committed
1139

1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
	newnode->data = charalloc(strlen(tmp) + extra + 1);
	strncpy(newnode->data, current->data, extra);
	strcpy(&newnode->data[extra], tmp);
    } else 
#endif
    {
	current_x = 0;
	newnode->data = charalloc(strlen(tmp) + 1);
	strcpy(newnode->data, tmp);
    }
    *tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1151

1152
1153
1154
1155
1156
    if (current->next == NULL) {
	filebot = newnode;
	editbot = newnode;
    }
    splice_node(current, newnode, current->next);
1157

1158
1159
1160
1161
    totsize++;
    renumber(current);
    current = newnode;
    align(&current->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1162

1163
1164
1165
1166
1167
1168
1169
    /* The logic here is as follows:
     *    -> If we are at the bottom of the buffer, we want to recenter
     *       (read: rebuild) the screen and forcibly move the cursor.
     *    -> otherwise, we want simply to redraw the screen and update
     *       where we think the cursor is.
     */
    if (current_y == editwinrows - 1) {
1170
1171
1172
1173
1174
1175
#ifndef NANO_SMALL
	if (ISSET(SMOOTHSCROLL))
	    edit_update(current, NONE);
	else
#endif
	    edit_update(current, CENTER);
1176
1177
1178
1179
1180
1181
	reset_cursor();
    } else {
	current_y++;
	edit_refresh();
	update_cursor();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1182

1183
1184
    totlines++;
    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
1185

1186
1187
    placewewant = xplustabs();
    return 1;
Chris Allegretta's avatar
Chris Allegretta committed
1188
1189
}

1190
1191
#ifndef NANO_SMALL
int do_next_word(void)
Chris Allegretta's avatar
Chris Allegretta committed
1192
{
1193
    filestruct *old = current;
Chris Allegretta's avatar
Chris Allegretta committed
1194

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

1197
1198
1199
1200
    /* Skip letters in this word first. */
    while (current->data[current_x] != '\0' &&
	    isalnum((int)current->data[current_x]))
	current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1201

1202
1203
1204
1205
    for (; current != NULL; current = current->next) {
	while (current->data[current_x] != '\0' &&
		!isalnum((int)current->data[current_x]))
	    current_x++;
Chris Allegretta's avatar
Chris Allegretta committed
1206

1207
1208
	if (current->data[current_x] != '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1209

1210
1211
1212
1213
	current_x = 0;
    }
    if (current == NULL)
	current = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1214

1215
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1216

1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
    if (current->lineno >= editbot->lineno) {
	/* If we're on the last line, don't center the screen. */
	if (current->lineno == filebot->lineno)
	    edit_refresh();
	else
	    edit_update(current, CENTER);
    }
    else {
	/* If we've jumped lines, refresh the old line.  We can't just
	   use current->prev here, because we may have skipped over some
	   blank lines, in which case the previous line is the wrong
	   one. */
	if (current != old) {
	    update_line(old, 0);
	    /* If the mark was set, then the lines between old and
	       current have to be updated too. */
	    if (ISSET(MARK_ISSET)) {
		while (old->next != current) {
		    old = old->next;
		    update_line(old, 0);
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1239
	}
1240
	update_line(current, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
1241
    }
1242
1243
    return 0;
}
Chris Allegretta's avatar
Chris Allegretta committed
1244

1245
1246
1247
1248
/* The same thing for backwards. */
int do_prev_word(void)
{
    filestruct *old = current;
Chris Allegretta's avatar
Chris Allegretta committed
1249

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

1252
1253
1254
    /* Skip letters in this word first. */
    while (current_x >= 0 && isalnum((int)current->data[current_x]))
	current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1255

1256
1257
1258
    for (; current != NULL; current = current->prev) {
	while (current_x >= 0 && !isalnum((int)current->data[current_x]))
	    current_x--;
Chris Allegretta's avatar
Chris Allegretta committed
1259

1260
1261
	if (current_x >= 0)
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
1262

1263
1264
	if (current->prev != NULL)
	    current_x = strlen(current->prev->data);
Chris Allegretta's avatar
Chris Allegretta committed
1265
1266
    }

1267
1268
1269
1270
1271
1272
    if (current != NULL) {
	while (current_x > 0 && isalnum((int)current->data[current_x - 1]))
	    current_x--;
    } else {
	current = fileage;
	current_x = 0;
1273
    }
Chris Allegretta's avatar
Chris Allegretta committed
1274

1275
    placewewant = xplustabs();
Chris Allegretta's avatar
Chris Allegretta committed
1276

1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
    if (current->lineno <= edittop->lineno) {
	/* If we're on the first line, don't center the screen. */
	if (current->lineno == fileage->lineno)
	    edit_refresh();
	else
	    edit_update(current, CENTER);
    }
    else {
	/* If we've jumped lines, refresh the old line.  We can't just
	   use current->prev here, because we may have skipped over some
	   blank lines, in which case the previous line is the wrong
	   one. */
	if (current != old) {
	    update_line(old, 0);
	    /* If the mark was set, then the lines between old and
	       current have to be updated too. */
	    if (ISSET(MARK_ISSET)) {
		while (old->prev != current) {
		    old = old->prev;
		    update_line(old, 0);
1297
		}
Chris Allegretta's avatar
Chris Allegretta committed
1298
1299
	    }
	}
1300
	update_line(current, current_x);
Chris Allegretta's avatar
Chris Allegretta committed
1301
    }
1302
1303
1304
    return 0;
}
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1305

1306
1307
1308
1309
1310
int do_mark(void)
{
#ifdef NANO_SMALL
    nano_disabled_msg();
#else
1311
1312
    TOGGLE(MARK_ISSET);
    if (ISSET(MARK_ISSET)) {
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
	statusbar(_("Mark Set"));
	mark_beginbuf = current;
	mark_beginx = current_x;
    } else {
	statusbar(_("Mark UNset"));
	edit_refresh();
    }
#endif
    return 1;
}
Chris Allegretta's avatar
Chris Allegretta committed
1323

1324
#ifndef DISABLE_WRAPPING
1325
1326
void wrap_reset(void)
{
1327
    same_line_wrap = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1328
}
1329
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1330

1331
1332
1333
1334
1335
#ifndef DISABLE_WRAPPING
/* We wrap the given line.  Precondition: we assume the cursor has been 
 * moved forward since the last typed character.  Return value:
 * whether we wrapped. */
int do_wrap(filestruct *inptr)
Chris Allegretta's avatar
Chris Allegretta committed
1336
{
1337
1338
1339
1340
    size_t len = strlen(inptr->data);	/* length of the line we wrap */
    int i = 0;			/* generic loop variable */
    int wrap_loc = -1;		/* index of inptr->data where we wrap */
    int word_back = -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1341
#ifndef NANO_SMALL
1342
1343
1344
    const char *indentation = NULL;
	/* indentation to prepend to the new line */
    int indent_len = 0;		/* strlen(indentation) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1345
#endif
1346
1347
1348
1349
1350
1351
1352
1353
    const char *after_break;	/* text after the wrap point */
    int after_break_len;	/* strlen(after_break) */
    int wrapping = 0;		/* do we prepend to the next line? */
    const char *wrap_line = NULL;
	/* the next line, minus indentation */
    int wrap_line_len = 0;	/* strlen(wrap_line) */
    char *newline = NULL;	/* the line we create */
    int new_line_len = 0;	/* eventual length of newline */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1354

1355
1356
/* 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
1357

1358
1359
/* Step 1, finding where to wrap.  We are going to add a new-line
 * after a white-space character.  In this step, we set wrap_loc as the
1360
1361
1362
1363
1364
1365
 * location of this replacement.
 *
 * Where should we break the line?  We need the last "legal wrap point"
 * 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.
 *
1366
1367
 * A "legal wrap point" is a white-space character that is not followed by
 * white-space.
1368
1369
1370
1371
1372
1373
1374
1375
1376
 *
 * 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!
 *
 * Note that the code below could be optimised, by not calling strnlenpt()
 * so often. */
1377

1378
1379
1380
1381
1382
#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
1383
    for (; i < len; i++, wrap_line++) {
1384
1385
1386
1387
1388
1389
1390
1391
	/* record where the last word ended */
	if (*wrap_line != ' ' && *wrap_line != '\t')
	    word_back = i;
	/* if we have found a "legal wrap point" and the current word
	 * extends too far, then we stop */
	if (wrap_loc != -1 && strnlenpt(inptr->data, word_back + 1) > fill)
	    break;
	/* we record the latest "legal wrap point" */
1392
	if (word_back != i && wrap_line[1] != ' ' && wrap_line[1] != '\t')
1393
	    wrap_loc = i;
1394
    }
1395
1396
    if (wrap_loc < 0 || i == len)
	return 0;
Chris Allegretta's avatar
Chris Allegretta committed
1397

1398
1399
1400
/* 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
1401

1402
1403
1404
1405
    /* after_break is the text that will be moved to the next line. */
    after_break = inptr->data + wrap_loc + 1;
    after_break_len = len - wrap_loc - 1;
    assert(after_break_len == strlen(after_break));
Chris Allegretta's avatar
Chris Allegretta committed
1406

1407
1408
1409
    /* 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
1410

1411
1412
1413
    /* 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. */
1414
    if (same_line_wrap && inptr->next) {
1415
1416
	wrap_line = inptr->next->data;
	wrap_line_len = strlen(wrap_line);
Chris Allegretta's avatar
Chris Allegretta committed
1417

1418
1419
1420
1421
1422
1423
	/* +1 for the space between after_break and wrap_line */
	if ((new_line_len + 1 + wrap_line_len) <= fill) {
	    wrapping = 1;
	    new_line_len += (1 + wrap_line_len);
	}
    }
1424

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1425
#ifndef NANO_SMALL
1426
    if (ISSET(AUTOINDENT)) {
1427
1428
	/* Indentation comes from the next line if wrapping, else from
	 * this line. */
1429
1430
1431
	indentation = (wrapping ? wrap_line : inptr->data);
	indent_len = indent_length(indentation);
	if (wrapping)
1432
1433
	    /* The wrap_line text should not duplicate indentation.
	     * Note in this case we need not increase new_line_len. */
1434
1435
1436
	    wrap_line += indent_len;
	else
	    new_line_len += indent_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1437
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1438
1439
#endif

1440
1441
1442
    /* Now we allocate the new line and copy into it. */
    newline = charalloc(new_line_len + 1);  /* +1 for \0 */
    *newline = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
1443

1444
1445
1446
1447
#ifndef NANO_SMALL
    if (ISSET(AUTOINDENT)) {
	strncpy(newline, indentation, indent_len);
	newline[indent_len] = '\0';
1448
    }
1449
#endif
1450
    strcat(newline, after_break);
1451
    /* We end the old line after wrap_loc.  Note this does not eat the
1452
     * space. */
1453
1454
    null_at(&inptr->data, wrap_loc + 1);
    totsize++;
1455
    if (wrapping) {
1456
	/* In this case, totsize increases by 1 since we add a space
1457
	 * between after_break and wrap_line.  If the line already ends
1458
1459
	 * in a tab or a space, we don't add a space and decrement
	 * totsize to account for that. */
1460
	if (!isspace((int) newline[strlen(newline) - 1]))
1461
1462
1463
	    strcat(newline, " ");
	else
	    totsize--;
1464
1465
1466
1467
1468
	strcat(newline, wrap_line);
	free(inptr->next->data);
	inptr->next->data = newline;
    } else {
	filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct));
1469

1470
	/* In this case, the file size changes by +1 for the new line, and
1471
	 * +indent_len for the new indentation. */
1472
1473
1474
1475
1476
1477
1478
1479
#ifndef NANO_SMALL
	totsize += indent_len;
#endif
	totlines++;
	temp->data = newline;
	temp->prev = inptr;
	temp->next = inptr->next;
	temp->prev->next = temp;
1480
1481
1482
	/* If temp->next is NULL, then temp is the last line of the
	 * file, so we must set filebot. */
	if (temp->next != NULL)
1483
1484
1485
1486
	    temp->next->prev = temp;
	else
	    filebot = temp;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1487

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

1491
    /* later wraps of this line will be prepended to the next line. */
1492
    same_line_wrap = 1;
Chris Allegretta's avatar
Chris Allegretta committed
1493

1494
1495
1496
1497
    /* 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
1498

1499
1500
1501
1502
1503
1504
    /* 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 +
1505
#endif
1506
1507
1508
1509
		wrap_loc + 1;
	wrap_reset();
	placewewant = xplustabs();
    }
Chris Allegretta's avatar
Chris Allegretta committed
1510

1511
#ifndef NANO_SMALL
1512
1513
1514
1515
1516
1517
1518
1519
1520
    /* 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 must move it
     * 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 */
1521

1522
1523
1524
1525
    /* Place the cursor. */
    reset_cursor();

    return 1;
1526
}
1527
#endif /* !DISABLE_WRAPPING */
1528

1529
#ifndef DISABLE_SPELLER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1530
1531
/* A word is misspelled in the file.  Let the user replace it.  We
 * return False if the user cancels. */
1532
int do_int_spell_fix(const char *word)
1533
{
1534
1535
    char *save_search;
    char *save_replace;
1536
1537
1538
1539
1540
1541
1542
    filestruct *current_save = current;
    int current_x_save = current_x;
    filestruct *edittop_save = edittop;
	/* Save where we are. */
    int i = 0;
	/* The return value. */
    int reverse_search_set = ISSET(REVERSE_SEARCH);
1543
#ifndef NANO_SMALL
1544
1545
    int case_sens_set = ISSET(CASE_SENSITIVE);
    int mark_set = ISSET(MARK_ISSET);
1546

1547
    SET(CASE_SENSITIVE);
1548
1549
1550
    /* Make sure the marking highlight is off during Spell Check */
    UNSET(MARK_ISSET);
#endif
1551
1552
    /* Make sure Spell Check goes forward only */
    UNSET(REVERSE_SEARCH);
1553

1554
1555
1556
1557
    /* save the current search/replace strings */
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;
1558

1559
1560
1561
    /* set search/replace strings to mis-spelt word */
    last_search = mallocstrcpy(NULL, word);
    last_replace = mallocstrcpy(NULL, word);
1562

1563
1564
    /* start from the top of file */
    current = fileage;
1565
    current_x = -1;
1566

1567
    search_last_line = FALSE;
1568

1569
1570
1571
    /* We find the first whole-word occurrence of word.  We call
       findnextstr() with bracket_mode set to TRUE in order to disable
       search wrapping. */
1572
    while (findnextstr(TRUE, TRUE, fileage, -1, word, 0))
1573
1574
	if (is_whole_word(current_x, current->data, word)) {
	    edit_refresh();
1575

1576
	    do_replace_highlight(TRUE, word);
1577

1578
	    /* allow replace word to be corrected */
1579
	    i = statusq(0, spell_list, word,
Chris Allegretta's avatar
Chris Allegretta committed
1580
#ifndef NANO_SMALL
1581
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
1582
#endif
1583
			 _("Edit a replacement"));
1584

1585
	    do_replace_highlight(FALSE, word);
1586

1587
1588
	    if (i != -1 && strcmp(word, answer)) {
		int j = 0;
1589

1590
1591
1592
		search_last_line = FALSE;
		current_x--;
		do_replace_loop(word, current_save, &current_x_save, TRUE, &j);
1593
	    }
1594
1595

	    break;
1596
	}
Chris Allegretta's avatar
Chris Allegretta committed
1597

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

1602
    /* restore where we were */
1603
1604
1605
    current = current_save;
    current_x = current_x_save;
    edittop = edittop_save;
Chris Allegretta's avatar
Chris Allegretta committed
1606

1607
1608
1609
    /* restore Search/Replace direction */
    if (reverse_search_set)
	SET(REVERSE_SEARCH);
1610

1611
#ifndef NANO_SMALL
1612
1613
1614
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);

1615
1616
1617
    /* restore marking highlight */
    if (mark_set)
	SET(MARK_ISSET);
1618
1619
#endif

1620
    return i != -1;
Chris Allegretta's avatar
Chris Allegretta committed
1621
1622
}

1623
1624
/* Integrated spell checking using 'spell' program.  Return value: NULL
 * for normal termination, otherwise the error string. */
1625
char *do_int_speller(char *tempfile_name)
Chris Allegretta's avatar
Chris Allegretta committed
1626
{
1627
1628
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1629
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1630
1631
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
Chris Allegretta's avatar
Chris Allegretta committed
1632

1633
    /* Create all three pipes up front */
Chris Allegretta's avatar
Chris Allegretta committed
1634

1635
1636
    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
1637

1638
    statusbar(_("Creating misspelled word list, please wait..."));
1639
    /* A new process to run spell in */
1640

1641
    if ((pid_spell = fork()) == 0) {
1642

1643
	/* Child continues, (i.e. future spell process) */
1644

1645
	close(spell_fd[0]);
1646

1647
	/* replace the standard in with the tempfile */
1648
1649
1650
1651
1652
1653
	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;

1654
	close(tempfile_fd);
1655

1656
	/* send spell's standard out to the pipe */
1657
1658
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1659

1660
	close(spell_fd[1]);
1661

1662
1663
	/* Start spell program, we are using the PATH here!?!? */
	execlp("spell", "spell", NULL);
1664

1665
1666
	/* Should not be reached, if spell is found!!! */
	exit(1);
1667
    }
Chris Allegretta's avatar
Chris Allegretta committed
1668

1669
    /* Parent continues here */
1670
1671
1672
1673
1674
1675
1676
    close(spell_fd[1]);

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

	/* Child continues, (i.e. future spell process) */
	/* replace the standard in with output of the old pipe */
1677
1678
1679
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1680
1681
1682
	close(spell_fd[0]);

	/* send sort's standard out to the new pipe */
1683
1684
	if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695

	close(sort_fd[1]);

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

	/* Should not be reached, if sort is found */
	exit(1);
    }

1696
    close(spell_fd[0]);
1697
1698
1699
1700
1701
1702
1703
    close(sort_fd[1]);

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

	/* Child continues, (i.e. future uniq process) */
	/* replace the standard in with output of the old pipe */
1704
1705
1706
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

1707
1708
1709
	close(sort_fd[0]);

	/* send uniq's standard out to the new pipe */
1710
1711
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721

	close(uniq_fd[1]);

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

	/* Should not be reached, if uniq is found */
	exit(1);
    }

1722
    close(sort_fd[0]);
1723
    close(uniq_fd[1]);
1724

1725
    /* Child process was not forked successfully */
1726
1727
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
1728
	return _("Could not fork");
1729
    }
1730

1731
    /* Get system pipe buffer size */
1732
1733
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
1734
	return _("Could not get size of pipe buffer");
1735
    }
Chris Allegretta's avatar
Chris Allegretta committed
1736

1737
1738
1739
1740
    /* Read-in the returned spelling errors */
    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
1741

1742
    while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
1743
1744
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1745
	read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
1746
	read_buff_ptr += read_buff_read;
1747

1748
    }
Chris Allegretta's avatar
Chris Allegretta committed
1749

1750
    *read_buff_ptr = (char)NULL;
1751
    close(uniq_fd[0]);
1752

1753
1754
    /* Process the spelling errors */
    read_buff_word = read_buff_ptr = read_buff;
Chris Allegretta's avatar
Chris Allegretta committed
1755

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

1758
	if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
1759
	    *read_buff_ptr = (char)NULL;
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
	    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
1770

1771
1772
1773
    /* special case where last word doesn't end with \n or \r */
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
1774

1775
1776
    free(read_buff);
    replace_abort();
1777
    edit_refresh();
1778

1779
    /* Process end of spell process */
1780

1781
1782
1783
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
1784

1785
1786
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
1787

1788
1789
1790
1791
1792
1793
1794
1795
    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;
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807

close_pipes_and_exit:

    /* Don't leak any handles */
    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
1808
1809
}

1810
1811
/* External spell checking.  Return value: NULL for normal termination,
 * otherwise the error string. */
1812
char *do_alt_speller(char *tempfile_name)
1813
{
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
    int alt_spell_status, lineno_cur = current->lineno;
    int x_cur = current_x, y_cur = current_y, pww_cur = placewewant;
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
    static char **spellargs = (char **)NULL;
#ifndef NANO_SMALL
    int mark_set = ISSET(MARK_ISSET);
    int mbb_lineno_cur = 0;
	/* We're going to close the current file, and open the output of
	   the alternate spell command.  The line that mark_beginbuf
	   points to will be freed, so we save the line number and restore
	   afterwards. */

    if (mark_set) {
	mbb_lineno_cur = mark_beginbuf->lineno;
	UNSET(MARK_ISSET);
    }
1832
#endif
1833

1834
    endwin();
1835

1836
1837
    /* Set up an argument list to pass the execvp function */
    if (spellargs == NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1838
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
1839

1840
1841
1842
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1843
	    spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *));
1844
1845
1846
1847
1848
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
    }
    spellargs[arglen - 2] = tempfile_name;
Chris Allegretta's avatar
Chris Allegretta committed
1849

1850
1851
1852
1853
    /* Start a new process for the alternate speller */
    if ((pid_spell = fork()) == 0) {
	/* Start alternate spell program; we are using the PATH here!?!? */
	execvp(spellargs[0], spellargs);
Chris Allegretta's avatar
Chris Allegretta committed
1854

1855
1856
1857
	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
    }
Chris Allegretta's avatar
Chris Allegretta committed
1858

1859
1860
    /* Could not fork?? */
    if (pid_spell < 0)
1861
	return _("Could not fork");
1862
1863
1864
1865

    /* Wait for alternate speller to complete */

    wait(&alt_spell_status);
1866
1867
1868
1869
1870
1871
1872
1873
1874
    if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0) {
	char *altspell_error = NULL;
	char *invoke_error = _("Could not invoke \"%s\"");
	int msglen = strlen(invoke_error) + strlen(alt_speller) + 2;

	altspell_error = charalloc(msglen);
	snprintf(altspell_error, msglen, invoke_error, alt_speller);
	return altspell_error;
    }
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889

    refresh();
    free_filestruct(fileage);
    global_init(1);
    open_file(tempfile_name, 0, 1);

#ifndef NANO_SMALL
    if (mark_set) {
	do_gotopos(mbb_lineno_cur, mark_beginx, y_cur, 0);
	mark_beginbuf = current;
	mark_beginx = current_x;
	    /* In case the line got shorter, assign mark_beginx. */
	SET(MARK_ISSET);
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1890

1891
1892
1893
1894
1895
1896
    /* go back to the old position, mark the file as modified, and make
       sure that the titlebar is refreshed */
    do_gotopos(lineno_cur, x_cur, y_cur, pww_cur);
    set_modified();
    clearok(topwin, FALSE);
    titlebar(NULL);
1897

1898
    return NULL;
1899
}
Chris Allegretta's avatar
Chris Allegretta committed
1900
#endif
1901

1902
1903
1904
1905
int do_spell(void)
{
#ifdef DISABLE_SPELLER
    nano_disabled_msg();
1906
    return TRUE;
1907
#else
1908
    char *temp, *spell_msg;
1909

1910
1911
1912
1913
1914
    if ((temp = safe_tempnam(0, "nano.")) == NULL) {
	statusbar(_("Could not create a temporary filename: %s"),
		  strerror(errno));
	return 0;
    }
1915

1916
1917
1918
1919
    if (write_file(temp, 1, 0, 0) == -1) {
	statusbar(_("Spell checking failed: unable to write temp file!"));
	free(temp);
	return 0;
1920
1921
    }

1922
1923
1924
1925
#ifdef ENABLE_MULTIBUFFER
    /* update the current open_files entry before spell-checking, in case
       any problems occur */
    add_open_file(1);
Chris Allegretta's avatar
Chris Allegretta committed
1926
#endif
1927

1928
    if (alt_speller != NULL)
1929
	spell_msg = do_alt_speller(temp);
1930
    else
1931
	spell_msg = do_int_speller(temp);
1932
    remove(temp);
1933
    free(temp);
1934

1935
    if (spell_msg != NULL) {
1936
1937
1938
	statusbar(_("Spell checking failed: %s"), spell_msg);
	return 0;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1939

1940
1941
    statusbar(_("Finished checking spelling"));
    return 1;
1942
1943
1944
#endif
}

Chris Allegretta's avatar
Chris Allegretta committed
1945
#if !defined(DISABLE_WRAPPING) && !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
Chris Allegretta's avatar
Chris Allegretta committed
1946
1947
/* The "indentation" of a line is the white-space between the quote part
 * and the non-white-space of the line. */
1948
1949
size_t indent_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
1950
    size_t len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
1951

Chris Allegretta's avatar
Chris Allegretta committed
1952
1953
1954
1955
    assert(line != NULL);
    while (*line == ' ' || *line == '\t') {
	line++;
	len++;
Chris Allegretta's avatar
Chris Allegretta committed
1956
    }
Chris Allegretta's avatar
Chris Allegretta committed
1957
    return len;
Chris Allegretta's avatar
Chris Allegretta committed
1958
}
1959
#endif /* !DISABLE_WRAPPING && !NANO_SMALL || !DISABLE_JUSTIFY */
Chris Allegretta's avatar
Chris Allegretta committed
1960

Chris Allegretta's avatar
Chris Allegretta committed
1961
1962
1963
1964
1965
#ifndef DISABLE_JUSTIFY
/* justify_format() replaces Tab by Space and multiple spaces by 1 (except
 * it maintains 2 after a . ! or ?).  Note the terminating \0
 * counts as a space.
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1966
 * If !changes_allowed and justify_format() needs to make a change, it
Chris Allegretta's avatar
Chris Allegretta committed
1967
1968
1969
1970
1971
 * returns 1, otherwise returns 0.
 *
 * If changes_allowed, justify_format() might make line->data
 * shorter, and change the actual pointer with null_at().
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1972
 * justify_format() will not look at the first skip characters of line.
Chris Allegretta's avatar
Chris Allegretta committed
1973
1974
 * skip should be at most strlen(line->data).  The skip+1st character must
 * not be whitespace. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1975
1976
int justify_format(int changes_allowed, filestruct *line, size_t skip)
{
1977
1978
    const char *punct = ".?!";
    const char *brackets = "'\")}]>";
Chris Allegretta's avatar
Chris Allegretta committed
1979
1980
1981
1982
1983
    char *back, *front;

    /* These four asserts are assumptions about the input data. */
    assert(line != NULL);
    assert(line->data != NULL);
1984
    assert(skip < strlen(line->data));
Chris Allegretta's avatar
Chris Allegretta committed
1985
1986
1987
1988
    assert(line->data[skip] != ' ' && line->data[skip] != '\t');

    back = line->data + skip;
    front = back;
1989
    for (front = back; ; front++) {
1990
1991
1992
	int remove_space = 0;
	    /* Do we want to remove this space? */

Chris Allegretta's avatar
Chris Allegretta committed
1993
1994
1995
1996
1997
1998
	if (*front == '\t') {
	    if (!changes_allowed)
		return 1;
	    *front = ' ';
	}
	/* these tests are safe since line->data + skip is not a space */
1999
	if ((*front == '\0' || *front == ' ') && *(front - 1) == ' ') {
2000
2001
2002
	    const char *bob = front - 2;

	    remove_space = 1;
2003
	    for (bob = back - 2; bob >= line->data + skip; bob--) {
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
		if (strchr(punct, *bob) != NULL) {
		    remove_space = 0;
		    break;
		}
		if (strchr(brackets, *bob) == NULL)
		    break;
	    }
	}

	if (remove_space) {
Chris Allegretta's avatar
Chris Allegretta committed
2014
	    /* Now *front is a space we want to remove.  We do that by
2015
	     * simply failing to assign it to *back. */
Chris Allegretta's avatar
Chris Allegretta committed
2016
2017
2018
2019
2020
2021
	    if (!changes_allowed)
		return 1;
#ifndef NANO_SMALL
	    if (mark_beginbuf == line && back - line->data < mark_beginx)
		mark_beginx--;
#endif
2022
2023
	    if (*front == '\0')
		*(back - 1) = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2024
2025
2026
2027
	} else {
	    *back = *front;
	    back++;
	}
2028
2029
	if (*front == '\0')
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2030
2031
    }

2032
    back--;
2033
    assert(*back == '\0' && *front == '\0');
Chris Allegretta's avatar
Chris Allegretta committed
2034

Chris Allegretta's avatar
Chris Allegretta committed
2035
    /* This assert merely documents a fact about the loop above. */
2036
    assert(changes_allowed != 0 || back == front);
Chris Allegretta's avatar
Chris Allegretta committed
2037

Chris Allegretta's avatar
Chris Allegretta committed
2038
2039
    /* Now back is the new end of line->data. */
    if (back != front) {
2040
	totsize -= front - back;
Chris Allegretta's avatar
Chris Allegretta committed
2041
2042
2043
2044
2045
	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
2046
    }
Chris Allegretta's avatar
Chris Allegretta committed
2047
2048
    return 0;
}
Chris Allegretta's avatar
Chris Allegretta committed
2049

Chris Allegretta's avatar
Chris Allegretta committed
2050
2051
2052
2053
2054
2055
2056
/* 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. */
#ifdef HAVE_REGEX_H
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2057
2058
size_t quote_length(const char *line, const regex_t *qreg)
{
Chris Allegretta's avatar
Chris Allegretta committed
2059
2060
    regmatch_t matches;
    int rc = regexec(qreg, line, 1, &matches, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2061

Chris Allegretta's avatar
Chris Allegretta committed
2062
2063
2064
2065
2066
    if (rc == REG_NOMATCH || matches.rm_so == (regoff_t) -1)
	return 0;
    /* matches.rm_so should be 0, since the quote string should start with
     * the caret ^. */
    return matches.rm_eo;
Chris Allegretta's avatar
Chris Allegretta committed
2067
}
Chris Allegretta's avatar
Chris Allegretta committed
2068
#else	/* !HAVE_REGEX_H */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2069
2070
size_t quote_length(const char *line)
{
Chris Allegretta's avatar
Chris Allegretta committed
2071
2072
    size_t qdepth = 0;
    size_t qlen = strlen(quotestr);
2073
2074

    /* Compute quote depth level */
Chris Allegretta's avatar
Chris Allegretta committed
2075
2076
2077
2078
2079
    while (!strcmp(line + qdepth, quotestr))
	qdepth += qlen;
    return qdepth;
}
#endif	/* !HAVE_REGEX_H */
Chris Allegretta's avatar
Chris Allegretta committed
2080

Chris Allegretta's avatar
Chris Allegretta committed
2081
2082
2083
/* 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. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2084
int quotes_match(const char *a_line, size_t a_quote,
2085
		IFREG(const char *b_line, const regex_t *qreg))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2086
{
Chris Allegretta's avatar
Chris Allegretta committed
2087
2088
    /* Here is the assumption about a_quote: */
    assert(a_quote == quote_length(IFREG(a_line, qreg)));
Chris Allegretta's avatar
Chris Allegretta committed
2089
    return a_quote == quote_length(IFREG(b_line, qreg)) &&
Chris Allegretta's avatar
Chris Allegretta committed
2090
2091
	!strncmp(a_line, b_line, a_quote);
}
2092

Chris Allegretta's avatar
Chris Allegretta committed
2093
2094
/* We assume a_line and b_line have no quote part.  Then, we return whether
 * b_line could follow a_line in a paragraph. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2095
size_t indents_match(const char *a_line, size_t a_indent,
2096
			const char *b_line, size_t b_indent)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2097
{
Chris Allegretta's avatar
Chris Allegretta committed
2098
2099
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));
Chris Allegretta's avatar
Chris Allegretta committed
2100

Chris Allegretta's avatar
Chris Allegretta committed
2101
2102
    return b_indent <= a_indent && !strncmp(a_line, b_line, b_indent);
}
Chris Allegretta's avatar
Chris Allegretta committed
2103

Chris Allegretta's avatar
Chris Allegretta committed
2104
2105
2106
2107
/* Put the next par_len lines, starting with first_line, in the cut
 * buffer.  We assume there are enough lines after first_line.  We leave
 * copies of the lines in place, too.  We return the new copy of
 * first_line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2108
filestruct *backup_lines(filestruct *first_line, size_t par_len,
2109
			size_t quote_len)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2110
{
Chris Allegretta's avatar
Chris Allegretta committed
2111
2112
2113
    /* We put the original lines, not copies, into the cut buffer, just
     * out of a misguided sense of consistency, so if you un-cut, you
     * get the actual same paragraph back, not a copy. */
2114
    filestruct *alice = first_line;
Chris Allegretta's avatar
Chris Allegretta committed
2115
2116

    set_modified();
2117
    cutbuffer = NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2118
    for (; par_len > 0; par_len--) {
2119
	filestruct *bob = copy_node(alice);
Chris Allegretta's avatar
Chris Allegretta committed
2120

2121
	if (alice == first_line)
Chris Allegretta's avatar
Chris Allegretta committed
2122
	    first_line = bob;
2123
	if (alice == current)
Chris Allegretta's avatar
Chris Allegretta committed
2124
	    current = bob;
2125
	if (alice == edittop)
Chris Allegretta's avatar
Chris Allegretta committed
2126
2127
	    edittop = bob;
#ifndef NANO_SMALL
2128
	if (alice == mark_beginbuf)
Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
2131
2132
2133
	    mark_beginbuf = bob;
#endif
	justify_format(1, bob,
			quote_len + indent_length(bob->data + quote_len));

2134
2135
	assert(alice != NULL && bob != NULL);
	add_to_cutbuffer(alice);
Chris Allegretta's avatar
Chris Allegretta committed
2136
	splice_node(bob->prev, bob, bob->next);
2137
	alice = bob->next;
Chris Allegretta's avatar
Chris Allegretta committed
2138
2139
2140
2141
    }
    return first_line;
}

Chris Allegretta's avatar
Chris Allegretta committed
2142
2143
2144
/* Is it possible to break line at or before goal? */
int breakable(const char *line, int goal)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2145
    for (; *line != '\0' && goal >= 0; line++) {
Chris Allegretta's avatar
Chris Allegretta committed
2146
2147
2148
2149
2150
2151
2152
2153
	if (*line == ' ' || *line == '\t')
	    return TRUE;

	if (is_cntrl_char(*line) != 0)
	    goal -= 2;
	else
	    goal -= 1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
2156
    /* If goal is not negative, the whole line (one word) was short
     * enough. */
    return goal >= 0;
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
}

Chris Allegretta's avatar
Chris Allegretta committed
2159
2160
2161
2162
2163
/* We are trying to break a chunk off line.  We find the last space such
 * that the display length to there is at most goal + 1.  If there is
 * no such space, and force is not 0, 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. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2164
2165
int break_line(const char *line, int goal, int force)
{
Chris Allegretta's avatar
Chris Allegretta committed
2166
2167
2168
2169
2170
2171
2172
2173
2174
    /* Note that we use int instead of size_t, since goal is at most COLS,
     * the screen width, which will always be reasonably small. */
    int space_loc = -1;
	/* Current tentative return value.  Index of the last space we
	 * found with short enough display width.  */
    int cur_loc = 0;
	/* Current index in line */

    assert(line != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2175
    for (; *line != '\0' && goal >= 0; line++, cur_loc++) {
Chris Allegretta's avatar
Chris Allegretta committed
2176
2177
2178
2179
	if (*line == ' ')
	    space_loc = cur_loc;
	assert(*line != '\t');

Chris Allegretta's avatar
Chris Allegretta committed
2180
	if (is_cntrl_char(*line))
Chris Allegretta's avatar
Chris Allegretta committed
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
	    goal -= 2;
	else
	    goal--;
    }
    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;
    if (space_loc == -1) {
	/* No space found short enough. */
	if (force)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2191
	    for (; *line != '\0'; line++, cur_loc++)
2192
		if (*line == ' ' && *(line + 1) != ' ' && *(line + 1) != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
2193
2194
2195
2196
		    return cur_loc;
	return -1;
    }
    /* Perhaps the character after space_loc is a space.  But because
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2197
     * of justify_format(), there can be only two adjacent. */
Chris Allegretta's avatar
Chris Allegretta committed
2198
2199
2200
2201
2202
2203
    if (*(line - cur_loc + space_loc + 1) == ' ' ||
	*(line - cur_loc + space_loc + 1) == '\0')
	space_loc++;
    return space_loc;
}

2204
2205
2206
/* This function performs operations on paragraphs: justify, go to
 * beginning, and go to end. */
int do_para_operation(int operation)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2207
{
2208
2209
2210
2211
2212
2213
/* operation == 0 means we're justifying the paragraph, operation == 1
 * means we're moving to the beginning line of the paragraph, and
 * operation == 2 means we're moving to the ending line of the
 * paragraph.
 *
 * To explain the justifying algorithm, I first need to define some
Chris Allegretta's avatar
Chris Allegretta committed
2214
2215
2216
2217
2218
2219
2220
2221
 * phrases about paragraphs and quotation:
 *   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.
 *
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2222
2223
 *   A line is "the beginning of a paragraph" if it is part of a
 *   paragraph and
Chris Allegretta's avatar
Chris Allegretta committed
2224
2225
2226
2227
 *	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
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2228
2229
 *	4) the indentation of this line is not an initial substring of
 *	   the indentation of the previous line, or
Chris Allegretta's avatar
Chris Allegretta committed
2230
2231
2232
 *	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
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2233
2234
2235
 *   indented line is expected to start a paragraph, like in books.
 *   Thus, nano can justify an indented paragraph only if AUTOINDENT is
 *   turned on.
Chris Allegretta's avatar
Chris Allegretta committed
2236
2237
 *
 *   A contiguous set of lines is a "paragraph" if each line is part of
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2238
 *   a paragraph and only the first line is the beginning of a
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2239
 *   paragraph. */
Chris Allegretta's avatar
Chris Allegretta committed
2240

Chris Allegretta's avatar
Chris Allegretta committed
2241
    size_t quote_len;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2242
2243
	/* Length of the initial quotation of the paragraph we
	 * justify. */
Chris Allegretta's avatar
Chris Allegretta committed
2244
2245
2246
2247
    size_t par_len;
	/* Number of lines in that paragraph. */
    filestruct *first_mod_line = NULL;
	/* Will be the first line of the resulting justified paragraph
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2248
2249
	 * that differs from the original.  For restoring after
	 * uncut. */
Chris Allegretta's avatar
Chris Allegretta committed
2250
2251
2252
2253
    filestruct *last_par_line = current;
	/* Will be the last line of the result, also for uncut. */
    filestruct *cutbuffer_save = cutbuffer;
	/* When the paragraph gets modified, all lines from the changed
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2254
2255
	 * one down are stored in the cut buffer.  We back up the
	 * original to restore it later. */
Chris Allegretta's avatar
Chris Allegretta committed
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269

    /* We save these global variables to be restored if the user
     * unjustifies.  Note we don't need to save totlines. */
    int current_x_save = current_x;
    int current_y_save = current_y;
    filestruct *current_save = current;
    int flags_save = flags;
    long totsize_save = totsize;
    filestruct *edittop_save = edittop;
    filestruct *editbot_save = editbot;
#ifndef NANO_SMALL
    filestruct *mark_beginbuf_save = mark_beginbuf;
    int mark_beginx_save = mark_beginx;
#endif
2270

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2271
2272
2273
    size_t indent_len;	/* Generic indentation length. */
    filestruct *line;	/* Generic line of text. */
    size_t i;		/* Generic loop variable. */
Robert Siemborski's avatar
Robert Siemborski committed
2274

2275
    static int no_restart = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2276
2277
    	/* Whether we're blocking restarting when searching for the
    	 * beginning line of the paragraph. */
2278

Chris Allegretta's avatar
Chris Allegretta committed
2279
2280
#ifdef HAVE_REGEX_H
    regex_t qreg;	/* qreg is the compiled quotation regexp. 
Chris Allegretta's avatar
Chris Allegretta committed
2281
			 * We no longer care about quotestr. */
Chris Allegretta's avatar
Chris Allegretta committed
2282
    int rc = regcomp(&qreg, quotestr, REG_EXTENDED);
Chris Allegretta's avatar
Chris Allegretta committed
2283

Chris Allegretta's avatar
Chris Allegretta committed
2284
2285
2286
    if (rc) {
	size_t size = regerror(rc, &qreg, NULL, 0);
	char *strerror = charalloc(size);
Chris Allegretta's avatar
Chris Allegretta committed
2287

Chris Allegretta's avatar
Chris Allegretta committed
2288
2289
2290
2291
	regerror(rc, &qreg, strerror, size);
	statusbar(_("Bad quote string %s: %s"), quotestr, strerror);
	free(strerror);
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
2292
    }
Chris Allegretta's avatar
Chris Allegretta committed
2293
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2294

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

2298
2299
2300
    current_x = 0;

  restart_bps:
Chris Allegretta's avatar
Chris Allegretta committed
2301
/* Here we find the first line of the paragraph to justify.  If the
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2302
 * current line is in a paragraph, then we move back to the first line.
Chris Allegretta's avatar
Chris Allegretta committed
2303
2304
2305
 * Otherwise we move down to the first line that is in a paragraph. */
    quote_len = quote_length(IFREG(current->data, &qreg));
    indent_len = indent_length(current->data + quote_len);
Robert Siemborski's avatar
Robert Siemborski committed
2306

Chris Allegretta's avatar
Chris Allegretta committed
2307
2308
    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
2309
2310
	 * the first line of this paragraph.  First we check items 1)
	 * and 3) above. */
2311
	while (current->prev != NULL && quotes_match(current->data,
Chris Allegretta's avatar
Chris Allegretta committed
2312
			quote_len, IFREG(current->prev->data, &qreg))) {
2313
	    size_t temp_id_len =
Chris Allegretta's avatar
Chris Allegretta committed
2314
			indent_length(current->prev->data + quote_len);
2315
		/* The indentation length of the previous line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2316

2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
	    /* Is this line the beginning of a paragraph, according to
	       items 2), 5), or 4) above?  If so, stop. */
	    if (current->prev->data[quote_len + temp_id_len] == '\0' ||
		    (quote_len == 0 && indent_len > 0
#ifndef NANO_SMALL
			&& !ISSET(AUTOINDENT)
#endif
			) ||
		    !indents_match(current->prev->data + quote_len,
			temp_id_len, current->data + quote_len, indent_len))
		break;
	    indent_len = temp_id_len;
	    current = current->prev;
	    current_y--;
Chris Allegretta's avatar
Chris Allegretta committed
2331
	}
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
    } else if (operation == 1) {
	/* This line is not part of a paragraph.  Move up until we get
	 * to a non "blank" line, and then move down once. */
	do {
	    /* There is no previous paragraph, so nothing to move to. */
	    if (current->prev == NULL) {
		placewewant = 0;
		if (current_y < 0)
		    edit_update(current, CENTER);
		else
		    edit_refresh();
		return 0;
	    }
	    current = current->prev;
	    current_y--;
	    quote_len = quote_length(IFREG(current->data, &qreg));
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
	current = current->next;
Chris Allegretta's avatar
Chris Allegretta committed
2351
    } else {
Chris Allegretta's avatar
Chris Allegretta committed
2352
2353
	/* 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
2354
2355
	do {
	    /* There is no next paragraph, so nothing to justify. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2356
2357
	    if (current->next == NULL) {
		placewewant = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2358
2359
2360
2361
		edit_refresh();
#ifdef HAVE_REGEX_H
		regfree(&qreg);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2362
		return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2363
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2364
	    current = current->next;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2365
	    current_y++;
Chris Allegretta's avatar
Chris Allegretta committed
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
	    quote_len = quote_length(IFREG(current->data, &qreg));
	    indent_len = indent_length(current->data + quote_len);
	} while (current->data[quote_len + indent_len] == '\0');
    }
/* Now current is the first line of the paragraph, and quote_len
 * is the quotation length of that line. */

/* Next step, compute par_len, the number of lines in this paragraph. */
    line = current;
    par_len = 1;
    indent_len = indent_length(line->data + quote_len);

2378
    while (line->next != NULL && quotes_match(current->data, quote_len,
Chris Allegretta's avatar
Chris Allegretta committed
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
				IFREG(line->next->data, &qreg))) {
	size_t temp_id_len = indent_length(line->next->data + quote_len);

	if (!indents_match(line->data + quote_len, indent_len,
			line->next->data + quote_len, temp_id_len) ||
		line->next->data[quote_len + temp_id_len] == '\0' ||
		(quote_len == 0 && temp_id_len > 0
#ifndef NANO_SMALL
			&& !ISSET(AUTOINDENT)
#endif
		))
	    break;
	indent_len = temp_id_len;
	line = line->next;
	par_len++;
    }
#ifdef HAVE_REGEX_H
2396
    /* We no longer need to check quotation, unless we're searching for
2397
     * the beginning of the paragraph. */
2398
2399
    if (operation != 1)
	regfree(&qreg);
Chris Allegretta's avatar
Chris Allegretta committed
2400
2401
2402
2403
#endif
/* Now par_len is the number of lines in this paragraph.  Should never
 * call quotes_match() or quote_length() again. */

2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
    /* If we're searching for the beginning of the paragraph, skip the
     * justification.  If we're searching for the end of the paragraph,
     * move down the number of lines in the paragraph and skip the
     * justification. */
    if (operation == 1)
	goto skip_justify;
    else if (operation == 2) {
	while (par_len > 0) {
	    current = current->next;
	    current_y++;
	    par_len--;
2415
2416
2417
2418
	}
	goto skip_justify;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2419
2420
/* Next step, we loop through the lines of this paragraph, justifying
 * each one individually. */
2421
    SET(JUSTIFY_MODE);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2422
    for (; par_len > 0; current_y++, par_len--) {
Chris Allegretta's avatar
Chris Allegretta committed
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
	size_t line_len;
	size_t display_len;
	    /* The width of current in screen columns. */
	int break_pos;
	    /* Where we will break the line. */

	indent_len = indent_length(current->data + quote_len) +
			quote_len; 
	/* justify_format() removes excess spaces from the line, and
	 * changes tabs to spaces.  The first argument, 0, means don't
	 * change the line, just say whether there are changes to be
	 * made.  If there are, we do backup_lines(), which copies the
	 * original paragraph to the cutbuffer for unjustification, and
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2436
	 * then calls justify_format() on the remaining lines. */
2437
	if (first_mod_line == NULL && justify_format(0, current, indent_len))
Chris Allegretta's avatar
Chris Allegretta committed
2438
2439
2440
2441
2442
2443
2444
2445
2446
	    first_mod_line = backup_lines(current, par_len, quote_len);

	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,
			    fill - strnlenpt(current->data, indent_len),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2447
			    1);
Chris Allegretta's avatar
Chris Allegretta committed
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
	    if (break_pos == -1 || break_pos + indent_len == line_len)
		/* 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 we haven't backed up the paragraph, do it now. */
	    if (first_mod_line == NULL)
		first_mod_line = backup_lines(current, par_len, quote_len);
	    if (par_len == 1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2458
2459
		/* There is no next line in this paragraph.  We make a
		 * new line and copy text after break_pos into it. */
Chris Allegretta's avatar
Chris Allegretta committed
2460
2461
		splice_node(current, make_new_node(current),
				current->next);
Chris Allegretta's avatar
Chris Allegretta committed
2462
2463
2464
2465
2466
2467
2468
		/* In a non-quoted paragraph, we copy the indent only if
		   AUTOINDENT is turned on. */
		if (quote_len == 0)
#ifndef NANO_SMALL
		    if (!ISSET(AUTOINDENT))
#endif
			indent_len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
		current->next->data = charalloc(indent_len + line_len -
						break_pos);
		strncpy(current->next->data, current->data,
			indent_len);
		strcpy(current->next->data + indent_len,
			current->data + break_pos + 1);
		assert(strlen(current->next->data) ==
			indent_len + line_len - break_pos - 1);
		totlines++;
		totsize += indent_len;
		par_len++;
	    } else {
		size_t next_line_len = strlen(current->next->data);

		indent_len = quote_len +
			indent_length(current->next->data + quote_len);
Chris Allegretta's avatar
Chris Allegretta committed
2485
2486
		current->next->data = charealloc(current->next->data,
			next_line_len + line_len - break_pos + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2487

2488
		charmove(current->next->data + indent_len + line_len - break_pos,
Chris Allegretta's avatar
Chris Allegretta committed
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
			current->next->data + indent_len,
			next_line_len - indent_len + 1);
		strcpy(current->next->data + indent_len,
			current->data + break_pos + 1);
		current->next->data[indent_len + line_len - break_pos - 1]
			= ' ';
#ifndef NANO_SMALL
		if (mark_beginbuf == current->next) {
		    if (mark_beginx < indent_len)
			mark_beginx = indent_len;
		    mark_beginx += line_len - break_pos;
		}
#endif
	    }
#ifndef NANO_SMALL
	    if (mark_beginbuf == current && mark_beginx > break_pos) {
		mark_beginbuf = current->next;
		mark_beginx -= break_pos + 1 - indent_len;
	    }
#endif
	    null_at(&current->data, break_pos);
	    current = current->next;
	} else if (display_len < fill && par_len > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2512
	    size_t next_line_len;
Chris Allegretta's avatar
Chris Allegretta committed
2513
2514
2515

	    indent_len = quote_len +
			indent_length(current->next->data + quote_len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2516
2517
	    /* If we can't pull a word from the next line up to this
	     * one, just go on. */
Chris Allegretta's avatar
Chris Allegretta committed
2518
2519
	    if (!breakable(current->next->data + indent_len,
		    fill - display_len - 1))
Chris Allegretta's avatar
Chris Allegretta committed
2520
2521
2522
2523
2524
		goto continue_loc;

	    /* If we haven't backed up the paragraph, do it now. */
	    if (first_mod_line == NULL)
		first_mod_line = backup_lines(current, par_len, quote_len);
Chris Allegretta's avatar
Chris Allegretta committed
2525
2526
2527
2528
2529

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

Chris Allegretta's avatar
Chris Allegretta committed
2530
	    current->data = charealloc(current->data,
Chris Allegretta's avatar
Chris Allegretta committed
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
					line_len + break_pos + 2);
	    current->data[line_len] = ' ';
	    strncpy(current->data + line_len + 1,
			current->next->data + indent_len, break_pos);
	    current->data[line_len + break_pos + 1] = '\0';
#ifndef NANO_SMALL
	    if (mark_beginbuf == current->next) {
		if (mark_beginx < indent_len + break_pos) {
 		    mark_beginbuf = current;
		    if (mark_beginx <= indent_len)
			mark_beginx = line_len + 1;
		    else
			mark_beginx = line_len + 1 + mark_beginx - indent_len;
		} else
		    mark_beginx -= break_pos + 1;
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2548
	    next_line_len = strlen(current->next->data);
Chris Allegretta's avatar
Chris Allegretta committed
2549
2550
	    if (indent_len + break_pos == next_line_len) {
		line = current->next;
2551
2552
2553
2554
2555

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

Chris Allegretta's avatar
Chris Allegretta committed
2556
2557
2558
2559
2560
2561
		unlink_node(line);
		delete_node(line);
		totlines--;
		totsize -= indent_len;
		current_y--;
	    } else {
2562
		charmove(current->next->data + indent_len,
Chris Allegretta's avatar
Chris Allegretta committed
2563
2564
2565
2566
2567
2568
2569
			current->next->data + indent_len + break_pos + 1,
			next_line_len - break_pos - indent_len);
		null_at(&current->next->data,
			next_line_len - break_pos);
		current = current->next;
	    }
	} else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2570
  continue_loc:
Chris Allegretta's avatar
Chris Allegretta committed
2571
2572
	    current = current->next;
    }
2573
    UNSET(JUSTIFY_MODE);
Chris Allegretta's avatar
Chris Allegretta committed
2574

2575
2576
/* We are now done justifying the paragraph.  There are cleanup things
 * to do, and we check for unjustify. */
Chris Allegretta's avatar
Chris Allegretta committed
2577

Chris Allegretta's avatar
Chris Allegretta committed
2578
2579
2580
2581
2582
    /* totlines, totsize, and current_y have been maintained above.  We
     * now set last_par_line to the new end of the paragraph, update
     * fileage, set current_x.  Also, edit_refresh() needs the line
     * numbers to be right, so we renumber(). */
    last_par_line = current->prev;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2583
2584
2585
    if (first_mod_line != NULL) {
	if (first_mod_line->prev == NULL)
	    fileage = first_mod_line;
Chris Allegretta's avatar
Chris Allegretta committed
2586
	renumber(first_mod_line);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2587
    }
2588

2589
  skip_justify:
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
    if (operation == 1) {
	/* We're on the same line we started on.  Search for the first
	 * non-"blank" line before the line we're on (if there is one),
	 * continually restart that search from the current position
	 * until we find a line that's part of a paragraph, and then
	 * search once more from there, so that we end up on the first
	 * line of that paragraph.  In the process, skip over lines
	 * consisting only of spacing characters, as searching for the
	 * end of the paragraph does.  Then update the screen. */
	if (current != fileage && current == current_save && !no_restart) {
	    while (current->prev != NULL) {
		int j, j_space = 0;
		current = current->prev;
		current_y--;
		for (j = 0; j < strlen(current->data); j++) {
		    if (isspace(current->data[j]))
			j_space++;
		    else {
			j = -1;
			break;
2610
		    }
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
		}
		if (j != j_space && strlen(current->data) >=
			(quote_len + indent_len) &&
			current->data[quote_len + indent_len] != '\0') {
		    no_restart = 1;
		    break;
		}
	    }
	    goto restart_bps;
	} else
	    no_restart = 0;
2622
#ifdef HAVE_REGEX_H
2623
2624
2625
	/* We no longer need to check quotation, if we were
	 * searching for the beginning of the paragraph. */
	regfree(&qreg);
2626
#endif
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
	if (current_y < 0)
	    edit_update(current, CENTER);
	else
	    edit_refresh();
	return 0;
    } else if (operation == 2) {
	/* We've already moved to the end of the paragraph.  Update the
	 * screen. */
	if (current_y > editwinrows - 1)
	    edit_update(current, CENTER);
	else
	    edit_refresh();
	return 0;
2640
2641
    }

2642
    if (current_y > editwinrows - 1)
2643
	edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
2644
2645
    else
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
2646

2647
    statusbar(_("Can now UnJustify!"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2648
    /* Change the shortcut list to display the unjustify code. */
2649
2650
    shortcut_init(1);
    display_main_list();
2651
2652
    reset_cursor();

Chris Allegretta's avatar
Chris Allegretta committed
2653
    /* Now get a keystroke and see if it's unjustify; if not, unget the
2654
     * keystroke and return. */
2655

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2656
2657
2658
    {
	int meta;
	i = get_kbinput(edit, &meta);
2659
#ifndef DISABLE_MOUSE
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2660
2661
2662
2663
2664
2665
2666
2667
	/* If it was a mouse click, parse it with do_mouse() and it
	 * might become the unjustify key.  Else give it back to the
	 * input stream. */
	if (i == KEY_MOUSE)
	    do_mouse();
	else
	    ungetch(i);
	i = get_kbinput(edit, &meta);
2668
#endif
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2669
    }
2670

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2671
    if (i != NANO_UNJUSTIFY_KEY) {
Chris Allegretta's avatar
Chris Allegretta committed
2672
2673
2674
2675
2676
	ungetch(i);
	/* Did we back up anything at all? */
	if (cutbuffer != cutbuffer_save)
	    free_filestruct(cutbuffer);
	placewewant = 0;
2677
    } else {
2678
	/* Else restore the justify we just did (ungrateful user!) */
Chris Allegretta's avatar
Chris Allegretta committed
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
	current = current_save;
	current_x = current_x_save;
	current_y = current_y_save;
	edittop = edittop_save;
	editbot = editbot_save;
	if (first_mod_line != NULL) {
	    filestruct *cutbottom = get_cutbottom();

	    /* Splice the cutbuffer back into the file. */
	    cutbottom->next = last_par_line->next;
	    cutbottom->next->prev = cutbottom;
		/* The line numbers after the end of the paragraph have
		 * been changed, so we change them back. */
	    renumber(cutbottom->next);
	    if (first_mod_line->prev != NULL) {
		cutbuffer->prev = first_mod_line->prev;
		cutbuffer->prev->next = cutbuffer;
	    } else
		fileage = cutbuffer;
	    cutbuffer = NULL;

	    last_par_line->next = NULL;
	    free_filestruct(first_mod_line);

	    /* Restore global variables from before justify */
	    totsize = totsize_save;
	    totlines = filebot->lineno;
#ifndef NANO_SMALL
	    mark_beginbuf = mark_beginbuf_save;
	    mark_beginx = mark_beginx_save;
#endif
	    flags = flags_save;
	    if (!ISSET(MODIFIED)) {
		titlebar(NULL);
		wrefresh(topwin);
	    }
	}
	edit_refresh();
2717
    }
Chris Allegretta's avatar
Chris Allegretta committed
2718
2719
    cutbuffer = cutbuffer_save;
    blank_statusbar_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
2720
    /* display shortcut list with UnCut */
2721
2722
    shortcut_init(0);
    display_main_list();
2723

Chris Allegretta's avatar
Chris Allegretta committed
2724
    return 0;
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
}
#endif /* !DISABLE_JUSTIFY */

int do_justify(void)
{
#ifdef DISABLE_JUSTIFY
    nano_disabled_msg();
    return 1;
#else
    return do_para_operation(0);
Chris Allegretta's avatar
Chris Allegretta committed
2735
2736
2737
#endif
}

2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
#ifndef DISABLE_JUSTIFY
int do_para_begin(void)
{
    return do_para_operation(1);
}

int do_para_end(void)
{
    return do_para_operation(2);
}
#endif

2750
int do_exit(void)
Chris Allegretta's avatar
Chris Allegretta committed
2751
{
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
    int i;

    if (!ISSET(MODIFIED)) {

#ifdef ENABLE_MULTIBUFFER
	if (!close_open_file()) {
	    display_main_list();
	    return 1;
	}
	else
2762
#endif
2763
2764
	    finish(0);
    }
2765

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2766
    if (ISSET(TEMP_OPT))
2767
	i = 1;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2768
2769
2770
    else
	i = do_yesno(0, _("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? "));
    
2771
2772
#ifdef DEBUG
    dump_buffer(fileage);
2773
#endif
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814

    if (i == 1) {
	if (do_writeout(filename, 1, 0) > 0) {

#ifdef ENABLE_MULTIBUFFER
	    if (!close_open_file()) {
		display_main_list();
		return 1;
	    }
	    else
#endif
		finish(0);
	}
    } else if (i == 0) {

#ifdef ENABLE_MULTIBUFFER
	if (!close_open_file()) {
	    display_main_list();
	    return 1;
	}
	else
#endif
	    finish(0);
    } else
	statusbar(_("Cancelled"));

    display_main_list();
    return 1;
}

void signal_init(void)
{
#ifdef _POSIX_VDISABLE
    struct termios term;
#endif

    /* Trap SIGINT and SIGQUIT cuz we want them to do useful things. */
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGINT, &act, NULL);

2815
2816
    /* Trap SIGHUP and SIGTERM cuz we want to write the file out. */
    act.sa_handler = handle_hupterm;
2817
    sigaction(SIGHUP, &act, NULL);
2818
    sigaction(SIGTERM, &act, NULL);
2819

2820
#ifndef NANO_SMALL
2821
2822
    act.sa_handler = handle_sigwinch;
    sigaction(SIGWINCH, &act, NULL);
2823
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2824

2825
2826
#ifdef _POSIX_VDISABLE
    tcgetattr(0, &term);
2827

2828
2829
2830
2831
2832
    if (!ISSET(PRESERVE)) {
	/* Ignore ^S and ^Q, much to Chris' chagrin */
	term.c_cc[VSTOP] = _POSIX_VDISABLE;
	term.c_cc[VSTART] = _POSIX_VDISABLE;
    }
2833
2834
2835
#ifdef VDSUSP
    term.c_cc[VDSUSP] = _POSIX_VDISABLE;
#endif /* VDSUSP */
2836

2837
#endif /* _POSIX_VDISABLE */
Chris Allegretta's avatar
Chris Allegretta committed
2838

2839
    if (!ISSET(SUSPEND)) {
2840
	/* Insane! */
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
#ifdef _POSIX_VDISABLE
	term.c_cc[VSUSP] = _POSIX_VDISABLE;
#else
	act.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &act, NULL);
#endif
    } else {
	/* If we don't do this, it seems other stuff interrupts the
	   suspend handler!  Try using nano with mutt without this
	   line. */
	sigfillset(&act.sa_mask);
Chris Allegretta's avatar
Chris Allegretta committed
2852

2853
2854
	act.sa_handler = do_suspend;
	sigaction(SIGTSTP, &act, NULL);
2855

2856
2857
2858
2859
2860
2861
	act.sa_handler = do_cont;
	sigaction(SIGCONT, &act, NULL);
    }

#ifdef _POSIX_VDISABLE
    tcsetattr(0, TCSANOW, &term);
2862
#endif
2863
}
2864

2865
2866
/* Handler for SIGHUP and SIGTERM */
RETSIGTYPE handle_hupterm(int signal)
2867
{
2868
    die(_("Received SIGHUP or SIGTERM\n"));
2869
}
2870

2871
2872
2873
2874
/* What do we do when we catch the suspend signal */
RETSIGTYPE do_suspend(int signal)
{
    endwin();
2875
    printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano"));
2876
    fflush(stdout);
2877

2878
2879
    /* Restore the terminal settings for the disabled keys */
    tcsetattr(0, TCSANOW, &oldterm);
2880

2881
2882
2883
2884
2885
2886
    /* Trap SIGHUP and SIGTERM so we can properly deal with them while
       suspended */
    act.sa_handler = handle_hupterm;
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGTERM, &act, NULL);

2887
    /* We used to re-enable the default SIG_DFL and raise SIGTSTP, but 
2888
2889
       then we could be (and were) interrupted in the middle of the call.
       So we do it the mutt way instead */
2890
2891
    kill(0, SIGSTOP);
}
2892

2893
2894
2895
2896
2897
2898
/* Restore the suspend handler when we come back into the prog */
RETSIGTYPE do_cont(int signal)
{
    /* Now we just update the screen instead of having to reenable the
       SIGTSTP handler. */
    doupdate();
2899

2900
    /* The Hurd seems to need this, otherwise a ^Y after a ^Z will
2901
       start suspending again. */
2902
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
2903

2904
#ifndef NANO_SMALL
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
    /* Perhaps the user resized the window while we slept. */
    handle_sigwinch(0);
#endif
}

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

2918
    if (tty == NULL)
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
	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;
2934
2935
    editwinrows = LINES - 5 + no_help();
    if (editwinrows < MIN_EDITOR_ROWS || COLS < MIN_EDITOR_COLS)
2936
2937
2938
2939
2940
2941
	die_too_small();

#ifndef DISABLE_WRAPJUSTIFY
    fill = wrap_at;
    if (fill <= 0)
	fill += COLS;
2942
2943
    if (fill < 0)
	fill = 0;
2944
2945
#endif

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2946
    hblank = charealloc(hblank, COLS + 1);
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
    memset(hblank, ' ', COLS);
    hblank[COLS] = '\0';

#ifdef HAVE_RESIZETERM
    resizeterm(LINES, COLS);
#ifdef HAVE_WRESIZE
    if (wresize(topwin, 2, COLS) == ERR)
	die(_("Cannot resize top win"));
    if (mvwin(topwin, 0, 0) == ERR)
	die(_("Cannot move top win"));
    if (wresize(edit, editwinrows, COLS) == ERR)
	die(_("Cannot resize edit win"));
    if (mvwin(edit, 2, 0) == ERR)
	die(_("Cannot move edit win"));
    if (wresize(bottomwin, 3 - no_help(), COLS) == ERR)
	die(_("Cannot resize bottom win"));
    if (mvwin(bottomwin, LINES - 3 + no_help(), 0) == ERR)
	die(_("Cannot move bottom win"));
#endif				/* HAVE_WRESIZE */
#endif				/* HAVE_RESIZETERM */

    fix_editbot();

    if (current_y > editwinrows - 1)
	edit_update(editbot, CENTER);
    erase();

    /* Do these b/c width may have changed... */
    refresh();
    titlebar(NULL);
    edit_refresh();
    display_main_list();
    blank_statusbar();
    total_refresh();

    /* Turn cursor back on for sure */
    curs_set(1);

    /* Jump back to main loop */
    siglongjmp(jmpbuf, 1);
}
2988
#endif /* !NANO_SMALL */
2989

2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
/* If the NumLock key has made the keypad go awry, print an error
   message; hopefully we can address it later. */
void print_numlock_warning(void)
{
    static int didmsg = 0;
    if (!didmsg) {
	statusbar(_
		  ("NumLock glitch detected.  Keypad will malfunction with NumLock off"));
	didmsg = 1;
    }
3000
3001
}

3002
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3003
void do_toggle(const toggle *which)
3004
{
Chris Allegretta's avatar
Chris Allegretta committed
3005
    int enabled;
3006

3007
    /* Even easier! */
3008
    TOGGLE(which->flag);
Chris Allegretta's avatar
Chris Allegretta committed
3009

3010
    switch (which->val) {
3011
3012
3013
    case TOGGLE_SUSPEND_KEY:
	signal_init();
	break;
3014
#ifndef DISABLE_MOUSE
3015
3016
3017
    case TOGGLE_MOUSE_KEY:
	mouse_init();
	break;
3018
#endif
3019
    case TOGGLE_NOHELP_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
3020
3021
3022
	wclear(bottomwin);
	wrefresh(bottomwin);
	window_init();
3023
	fix_editbot();
Chris Allegretta's avatar
Chris Allegretta committed
3024
3025
	edit_refresh();
	display_main_list();
3026
	break;
3027
3028
3029
3030
3031
3032
    case TOGGLE_DOS_KEY:
	UNSET(MAC_FILE);
	break;
    case TOGGLE_MAC_KEY:
	UNSET(DOS_FILE);
	break;
3033
#ifdef ENABLE_COLOR
3034
3035
    case TOGGLE_SYNTAX_KEY:
	edit_refresh();
3036
3037
	break;
#endif
3038
    }
Chris Allegretta's avatar
Chris Allegretta committed
3039

Chris Allegretta's avatar
Chris Allegretta committed
3040
3041
3042
3043
3044
3045
3046
    /* 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
3047
}
3048
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
3049
3050
3051
3052

int main(int argc, char *argv[])
{
    int optchr;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3053
    int startline = 0;		/* Line to try and start at */
3054
    int modify_control_seq = 0;
Chris Allegretta's avatar
Chris Allegretta committed
3055
    int fill_flag_used = 0;	/* Was the fill option used? */
Chris Allegretta's avatar
Chris Allegretta committed
3056
    const shortcut *s;
3057
    int keyhandled = 0;	/* Have we handled the keystroke yet? */
3058
    int kbinput;		/* Input from keyboard */
3059
    int meta;
3060

Chris Allegretta's avatar
Chris Allegretta committed
3061
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
3062
    const toggle *t;
Chris Allegretta's avatar
Chris Allegretta committed
3063
#endif
3064
3065
3066
#ifdef _POSIX_VDISABLE
    struct termios term;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3067
#ifdef HAVE_GETOPT_LONG
3068
    const struct option long_options[] = {
3069
3070
3071
	{"help", 0, 0, 'h'},
#ifdef ENABLE_MULTIBUFFER
	{"multibuffer", 0, 0, 'F'},
Chris Allegretta's avatar
Chris Allegretta committed
3072
3073
#endif
#ifdef ENABLE_NANORC
3074
#ifndef NANO_SMALL
3075
	{"historylog", 0, 0, 'H'},
3076
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3077
	{"ignorercfiles", 0, 0, 'I'},
3078
3079
3080
3081
#endif
#ifndef DISABLE_JUSTIFY
	{"quotestr", 1, 0, 'Q'},
#endif
3082
#ifdef HAVE_REGEX_H
3083
	{"regexp", 0, 0, 'R'},
3084
#endif
3085
	{"tabsize", 1, 0, 'T'},
Chris Allegretta's avatar
Chris Allegretta committed
3086
	{"version", 0, 0, 'V'},
3087
3088
#ifdef ENABLE_COLOR
	{"syntax", 1, 0, 'Y'},
3089
#endif
3090
	{"const", 0, 0, 'c'},
3091
	{"rebinddelete", 0, 0, 'd'},
3092
	{"nofollow", 0, 0, 'l'},
3093
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3094
	{"mouse", 0, 0, 'm'},
3095
#endif
3096
3097
3098
#ifndef DISABLE_OPERATINGDIR
	{"operatingdir", 1, 0, 'o'},
#endif
3099
	{"preserve", 0, 0, 'p'},
3100
3101
3102
3103
3104
#ifndef DISABLE_WRAPJUSTIFY
	{"fill", 1, 0, 'r'},
#endif
#ifndef DISABLE_SPELLER
	{"speller", 1, 0, 's'},
3105
#endif
3106
3107
	{"tempfile", 0, 0, 't'},
	{"view", 0, 0, 'v'},
3108
#ifndef DISABLE_WRAPPING
3109
	{"nowrap", 0, 0, 'w'},
3110
#endif
3111
3112
	{"nohelp", 0, 0, 'x'},
	{"suspend", 0, 0, 'z'},
3113
#ifndef NANO_SMALL
3114
3115
3116
3117
	{"backup", 0, 0, 'B'},
	{"dos", 0, 0, 'D'},
	{"mac", 0, 0, 'M'},
	{"noconvert", 0, 0, 'N'},
3118
	{"smooth", 0, 0, 'S'},
3119
3120
	{"autoindent", 0, 0, 'i'},
	{"cut", 0, 0, 'k'},
3121
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3122
3123
3124
3125
	{0, 0, 0, 0}
    };
#endif

3126
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
3127
3128
3129
3130
3131
    setlocale(LC_ALL, "");
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3132
#if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
3133
3134
3135
3136
3137
    /* if we don't have rcfile support, we're root, and
       --disable-wrapping-as-root is used, turn wrapping off */
    if (geteuid() == 0)
	SET(NO_WRAP);
#endif
3138

Chris Allegretta's avatar
Chris Allegretta committed
3139
#ifdef HAVE_GETOPT_LONG
3140
    while ((optchr = getopt_long(argc, argv, "h?BDFHIMNQ:RST:VY:abcdefgijklmo:pr:s:tvwxz",
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3141
				 long_options, NULL)) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3142
#else
3143
    while ((optchr =
3144
	    getopt(argc, argv, "h?BDFHIMNQ:RST:VY:abcdefgijklmo:pr:s:tvwxz")) != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
3145
3146
3147
#endif

	switch (optchr) {
3148

3149
3150
3151
3152
3153
3154
3155
3156
	case 'a':
	case 'b':
	case 'e':
	case 'f':
	case 'g':
	case 'j':
	    /* Pico compatibility flags */
	    break;
3157
#ifndef NANO_SMALL
3158
3159
3160
	case 'B':
	    SET(BACKUP_FILE);
	    break;
3161
3162
3163
3164
	case 'D':
	    SET(DOS_FILE);
	    break;
#endif
3165
#ifdef ENABLE_MULTIBUFFER
3166
	case 'F':
3167
	    SET(MULTIBUFFER);
3168
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
3169
3170
#endif
#ifdef ENABLE_NANORC
3171
#ifndef NANO_SMALL
3172
3173
3174
	case 'H':
	    SET(HISTORYLOG);
	    break;
3175
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3176
	case 'I':
Chris Allegretta's avatar
Chris Allegretta committed
3177
3178
	    SET(NO_RCFILE);
	    break;
3179
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3180
3181
3182
3183
#ifndef NANO_SMALL
	case 'M':
	    SET(MAC_FILE);
	    break;
3184
3185
	case 'N':
	    SET(NO_CONVERT);
Chris Allegretta's avatar
Chris Allegretta committed
3186
	    break;
3187
3188
#endif
#ifndef DISABLE_JUSTIFY
3189
	case 'Q':
Chris Allegretta's avatar
Chris Allegretta committed
3190
	    quotestr = mallocstrcpy(quotestr, optarg);
3191
	    break;
3192
#endif
3193
#ifdef HAVE_REGEX_H
3194
3195
3196
	case 'R':
	    SET(USE_REGEXP);
	    break;
3197
3198
3199
3200
3201
#endif
#ifndef NANO_SMALL
	case 'S':
	    SET(SMOOTHSCROLL);
	    break;
3202
#endif
3203
	case 'T':
Chris Allegretta's avatar
Chris Allegretta committed
3204
3205
3206
3207
	    {
		int i;
		char *first_error;

Chris Allegretta's avatar
Chris Allegretta committed
3208
3209
		/* Using strtol() instead of atoi() lets us accept 0
		 * while checking other errors. */
Chris Allegretta's avatar
Chris Allegretta committed
3210
		i = (int)strtol(optarg, &first_error, 10);
Chris Allegretta's avatar
Chris Allegretta committed
3211
		if (errno == ERANGE || *optarg == '\0' || *first_error != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
3212
		    usage();
Chris Allegretta's avatar
Chris Allegretta committed
3213
		else
Chris Allegretta's avatar
Chris Allegretta committed
3214
3215
3216
3217
3218
		    tabsize = i;
		if (tabsize <= 0) {
		    fprintf(stderr, _("Tab size is too small for nano...\n"));
		    exit(1);
		}
3219
3220
	    }
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
3221
3222
3223
	case 'V':
	    version();
	    exit(0);
3224
3225
3226
3227
3228
#ifdef ENABLE_COLOR
	case 'Y':
	    syntaxstr = mallocstrcpy(syntaxstr, optarg);
	    break;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3229
3230
3231
	case 'c':
	    SET(CONSTUPDATE);
	    break;
3232
3233
3234
	case 'd':
	    SET(REBIND_DELETE);
	    break;
3235
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
3236
3237
3238
	case 'i':
	    SET(AUTOINDENT);
	    break;
3239
3240
3241
	case 'k':
	    SET(CUT_TO_END);
	    break;
3242
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3243
	case 'l':
3244
	    SET(NOFOLLOW_SYMLINKS);
Chris Allegretta's avatar
Chris Allegretta committed
3245
	    break;
3246
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3247
3248
3249
	case 'm':
	    SET(USE_MOUSE);
	    break;
3250
#endif
3251
3252
#ifndef DISABLE_OPERATINGDIR
	case 'o':
3253
	    operating_dir = mallocstrcpy(operating_dir, optarg);
3254
3255
	    break;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3256
	case 'p':
3257
3258
3259
	    SET(PRESERVE);
#ifdef HAVE_GETOPT_LONG
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3260
	    break;
3261
#ifndef DISABLE_WRAPJUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3262
3263
3264
3265
3266
	case 'r':
	    {
		int i;
		char *first_error;

Chris Allegretta's avatar
Chris Allegretta committed
3267
3268
		/* Using strtol() instead of atoi() lets us accept 0
		 * while checking other errors. */
Chris Allegretta's avatar
Chris Allegretta committed
3269
		i = (int)strtol(optarg, &first_error, 10);
Chris Allegretta's avatar
Chris Allegretta committed
3270
		if (errno == ERANGE || *optarg == '\0' || *first_error != '\0')
Chris Allegretta's avatar
Chris Allegretta committed
3271
		    usage();
Chris Allegretta's avatar
Chris Allegretta committed
3272
		else
Chris Allegretta's avatar
Chris Allegretta committed
3273
		    wrap_at = i;
Chris Allegretta's avatar
Chris Allegretta committed
3274
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3275
	    fill_flag_used = 1;
Chris Allegretta's avatar
Chris Allegretta committed
3276
	    break;
3277
#endif
3278
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
3279
	case 's':
3280
	    alt_speller = mallocstrcpy(alt_speller, optarg);
Chris Allegretta's avatar
Chris Allegretta committed
3281
	    break;
3282
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3283
	case 't':
3284
	    SET(TEMP_OPT);
Chris Allegretta's avatar
Chris Allegretta committed
3285
3286
3287
3288
	    break;
	case 'v':
	    SET(VIEW_MODE);
	    break;
3289
#ifndef DISABLE_WRAPPING
3290
	case 'w':
Chris Allegretta's avatar
Chris Allegretta committed
3291
3292
	    SET(NO_WRAP);
	    break;
3293
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
	case 'x':
	    SET(NO_HELP);
	    break;
	case 'z':
	    SET(SUSPEND);
	    break;
	default:
	    usage();
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
/* We've read through the command line options.  Now back up the flags
   and values that are set, and read the rcfile(s).  If the values
   haven't changed afterward, restore the backed-up values. */
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE)) {
#ifndef DISABLE_OPERATINGDIR
	char *operating_dir_cpy = operating_dir;
#endif
#ifndef DISABLE_WRAPPING
	int wrap_at_cpy = wrap_at;
#endif
#ifndef DISABLE_JUSTIFY
	char *quotestr_cpy = quotestr;
#endif
#ifndef DISABLE_SPELLER
	char *alt_speller_cpy = alt_speller;
#endif
	int tabsize_cpy = tabsize;
	long flags_cpy = flags;

3325
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3326
	operating_dir = NULL;
3327
3328
#endif
#ifndef DISABLE_JUSTIFY
Chris Allegretta's avatar
Chris Allegretta committed
3329
	quotestr = NULL;
3330
3331
#endif
#ifndef DISABLE_SPELLER
Chris Allegretta's avatar
Chris Allegretta committed
3332
	alt_speller = NULL;
3333
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368

	do_rcfile();

#ifndef DISABLE_OPERATINGDIR
	if (operating_dir_cpy != NULL) {
	    free(operating_dir);
	    operating_dir = operating_dir_cpy;
	}
#endif
#ifndef DISABLE_WRAPPING
	if (fill_flag_used)
	    wrap_at = wrap_at_cpy;
#endif
#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
	if (tabsize_cpy > 0)
	    tabsize = tabsize_cpy;
	flags |= flags_cpy;
    }
#if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING)
    else if (geteuid() == 0)
	SET(NO_WRAP);
#endif
#endif /* ENABLE_NANORC */

3369
3370
3371
3372
3373
3374
3375
3376
#ifndef NANO_SMALL
    history_init();
#ifdef ENABLE_NANORC
    if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG))
	load_history();
#endif
#endif

3377
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
3378
3379
    /* Set up the operating directory.  This entails chdir()ing there,
       so that file reads and writes will be based there. */
3380
3381
3382
    init_operating_dir();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
#ifndef DISABLE_JUSTIFY
    if (quotestr == NULL)
#ifdef HAVE_REGEX_H
	quotestr = mallocstrcpy(NULL, "^([ \t]*[|>:}#])+");
#else
	quotestr = mallocstrcpy(NULL, "> ");
#endif
#endif /* !DISABLE_JUSTIFY */
    if (tabsize == -1)
	tabsize = 8;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3394
3395
3396
3397
    /* Clear the filename we'll be using */
    filename = charalloc(1);
    filename[0] = '\0';

Chris Allegretta's avatar
Chris Allegretta committed
3398
3399
3400
3401
3402
3403
3404
    /* If there's a +LINE flag, it is the first non-option argument. */
    if (0 < optind && optind < argc && argv[optind][0] == '+') {
	startline = atoi(&argv[optind][1]);
	optind++;
    }
    if (0 < optind && optind < argc)
	filename = mallocstrcpy(filename, argv[optind]);
Chris Allegretta's avatar
Chris Allegretta committed
3405
3406
3407

    /* See if there's a non-option in argv (first non-option is the
       filename, if +LINE is not given) */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3408
    if (argc > 1 && argc > optind) {
Chris Allegretta's avatar
Chris Allegretta committed
3409
3410
3411
3412
	/* Look for the +line flag... */
	if (argv[optind][0] == '+') {
	    startline = atoi(&argv[optind][1]);
	    optind++;
Chris Allegretta's avatar
Chris Allegretta committed
3413
	    if (argc > optind)
Chris Allegretta's avatar
Chris Allegretta committed
3414
3415
3416
		filename = mallocstrcpy(filename, argv[optind]);
	} else
	    filename = mallocstrcpy(filename, argv[optind]);
Chris Allegretta's avatar
Chris Allegretta committed
3417
3418
    }

3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
    /* First back up the old settings so they can be restored, duh */
    tcgetattr(0, &oldterm);

#ifdef _POSIX_VDISABLE
    term = oldterm;
    term.c_cc[VINTR] = _POSIX_VDISABLE;
    term.c_cc[VQUIT] = _POSIX_VDISABLE;
    term.c_lflag &= ~IEXTEN;
    tcsetattr(0, TCSANOW, &term);
#endif
3429

Chris Allegretta's avatar
Chris Allegretta committed
3430
3431
3432
3433
3434
3435
3436
3437
    /* now ncurses init stuff... */
    initscr();
    savetty();
    nonl();
    cbreak();
    noecho();

    /* Set up some global variables */
3438
    global_init(0);
3439
    shortcut_init(0);
3440
    signal_init();
Chris Allegretta's avatar
Chris Allegretta committed
3441
3442

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

3446
    window_init();
3447
#ifndef DISABLE_MOUSE
3448
    mouse_init();
3449
#endif
3450

3451
3452
    keypad(edit, TRUE);
    keypad(bottomwin, TRUE);
3453

Chris Allegretta's avatar
Chris Allegretta committed
3454
#ifdef DEBUG
3455
    fprintf(stderr, "Main: bottom win\n");
Chris Allegretta's avatar
Chris Allegretta committed
3456
#endif
3457
    /* Set up bottom of window */
Chris Allegretta's avatar
Chris Allegretta committed
3458
3459
3460
    display_main_list();

#ifdef DEBUG
3461
    fprintf(stderr, "Main: open file\n");
Chris Allegretta's avatar
Chris Allegretta committed
3462
3463
#endif

3464
    open_file(filename, 0, 1);
Chris Allegretta's avatar
Chris Allegretta committed
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
#ifdef ENABLE_MULTIBUFFER
    /* If we're using multibuffers and more than one file is specified
       on the command line, load them all and switch to the first one
       afterward */
    if (ISSET(MULTIBUFFER) && optind + 1 < argc) {
	for (optind++; optind < argc; optind++) {
	    add_open_file(1);
	    new_file();
	    filename = mallocstrcpy(filename, argv[optind]);
	    open_file(filename, 0, 0);
	    load_file(0);
	}
	open_nextfile_void();
    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3480

Chris Allegretta's avatar
Chris Allegretta committed
3481
3482
    titlebar(NULL);

Chris Allegretta's avatar
Chris Allegretta committed
3483
    if (startline > 0)
3484
	do_gotoline(startline, 0);
Chris Allegretta's avatar
Chris Allegretta committed
3485

Chris Allegretta's avatar
Chris Allegretta committed
3486
    /* Return here after a sigwinch */
3487
    sigsetjmp(jmpbuf, 1);
3488

3489
3490
3491
3492
3493
    /* SHUT UP GCC! */
    startline = 0;
    fill_flag_used = 0;
    keyhandled = 0;

Chris Allegretta's avatar
Chris Allegretta committed
3494
3495
    /* This variable should be initialized after the sigsetjmp(), so we
       can't do Esc-Esc then quickly resize and muck things up. */
3496
3497
    modify_control_seq = 0;

Robert Siemborski's avatar
Robert Siemborski committed
3498
3499
3500
    edit_refresh();
    reset_cursor();

Chris Allegretta's avatar
Chris Allegretta committed
3501
    while (1) {
3502
	keyhandled = 0;
3503

3504
3505
3506
	if (ISSET(CONSTUPDATE))
	    do_cursorpos(1);

3507
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
3508
	currshortcut = main_list;
3509
#endif
3510

3511
3512
3513
3514
3515
#ifndef _POSIX_VDISABLE
	/* We're going to have to do it the old way, i.e. on cygwin */
	raw();
#endif

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3516
	kbinput = get_kbinput(edit, &meta);
3517
#ifdef DEBUG
3518
	fprintf(stderr, "AHA!  %c (%d)\n", kbinput, kbinput);
3519
#endif
3520
	if (meta == 1) {
3521
	    /* Check for the metaval and misc defs... */
3522
	    for (s = main_list; s != NULL; s = s->next)
3523
3524
		if ((s->metaval != NANO_NO_KEY && kbinput == s->metaval) ||
		    (s->misc != NANO_NO_KEY && kbinput == s->misc)) {
3525
3526
3527
3528
3529
3530
		    if (ISSET(VIEW_MODE) && !s->viewok)
			print_view_warning();
		    else {
			if (s->func != do_cut_text)
			    UNSET(KEEP_CUTBUFFER);
			s->func();
Chris Allegretta's avatar
Chris Allegretta committed
3531
		    }
3532
3533
		    keyhandled = 1;
		}
3534
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
3535
3536
3537
		if (!keyhandled)
		    /* And for toggle switches */
		    for (t = toggles; t != NULL; t = t->next)
3538
			if (kbinput == t->val) {
Chris Allegretta's avatar
Chris Allegretta committed
3539
3540
3541
3542
			    UNSET(KEEP_CUTBUFFER);
			    do_toggle(t);
			    keyhandled = 1;
		        }
3543
#endif
Chris Allegretta's avatar
Chris Allegretta committed
3544
#ifdef DEBUG
3545
3546
	    fprintf(stderr, "I got Alt-%c! (%d)\n", kbinput,
		kbinput);
Chris Allegretta's avatar
Chris Allegretta committed
3547
3548
#endif
	}
3549

Chris Allegretta's avatar
Chris Allegretta committed
3550
	/* Look through the main shortcut list to see if we've hit a
3551
	   shortcut key or function key */
3552

Chris Allegretta's avatar
Chris Allegretta committed
3553
	if (!keyhandled)
3554
#if !defined(DISABLE_BROWSER) || !defined (DISABLE_HELP) || !defined(DISABLE_MOUSE)
Chris Allegretta's avatar
Chris Allegretta committed
3555
	    for (s = currshortcut; s != NULL && !keyhandled; s = s->next) {
3556
#else
Chris Allegretta's avatar
Chris Allegretta committed
3557
	    for (s = main_list; s != NULL && !keyhandled; s = s->next) {
3558
#endif
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3559
3560
		if ((s->val != NANO_NO_KEY && kbinput == s->val) ||
		    (s->func_key != NANO_NO_KEY && kbinput == s->func_key)) {
Chris Allegretta's avatar
Chris Allegretta committed
3561
3562
3563
3564
3565
3566
3567
3568
		    if (ISSET(VIEW_MODE) && !s->viewok)
			print_view_warning();
		    else {
			if (s->func != do_cut_text)
			    UNSET(KEEP_CUTBUFFER);
			s->func();
		    }
		    keyhandled = 1;
3569
3570
		    /* Break out explicitly once we successfully handle
		       a shortcut */
Chris Allegretta's avatar
Chris Allegretta committed
3571
3572
		    break;
		}
Chris Allegretta's avatar
Chris Allegretta committed
3573
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3574
3575
3576

	if (!keyhandled)
	    UNSET(KEEP_CUTBUFFER);
3577
3578
3579

#ifdef _POSIX_VDISABLE
	/* Don't even think about changing this string */
3580
	if (kbinput == NANO_CONTROL_Q)
3581
	    statusbar(_("XON ignored, mumble mumble."));
3582
3583
	if (kbinput == NANO_CONTROL_S)
	    statusbar(_("XOFF ignored, mumble mumble."));
3584
#endif
3585
3586
	/* If we're in raw mode or using Alt-Alt-x, we have to catch
	   Control-S and Control-Q */
3587
	if (kbinput == NANO_CONTROL_Q || kbinput == NANO_CONTROL_S)
3588
3589
	    keyhandled = 1;

3590
3591
	/* Catch ^Z by hand when triggered also */
	if (kbinput == NANO_SUSPEND_KEY) {
3592
3593
3594
3595
3596
	    if (ISSET(SUSPEND))
		do_suspend(0);
	    keyhandled = 1;
	}

Chris Allegretta's avatar
Chris Allegretta committed
3597
3598
3599
	/* Last gasp, stuff that's not in the main lists */
	if (!keyhandled)
	    switch (kbinput) {
3600
#ifndef DISABLE_MOUSE
Chris Allegretta's avatar
Chris Allegretta committed
3601
3602
3603
3604
	    case KEY_MOUSE:
		do_mouse();
		break;
#endif
3605

3606
3607
3608
3609
	    case NANO_CONTROL_3:	/* Ctrl-[ (Esc), which should
	    				 * have been handled before we
	    				 * got here */
	    case NANO_CONTROL_5:	/* Ctrl-] */
Chris Allegretta's avatar
Chris Allegretta committed
3610
3611
3612
		break;
	    default:
#ifdef DEBUG
3613
		fprintf(stderr, "I got %c (%d)!\n", kbinput, kbinput);
Chris Allegretta's avatar
Chris Allegretta committed
3614
3615
3616
3617
#endif
		/* We no longer stop unhandled sequences so that people with
		   odd character sets can type... */

Chris Allegretta's avatar
Chris Allegretta committed
3618
		if (ISSET(VIEW_MODE))
Chris Allegretta's avatar
Chris Allegretta committed
3619
		    print_view_warning();
Chris Allegretta's avatar
Chris Allegretta committed
3620
3621
		else
		    do_char(kbinput);
Chris Allegretta's avatar
Chris Allegretta committed
3622
	    }
Chris Allegretta's avatar
Chris Allegretta committed
3623

Chris Allegretta's avatar
Chris Allegretta committed
3624
3625
3626
	reset_cursor();
	wrefresh(edit);
    }
Chris Allegretta's avatar
Chris Allegretta committed
3627
    assert(0);
Chris Allegretta's avatar
Chris Allegretta committed
3628
}