text.c 66.9 KB
Newer Older
1
2
/* $Id$ */
/**************************************************************************
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3
 *   text.c                                                               *
4
 *                                                                        *
5
6
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
 *   Copyright (C) 2005 David Lawrence Ramsey                             *
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *   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 *
 *   the Free Software Foundation; either version 2, or (at your option)  *
 *   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., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

26
#include <stdio.h>
27
28
29
30
31
32
33
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h>

34
#ifndef NANO_TINY
35
static pid_t pid = -1;
36
37
	/* The PID of the forked process in execute_command(), for use
	 * with the cancel_command() signal handler. */
38
39
#endif
#ifndef DISABLE_WRAPPING
40
static bool prepend_wrap = FALSE;
41
42
43
44
	/* Should we prepend wrapped text to the next line? */
#endif
#ifndef DISABLE_JUSTIFY
static filestruct *jusbottom = NULL;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
45
	/* Pointer to the end of the justify buffer. */
46
47
#endif

48
#ifndef NANO_TINY
49
/* Toggle the mark. */
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void do_mark(void)
{
    openfile->mark_set = !openfile->mark_set;
    if (openfile->mark_set) {
	statusbar(_("Mark Set"));
	openfile->mark_begin = openfile->current;
	openfile->mark_begin_x = openfile->current_x;
    } else {
	statusbar(_("Mark UNset"));
	openfile->mark_begin = NULL;
	openfile->mark_begin_x = 0;
	edit_refresh();
    }
}
64
#endif /* !NANO_TINY */
65

66
/* Delete one character. */
67
68
69
70
71
72
73
74
75
76
77
78
void do_delete(void)
{
    bool do_refresh = FALSE;
	/* Do we have to call edit_refresh(), or can we get away with
	 * update_line()? */

    assert(openfile->current != NULL && openfile->current->data != NULL && openfile->current_x <= strlen(openfile->current->data));

    openfile->placewewant = xplustabs();

    if (openfile->current->data[openfile->current_x] != '\0') {
	int char_buf_len = parse_mbchar(openfile->current->data +
79
		openfile->current_x, NULL, NULL);
80
81
82
83
84
85
86
87
88
89
90
91
	size_t line_len = strlen(openfile->current->data +
		openfile->current_x);

	assert(openfile->current_x < strlen(openfile->current->data));

	/* Let's get dangerous. */
	charmove(&openfile->current->data[openfile->current_x],
		&openfile->current->data[openfile->current_x +
		char_buf_len], line_len - char_buf_len + 1);

	null_at(&openfile->current->data, openfile->current_x +
		line_len - char_buf_len);
92
#ifndef NANO_TINY
93
94
95
96
97
98
	if (openfile->mark_set && openfile->mark_begin ==
		openfile->current && openfile->current_x <
		openfile->mark_begin_x)
	    openfile->mark_begin_x -= char_buf_len;
#endif
	openfile->totsize--;
99
    } else if (openfile->current != openfile->filebot) {
100
101
102
103
104
105
106
107
108
109
110
111
112
	filestruct *foo = openfile->current->next;

	assert(openfile->current_x == strlen(openfile->current->data));

	/* If we're deleting at the end of a line, we need to call
	 * edit_refresh(). */
	if (openfile->current->data[openfile->current_x] == '\0')
	    do_refresh = TRUE;

	openfile->current->data = charealloc(openfile->current->data,
		openfile->current_x + strlen(foo->data) + 1);
	strcpy(openfile->current->data + openfile->current_x,
		foo->data);
113
#ifndef NANO_TINY
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
	if (openfile->mark_set && openfile->mark_begin ==
		openfile->current->next) {
	    openfile->mark_begin = openfile->current;
	    openfile->mark_begin_x += openfile->current_x;
	}
#endif
	if (openfile->filebot == foo)
	    openfile->filebot = openfile->current;

	unlink_node(foo);
	delete_node(foo);
	renumber(openfile->current);
	openfile->totsize--;
#ifndef DISABLE_WRAPPING
	wrap_reset();
#endif
130

131
132
	/* If the NO_NEWLINES flag isn't set, and text has been added to
	 * the magicline as a result of deleting at the end of the line
133
	 * before filebot, add a new magicline. */
134
135
	if (!ISSET(NO_NEWLINES) && openfile->current ==
		openfile->filebot && openfile->current->data[0] != '\0')
136
	    new_magicline();
137
138
139
    } else
	return;

140
    set_modified();
141
142
143
144
145
146
147
148
149
150
151
152
153
154

#ifdef ENABLE_COLOR
    /* If color syntaxes are available and turned on, we need to call
     * edit_refresh(). */
    if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX))
	do_refresh = TRUE;
#endif

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

155
/* Backspace over one character. */
156
157
158
159
void do_backspace(void)
{
    if (openfile->current != openfile->fileage ||
	openfile->current_x > 0) {
160
	do_left();
161
162
163
164
	do_delete();
    }
}

165
166
/* Insert a tab.  If the TABS_TO_SPACES flag is set, insert the number
 * of spaces that a tab would normally take up. */
167
168
void do_tab(void)
{
169
#ifndef NANO_TINY
170
171
    if (ISSET(TABS_TO_SPACES)) {
	char *output;
172
	size_t output_len = 0, new_pww = xplustabs();
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

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

	output = charalloc(output_len + 1);

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

	do_output(output, output_len, TRUE);

	free(output);
    } else {
#endif
	do_output("\t", 1, TRUE);
190
#ifndef NANO_TINY
191
192
193
194
    }
#endif
}

195
/* Someone hits Enter *gasp!* */
196
197
198
199
200
201
202
void do_enter(void)
{
    filestruct *newnode = make_new_node(openfile->current);
    size_t extra = 0;

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

203
#ifndef NANO_TINY
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
    if (ISSET(AUTOINDENT)) {
	/* If we are breaking the line in the indentation, the new
	 * indentation should have only current_x characters, and
	 * current_x should not change. */
	extra = indent_length(openfile->current->data);
	if (extra > openfile->current_x)
	    extra = openfile->current_x;
    }
#endif
    newnode->data = charalloc(strlen(openfile->current->data +
	openfile->current_x) + extra + 1);
    strcpy(&newnode->data[extra], openfile->current->data +
	openfile->current_x);
218
#ifndef NANO_TINY
219
220
221
222
223
224
    if (ISSET(AUTOINDENT)) {
	strncpy(newnode->data, openfile->current->data, extra);
	openfile->totsize += mbstrlen(newnode->data);
    }
#endif
    null_at(&openfile->current->data, openfile->current_x);
225
#ifndef NANO_TINY
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    if (openfile->mark_set && openfile->current ==
	openfile->mark_begin && openfile->current_x <
	openfile->mark_begin_x) {
	openfile->mark_begin = newnode;
	openfile->mark_begin_x += extra - openfile->current_x;
    }
#endif
    openfile->current_x = extra;

    if (openfile->current == openfile->filebot)
	openfile->filebot = newnode;
    splice_node(openfile->current, newnode,
	openfile->current->next);

    renumber(openfile->current);
    openfile->current = newnode;

    openfile->totsize++;
    set_modified();
245

246
    openfile->placewewant = xplustabs();
247
248

    edit_refresh();
249
250
}

251
#ifndef NANO_TINY
252
253
/* Send a SIGKILL (unconditional kill) to the forked process in
 * execute_command(). */
254
RETSIGTYPE cancel_command(int signal)
255
256
257
258
259
{
    if (kill(pid, SIGKILL) == -1)
	nperror("kill");
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
260
/* Execute command in a shell.  Return TRUE on success. */
261
262
263
264
bool execute_command(const char *command)
{
    int fd[2];
    FILE *f;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
265
    char *shellenv;
266
267
268
269
270
271
272
273
274
275
276
    struct sigaction oldaction, newaction;
	/* Original and temporary handlers for SIGINT. */
    bool sig_failed = FALSE;
	/* Did sigaction() fail without changing the signal handlers? */

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
277
    /* Check $SHELL for the shell to use.  If it isn't set, use
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
278
279
     * /bin/sh.  Note that $SHELL should contain only a path, with no
     * arguments. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
280
281
282
283
    shellenv = getenv("SHELL");
    if (shellenv == NULL)
	shellenv = "/bin/sh";

284
285
286
287
288
289
290
    /* Fork a child. */
    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. */
291
	execl(shellenv, tail(shellenv), "-c", command, NULL);
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
	exit(0);
    }

    /* Else continue as parent. */
    close(fd[1]);

    if (pid == -1) {
	close(fd[0]);
	statusbar(_("Could not fork"));
	return FALSE;
    }

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

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

    if (sigaction(SIGINT, NULL, &newaction) == -1) {
	sig_failed = TRUE;
	nperror("sigaction");
    } else {
	newaction.sa_handler = cancel_command;
	if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
	    sig_failed = TRUE;
	    nperror("sigaction");
	}
    }

    /* Note that now oldaction is the previous SIGINT signal handler,
     * to be restored later. */

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

    read_file(f, "stdin");

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

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

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

    return TRUE;
}
348
#endif /* !NANO_TINY */
349
350

#ifndef DISABLE_WRAPPING
351
352
/* Clear the prepend_wrap flag.  We need to do this as soon as we do
 * something other than type text. */
353
354
void wrap_reset(void)
{
355
    prepend_wrap = FALSE;
356
357
358
359
360
361
362
363
}

/* We wrap the given line.  Precondition: we assume the cursor has been
 * moved forward since the last typed character.  Return value: whether
 * we wrapped. */
bool do_wrap(filestruct *line)
{
    size_t line_len;
364
	/* The length of the line we wrap. */
365
    ssize_t wrap_loc;
366
	/* The index of line->data where we wrap. */
367
#ifndef NANO_TINY
368
369
    const char *indent_string = NULL;
	/* Indentation to prepend to the new line. */
370
371
    size_t indent_len = 0;
	/* The length of indent_string. */
372
#endif
373
374
375
376
    const char *after_break;
	/* The text after the wrap point. */
    size_t after_break_len;
	/* The length of after_break. */
377
    bool prepending = FALSE;
378
	/* Do we prepend to the next line? */
379
380
    const char *next_line = NULL;
	/* The next line, minus indentation. */
381
382
383
384
385
386
    size_t next_line_len = 0;
	/* The length of next_line. */
    char *new_line = NULL;
	/* The line we create. */
    size_t new_line_len = 0;
	/* The eventual length of new_line. */
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

    /* There are three steps.  First, we decide where to wrap.  Then, we
     * create the new wrap line.  Finally, we clean up. */

    /* Step 1, finding where to wrap.  We are going to add a new line
     * after a blank character.  In this step, we call break_line() to
     * get the location of the last blank we can break the line at, and
     * and set wrap_loc to the location of the character after it, so
     * that the blank is preserved at the end of the line.
     *
     * If there is no legal wrap point, or we reach the last character
     * of the line while trying to find one, we should return without
     * wrapping.  Note that if autoindent is turned on, we don't break
     * at the end of it! */
    assert(line != NULL && line->data != NULL);

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

    /* Find the last blank where we can break the line. */
407
408
409
410
411
    wrap_loc = break_line(line->data, fill
#ifndef DISABLE_HELP
	, FALSE
#endif
	);
412
413
414
415
416
417
418
419
420
421
422
423
424

    /* If we couldn't break the line, or we've reached the end of it, we
     * don't wrap. */
    if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
	return FALSE;

    /* Otherwise, move forward to the character just after the blank. */
    wrap_loc += move_mbright(line->data + wrap_loc, 0);

    /* If we've reached the end of the line, we don't wrap. */
    if (line->data[wrap_loc] == '\0')
	return FALSE;

425
#ifndef NANO_TINY
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
    /* If autoindent is turned on, and we're on the character just after
     * the indentation, we don't wrap. */
    if (ISSET(AUTOINDENT)) {
	/* Get the indentation of this line. */
	indent_string = line->data;
	indent_len = indent_length(indent_string);

	if (wrap_loc == indent_len)
	    return FALSE;
    }
#endif

    /* Step 2, making the new wrap line.  It will consist of indentation
     * followed by the text after the wrap point, optionally followed by
     * a space (if the text after the wrap point doesn't end in a blank)
     * and the text of the next line, if they can fit without
442
     * wrapping, the next line exists, and the prepend_wrap flag is
443
444
445
446
447
448
449
450
     * set. */

    /* after_break is the text that will be wrapped to the next line. */
    after_break = line->data + wrap_loc;
    after_break_len = line_len - wrap_loc;

    assert(strlen(after_break) == after_break_len);

451
452
453
454
    /* We prepend the wrapped text to the next line, if the prepend_wrap
     * flag is set, there is a next line, and prepending would not make
     * the line too long. */
    if (prepend_wrap && line != openfile->filebot) {
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
	const char *end = after_break + move_mbleft(after_break,
		after_break_len);

	/* If after_break doesn't end in a blank, make sure it ends in a
	 * space. */
	if (!is_blank_mbchar(end)) {
	    line_len++;
	    line->data = charealloc(line->data, line_len + 1);
	    line->data[line_len - 1] = ' ';
	    line->data[line_len] = '\0';
	    after_break = line->data + wrap_loc;
	    after_break_len++;
	    openfile->totsize++;
	}

	next_line = line->next->data;
	next_line_len = strlen(next_line);

	if (after_break_len + next_line_len <= fill) {
474
	    prepending = TRUE;
475
476
477
478
479
480
481
482
483
	    new_line_len += next_line_len;
	}
    }

    /* new_line_len is now the length of the text that will be wrapped
     * to the next line, plus (if we're prepending to it) the length of
     * the text of the next line. */
    new_line_len += after_break_len;

484
#ifndef NANO_TINY
485
    if (ISSET(AUTOINDENT)) {
486
487
	if (prepending) {
	    /* If we're prepending, the indentation will come from the
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
	     * next line. */
	    indent_string = next_line;
	    indent_len = indent_length(indent_string);
	    next_line += indent_len;
	} else {
	    /* Otherwise, it will come from this line, in which case
	     * we should increase new_line_len to make room for it. */
	    new_line_len += indent_len;
	    openfile->totsize += mbstrnlen(indent_string, indent_len);
	}
    }
#endif

    /* Now we allocate the new line and copy the text into it. */
    new_line = charalloc(new_line_len + 1);
    new_line[0] = '\0';

505
#ifndef NANO_TINY
506
507
508
509
510
511
512
513
514
515
516
517
518
519
    if (ISSET(AUTOINDENT)) {
	/* Copy the indentation. */
	strncpy(new_line, indent_string, indent_len);
	new_line[indent_len] = '\0';
	new_line_len += indent_len;
    }
#endif

    /* Copy all the text after the wrap point of the current line. */
    strcat(new_line, after_break);

    /* Break the current line at the wrap point. */
    null_at(&line->data, wrap_loc);

520
521
    if (prepending) {
	/* If we're prepending, copy the text from the next line, minus
522
523
524
525
526
	 * the indentation that we already copied above. */
	strcat(new_line, next_line);

	free(line->next->data);
	line->next->data = new_line;
527
528
529
530
531

	/* If the NO_NEWLINES flag isn't set, and text has been added to
	 * the magicline, make a new magicline. */
	if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
	    new_magicline();
532
533
534
535
536
537
    } else {
	/* Otherwise, make a new line and copy the text after where we
	 * broke this line to the beginning of the new line. */
	splice_node(openfile->current, make_new_node(openfile->current),
		openfile->current->next);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
538
539
	/* If the current line is the last line of the file, move the
	 * last line of the file down to the next line. */
540
541
542
	if (openfile->filebot == openfile->current)
	    openfile->filebot = openfile->current->next;

543
544
545
546
547
548
549
550
	openfile->current->next->data = new_line;

	openfile->totsize++;
    }

    /* Step 3, clean up.  Reposition the cursor and mark, and do some
     * other sundry things. */

551
552
553
    /* Set the prepend_wrap flag, so that later wraps of this line will
     * be prepended to the next line. */
    prepend_wrap = TRUE;
554
555
556

    /* Each line knows its line number.  We recalculate these if we
     * inserted a new line. */
557
    if (!prepending)
558
559
560
	renumber(line);

    /* If the cursor was after the break point, we must move it.  We
561
     * also clear the prepend_wrap flag in this case. */
562
    if (openfile->current_x > wrap_loc) {
563
	prepend_wrap = FALSE;
564

565
566
	openfile->current = openfile->current->next;
	openfile->current_x -= wrap_loc
567
#ifndef NANO_TINY
568
569
570
571
572
573
		- indent_len
#endif
		;
	openfile->placewewant = xplustabs();
    }

574
#ifndef NANO_TINY
575
    /* If the mark was on this line after the wrap point, we move it
576
     * down.  If it was on the next line and we prepended to that line,
577
578
579
580
581
582
     * we move it right. */
    if (openfile->mark_set) {
	if (openfile->mark_begin == line && openfile->mark_begin_x >
		wrap_loc) {
	    openfile->mark_begin = line->next;
	    openfile->mark_begin_x -= wrap_loc - indent_len + 1;
583
	} else if (prepending && openfile->mark_begin == line->next)
584
585
586
587
588
589
590
591
	    openfile->mark_begin_x += after_break_len;
    }
#endif

    return TRUE;
}
#endif /* !DISABLE_WRAPPING */

592
#if !defined(DISABLE_HELP) || !defined(DISABLE_WRAPJUSTIFY)
593
/* We are trying to break a chunk off line.  We find the last blank such
594
595
 * that the display length to there is at most (goal + 1).  If there is
 * no such blank, then we find the first blank.  We then take the last
596
597
 * blank in that group of blanks.  The terminating '\0' counts as a
 * blank, as does a '\n' if newline is TRUE. */
598
599
600
601
602
ssize_t break_line(const char *line, ssize_t goal
#ifndef DISABLE_HELP
	, bool newline
#endif
	)
603
{
604
605
606
607
608
609
    ssize_t blank_loc = -1;
	/* Current tentative return value.  Index of the last blank we
	 * found with short enough display width.  */
    ssize_t cur_loc = 0;
	/* Current index in line. */
    int line_len;
610

611
    assert(line != NULL);
612

613
    while (*line != '\0' && goal >= 0) {
614
	size_t pos = 0;
615

616
	line_len = parse_mbchar(line, NULL, &pos);
617

618
619
620
621
622
	if (is_blank_mbchar(line)
#ifndef DISABLE_HELP
		|| (newline && *line == '\n')
#endif
		) {
623
	    blank_loc = cur_loc;
624

625
#ifndef DISABLE_HELP
626
627
	    if (newline && *line == '\n')
		break;
628
#endif
629
630
	}

631
	goal -= pos;
632
633
	line += line_len;
	cur_loc += line_len;
634
635
    }

636
637
638
    if (goal >= 0)
	/* In fact, the whole line displays shorter than goal. */
	return cur_loc;
639

640
641
642
    if (blank_loc == -1) {
	/* No blank was found that was short enough. */
	bool found_blank = FALSE;
643
	ssize_t found_blank_loc = 0;
644

645
	while (*line != '\0') {
646
	    line_len = parse_mbchar(line, NULL, NULL);
647

648
649
650
651
652
	    if (is_blank_mbchar(line)
#ifndef DISABLE_HELP
		|| (newline && *line == '\n')
#endif
		) {
653
654
		if (!found_blank)
		    found_blank = TRUE;
655
		found_blank_loc = cur_loc;
656
	    } else if (found_blank)
657
		return found_blank_loc;
658

659
660
661
	    line += line_len;
	    cur_loc += line_len;
	}
662

663
664
	return -1;
    }
665

666
667
668
    /* Move to the last blank after blank_loc, if there is one. */
    line -= cur_loc;
    line += blank_loc;
669
    line_len = parse_mbchar(line, NULL, NULL);
670
    line += line_len;
671

672
673
674
675
676
    while (*line != '\0' && (is_blank_mbchar(line)
#ifndef DISABLE_HELP
	|| (newline && *line == '\n')
#endif
	)) {
677
	line_len = parse_mbchar(line, NULL, NULL);
678

679
680
	line += line_len;
	blank_loc += line_len;
681
682
    }

683
684
    return blank_loc;
}
685
#endif /* !DISABLE_HELP || !DISABLE_WRAPJUSTIFY */
686

687
#if !defined(NANO_TINY) || !defined(DISABLE_JUSTIFY)
688
689
690
691
692
693
694
/* The "indentation" of a line is the whitespace between the quote part
 * and the non-whitespace of the line. */
size_t indent_length(const char *line)
{
    size_t len = 0;
    char *blank_mb;
    int blank_mb_len;
695

696
    assert(line != NULL);
697

698
    blank_mb = charalloc(mb_cur_max());
699

700
    while (*line != '\0') {
701
	blank_mb_len = parse_mbchar(line, blank_mb, NULL);
702

703
704
	if (!is_blank_mbchar(blank_mb))
	    break;
705

706
707
708
	line += blank_mb_len;
	len += blank_mb_len;
    }
709

710
711
712
    free(blank_mb);

    return len;
713
}
714
#endif /* !NANO_TINY || !DISABLE_JUSTIFY */
715

716
717
718
719
720
721
722
723
724
725
726
727
#ifndef DISABLE_JUSTIFY
/* justify_format() replaces blanks with spaces and multiple spaces by 1
 * (except it maintains up to 2 after a character in punct optionally
 * followed by a character in brackets, and removes all from the end).
 *
 * justify_format() might make paragraph->data shorter, and change the
 * actual pointer with null_at().
 *
 * justify_format() will not look at the first skip characters of
 * paragraph.  skip should be at most strlen(paragraph->data).  The
 * character at paragraph[skip + 1] must not be blank. */
void justify_format(filestruct *paragraph, size_t skip)
728
{
729
730
    char *end, *new_end, *new_paragraph_data;
    size_t shift = 0;
731
#ifndef NANO_TINY
732
733
    size_t mark_shift = 0;
#endif
734

735
736
737
738
739
    /* These four asserts are assumptions about the input data. */
    assert(paragraph != NULL);
    assert(paragraph->data != NULL);
    assert(skip < strlen(paragraph->data));
    assert(!is_blank_mbchar(paragraph->data + skip));
740

741
742
743
744
    end = paragraph->data + skip;
    new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
    strncpy(new_paragraph_data, paragraph->data, skip);
    new_end = new_paragraph_data + skip;
745

746
747
    while (*end != '\0') {
	int end_len;
748

749
750
751
	/* If this character is blank, make sure that it's a space with
	 * no blanks after it. */
	if (is_blank_mbchar(end)) {
752
	    end_len = parse_mbchar(end, NULL, NULL);
753

754
755
756
	    *new_end = ' ';
	    new_end++;
	    end += end_len;
757

758
	    while (*end != '\0' && is_blank_mbchar(end)) {
759
		end_len = parse_mbchar(end, NULL, NULL);
760

761
762
		end += end_len;
		shift += end_len;
763

764
#ifndef NANO_TINY
765
766
767
768
769
770
771
772
773
774
775
776
		/* Keep track of the change in the current line. */
		if (openfile->mark_set && openfile->mark_begin ==
			paragraph && openfile->mark_begin_x >= end -
			paragraph->data)
		    mark_shift += end_len;
#endif
	    }
	/* If this character is punctuation optionally followed by a
	 * bracket and then followed by blanks, make sure there are no
	 * more than two blanks after it, and make sure that the blanks
	 * are spaces. */
	} else if (mbstrchr(punct, end) != NULL) {
777
	    end_len = parse_mbchar(end, NULL, NULL);
778

779
780
781
782
783
784
	    while (end_len > 0) {
		*new_end = *end;
		new_end++;
		end++;
		end_len--;
	    }
785

786
	    if (*end != '\0' && mbstrchr(brackets, end) != NULL) {
787
		end_len = parse_mbchar(end, NULL, NULL);
788

789
790
791
792
793
794
795
		while (end_len > 0) {
		    *new_end = *end;
		    new_end++;
		    end++;
		    end_len--;
		}
	    }
796

797
	    if (*end != '\0' && is_blank_mbchar(end)) {
798
		end_len = parse_mbchar(end, NULL, NULL);
799

800
801
802
803
		*new_end = ' ';
		new_end++;
		end += end_len;
	    }
804

805
	    if (*end != '\0' && is_blank_mbchar(end)) {
806
		end_len = parse_mbchar(end, NULL, NULL);
807

808
809
810
811
		*new_end = ' ';
		new_end++;
		end += end_len;
	    }
812

813
	    while (*end != '\0' && is_blank_mbchar(end)) {
814
		end_len = parse_mbchar(end, NULL, NULL);
815

816
817
		end += end_len;
		shift += end_len;
818

819
#ifndef NANO_TINY
820
821
822
823
824
825
826
827
828
829
		/* Keep track of the change in the current line. */
		if (openfile->mark_set && openfile->mark_begin ==
			paragraph && openfile->mark_begin_x >= end -
			paragraph->data)
		    mark_shift += end_len;
#endif
	    }
	/* If this character is neither blank nor punctuation, leave it
	 * alone. */
	} else {
830
	    end_len = parse_mbchar(end, NULL, NULL);
831

832
833
834
835
836
837
838
	    while (end_len > 0) {
		*new_end = *end;
		new_end++;
		end++;
		end_len--;
	    }
	}
839
840
    }

841
    assert(*end == '\0');
842

843
    *new_end = *end;
844

845
846
847
848
849
    /* Make sure that there are no spaces at the end of the line. */
    while (new_end > new_paragraph_data + skip &&
	*(new_end - 1) == ' ') {
	new_end--;
	shift++;
850
851
    }

852
853
854
855
856
    if (shift > 0) {
	openfile->totsize -= shift;
	null_at(&new_paragraph_data, new_end - new_paragraph_data);
	free(paragraph->data);
	paragraph->data = new_paragraph_data;
857

858
#ifndef NANO_TINY
859
860
861
862
863
864
	/* Adjust the mark coordinates to compensate for the change in
	 * the current line. */
	if (openfile->mark_set && openfile->mark_begin == paragraph) {
	    openfile->mark_begin_x -= mark_shift;
	    if (openfile->mark_begin_x > new_end - new_paragraph_data)
		openfile->mark_begin_x = new_end - new_paragraph_data;
865
	}
866
867
868
869
#endif
    } else
	free(new_paragraph_data);
}
870

871
872
873
874
875
876
877
878
879
880
881
/* 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. */
size_t quote_length(const char *line)
{
#ifdef HAVE_REGEX_H
    regmatch_t matches;
    int rc = regexec(&quotereg, line, 1, &matches, 0);
882

883
884
885
886
887
888
889
    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;
#else	/* !HAVE_REGEX_H */
    size_t qdepth = 0;
890

891
892
893
894
895
896
    /* Compute quote depth level. */
    while (strncmp(line + qdepth, quotestr, quotelen) == 0)
	qdepth += quotelen;
    return qdepth;
#endif	/* !HAVE_REGEX_H */
}
897

898
899
900
901
902
903
904
905
/* 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. */
bool quotes_match(const char *a_line, size_t a_quote, const char
	*b_line)
{
    /* Here is the assumption about a_quote. */
    assert(a_quote == quote_length(a_line));
906

907
908
909
    return (a_quote == quote_length(b_line) &&
	strncmp(a_line, b_line, a_quote) == 0);
}
910

911
912
913
914
915
916
917
918
919
920
/* We assume a_line and b_line have no quote part.  Then, we return
 * whether b_line could follow a_line in a paragraph. */
bool indents_match(const char *a_line, size_t a_indent, const char
	*b_line, size_t b_indent)
{
    assert(a_indent == indent_length(a_line));
    assert(b_indent == indent_length(b_line));

    return (b_indent <= a_indent &&
	strncmp(a_line, b_line, b_indent) == 0);
921
922
}

923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
/* Is foo the beginning of a paragraph?
 *
 *   A line of text consists of a "quote part", followed by an
 *   "indentation part", followed by text.  The functions quote_length()
 *   and indent_length() calculate these parts.
 *
 *   A line is "part of a paragraph" if it has a part not in the quote
 *   part or the indentation.
 *
 *   A line is "the beginning of a paragraph" if it is part of a
 *   paragraph and
 *	1) it is the top line of the file, or
 *	2) the line above it is not part of a paragraph, or
 *	3) the line above it does not have precisely the same quote
 *	   part, or
 *	4) the indentation of this line is not an initial substring of
 *	   the indentation of the previous line, or
 *	5) this line has no quote part and some indentation, and
 *	   autoindent isn't turned on.
 *   The reason for number 5) is that if autoindent isn't turned on,
 *   then an indented line is expected to start a paragraph, as in
 *   books.  Thus, nano can justify an indented paragraph only if
 *   autoindent is turned on. */
bool begpar(const filestruct *const foo)
947
{
948
949
950
951
    size_t quote_len, indent_len, temp_id_len;

    if (foo == NULL)
	return FALSE;
952

953
    /* Case 1). */
954
    if (foo == openfile->fileage)
955
	return TRUE;
956

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

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

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

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

970
971
972
    /* Case 2) or 5) or 4). */
    if (foo->prev->data[quote_len + temp_id_len] == '\0' ||
	(quote_len == 0 && indent_len > 0
973
#ifndef NANO_TINY
974
975
976
977
978
	&& !ISSET(AUTOINDENT)
#endif
	) || !indents_match(foo->prev->data + quote_len, temp_id_len,
	foo->data + quote_len, indent_len))
	return TRUE;
979

980
981
    return FALSE;
}
982

983
984
985
986
/* Is foo inside a paragraph? */
bool inpar(const filestruct *const foo)
{
    size_t quote_len;
987

988
989
    if (foo == NULL)
	return FALSE;
990

991
    quote_len = quote_length(foo->data);
992

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
993
994
    return (foo->data[quote_len + indent_length(foo->data +
	quote_len)] != '\0');
995
}
996

997
/* Move the next par_len lines, starting with first_line, into the
998
999
 * justify buffer, leaving copies of those lines in place.  Assume that
 * par_len is greater than zero, and that there are enough lines after
1000
1001
 * first_line. */
void backup_lines(filestruct *first_line, size_t par_len)
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
{
    filestruct *top = first_line;
	/* The top of the paragraph we're backing up. */
    filestruct *bot = first_line;
	/* The bottom of the paragraph we're backing up. */
    size_t i;
	/* Generic loop variable. */
    size_t current_x_save = openfile->current_x;
    ssize_t fl_lineno_save = first_line->lineno;
    ssize_t edittop_lineno_save = openfile->edittop->lineno;
    ssize_t current_lineno_save = openfile->current->lineno;
1013
#ifndef NANO_TINY
1014
1015
1016
    bool old_mark_set = openfile->mark_set;
    ssize_t mb_lineno_save = 0;
    size_t mark_begin_x_save = 0;
1017
1018

    if (old_mark_set) {
1019
1020
	mb_lineno_save = openfile->mark_begin->lineno;
	mark_begin_x_save = openfile->mark_begin_x;
1021
1022
1023
    }
#endif

1024
1025
1026
    /* par_len will be one greater than the number of lines between
     * current and filebot if filebot is the last line in the
     * paragraph. */
1027
    assert(par_len > 0 && openfile->current->lineno + par_len <=
1028
	openfile->filebot->lineno + 1);
1029

1030
1031
1032
    /* Move bot down par_len lines to the line after the last line of
     * the paragraph, if there is one. */
    for (i = par_len; i > 0 && bot != openfile->filebot; i--)
1033
	bot = bot->next;
1034

1035
1036
    /* Move the paragraph from the current buffer's filestruct to the
     * justify buffer. */
1037
    move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot,
1038
	(i == 1 && bot == openfile->filebot) ? strlen(bot->data) : 0);
1039

1040
1041
1042
    /* Copy the paragraph back to the current buffer's filestruct from
     * the justify buffer. */
    copy_from_filestruct(jusbuffer, jusbottom);
1043

1044
1045
1046
1047
    /* Move upward from the last line of the paragraph to the first
     * line, putting first_line, edittop, current, and mark_begin at the
     * same lines in the copied paragraph that they had in the original
     * paragraph. */
1048
    if (openfile->current != openfile->fileage)
1049
1050
1051
	top = openfile->current->prev;
    else
	top = openfile->current;
1052
    for (i = par_len; i > 0 && top != NULL; i--) {
1053
1054
1055
1056
1057
1058
	if (top->lineno == fl_lineno_save)
	    first_line = top;
	if (top->lineno == edittop_lineno_save)
	    openfile->edittop = top;
	if (top->lineno == current_lineno_save)
	    openfile->current = top;
1059
#ifndef NANO_TINY
1060
1061
1062
	if (old_mark_set && top->lineno == mb_lineno_save) {
	    openfile->mark_begin = top;
	    openfile->mark_begin_x = mark_begin_x_save;
1063
	}
1064
1065
1066
#endif
	top = top->prev;
    }
1067

1068
1069
1070
    /* Put current_x at the same place in the copied paragraph that it
     * had in the original paragraph. */
    openfile->current_x = current_x_save;
1071

1072
1073
    set_modified();
}
1074

1075
1076
1077
1078
1079
/* Find the beginning of the current paragraph if we're in one, or the
 * beginning of the next paragraph if we're not.  Afterwards, save the
 * quote length and paragraph length in *quote and *par.  Return TRUE if
 * we found a paragraph, or FALSE if there was an error or we didn't
 * find a paragraph.
1080
1081
1082
 *
 * See the comment at begpar() for more about when a line is the
 * beginning of a paragraph. */
1083
bool find_paragraph(size_t *const quote, size_t *const par)
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
{
    size_t quote_len;
	/* Length of the initial quotation of the paragraph we search
	 * for. */
    size_t par_len;
	/* Number of lines in the paragraph we search for. */
    filestruct *current_save;
	/* The line at the beginning of the paragraph we search for. */
    ssize_t current_y_save;
	/* The y-coordinate at the beginning of the paragraph we search
	 * for. */
1095

1096
1097
1098
1099
#ifdef HAVE_REGEX_H
    if (quoterc != 0) {
	statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
	return FALSE;
1100
1101
1102
    }
#endif

1103
    assert(openfile->current != NULL);
1104

1105
1106
1107
1108
1109
1110
1111
    /* If we're at the end of the last line of the file, it means that
     * there aren't any paragraphs left, so get out. */
    if (openfile->current == openfile->filebot && openfile->current_x ==
	strlen(openfile->filebot->data))
	return FALSE;

    /* If the current line isn't in a paragraph, move forward to the
1112
     * last line of the next paragraph, if any. */
1113
1114
    if (!inpar(openfile->current)) {
	do_para_end(FALSE);
1115

1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
	/* If we end up past the beginning of the line, it means that
	 * we're at the end of the last line of the file, and the line
	 * isn't blank, in which case the last line of the file is the
	 * last line of the next paragraph.
	 *
	 * Otherwise, if we end up on a line that's in a paragraph, it
	 * means that we're on the line after the last line of the next
	 * paragraph, in which case we should move back to the last line
	 * of the next paragraph. */
	if (openfile->current_x == 0) {
	    if (!inpar(openfile->current->prev))
		return FALSE;
	    if (openfile->current != openfile->fileage)
		openfile->current = openfile->current->prev;
	}
1131
    }
1132

1133
    /* If the current line isn't the first line of the paragraph, move
1134
1135
     * back to the first line of the paragraph. */
    if (!begpar(openfile->current))
1136
	do_para_begin(FALSE);
1137

1138
1139
    /* Now current is the first line of the paragraph.  Set quote_len to
     * the quotation length of that line, and set par_len to the number
1140
     * of lines in this paragraph. */
1141
1142
1143
1144
1145
    quote_len = quote_length(openfile->current->data);
    current_save = openfile->current;
    current_y_save = openfile->current_y;
    do_para_end(FALSE);
    par_len = openfile->current->lineno - current_save->lineno;
1146

1147
1148
1149
1150
    /* If we end up past the beginning of the line, it means that we're
     * at the end of the last line of the file, and the line isn't
     * blank, in which case the last line of the file is part of the
     * paragraph. */
1151
1152
    if (openfile->current_x > 0)
	par_len++;
1153
1154
    openfile->current = current_save;
    openfile->current_y = current_y_save;
1155

1156
1157
    /* Save the values of quote_len and par_len. */
    assert(quote != NULL && par != NULL);
1158

1159
1160
    *quote = quote_len;
    *par = par_len;
1161

1162
    return TRUE;
1163
1164
}

1165
1166
1167
/* If full_justify is TRUE, justify the entire file.  Otherwise, justify
 * the current paragraph. */
void do_justify(bool full_justify)
1168
{
1169
    filestruct *first_par_line = NULL;
1170
1171
	/* Will be the first line of the justified paragraph(s), if any.
	 * For restoring after unjustify. */
1172
    filestruct *last_par_line = NULL;
1173
	/* Will be the line after the last line of the justified
1174
	 * paragraph(s), if any.  Also for restoring after unjustify. */
1175
    bool filebot_inpar = FALSE;
1176
1177
	/* Whether the text at filebot is part of the current
	 * paragraph. */
1178

1179
1180
    /* We save these variables to be restored if the user
     * unjustifies. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1181
1182
    filestruct *edittop_save = openfile->edittop;
    filestruct *current_save = openfile->current;
1183
1184
1185
    size_t current_x_save = openfile->current_x;
    size_t pww_save = openfile->placewewant;
    size_t totsize_save = openfile->totsize;
1186
#ifndef NANO_TINY
1187
1188
1189
    filestruct *mark_begin_save = openfile->mark_begin;
    size_t mark_begin_x_save = openfile->mark_begin_x;
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1190
1191
    bool modified_save = openfile->modified;

1192
1193
    int kbinput;
    bool meta_key, func_key, s_or_t, ran_func, finished;
1194

1195
    /* Move to the beginning of the current line, so that justifying at
1196
1197
     * the end of the last line of the file, if that line isn't blank,
     * will work the first time through. */
1198
1199
    openfile->current_x = 0;

1200
1201
1202
    /* If we're justifying the entire file, start at the beginning. */
    if (full_justify)
	openfile->current = openfile->fileage;
1203

1204
1205
1206
    while (TRUE) {
	size_t i;
	    /* Generic loop variable. */
1207
1208
	filestruct *curr_first_par_line;
	    /* The first line of the current paragraph. */
1209
	size_t quote_len;
1210
1211
	    /* Length of the initial quotation of the current
	     * paragraph. */
1212
	size_t indent_len;
1213
1214
	    /* Length of the initial indentation of the current
	     * paragraph. */
1215
	size_t par_len;
1216
	    /* Number of lines in the current paragraph. */
1217
1218
1219
1220
	ssize_t break_pos;
	    /* Where we will break lines. */
	char *indent_string;
	    /* The first indentation that doesn't match the initial
1221
1222
1223
1224
1225
1226
1227
	     * indentation of the current paragraph.  This is put at the
	     * beginning of every line broken off the first justified
	     * line of the paragraph.  Note that this works because a
	     * paragraph can only contain two indentations at most: the
	     * initial one, and a different one starting on a line after
	     * the first.  See the comment at begpar() for more about
	     * when a line is part of a paragraph. */
1228

1229
1230
1231
1232
1233
1234
1235
	/* Find the first line of the paragraph to be justified.  That
	 * is the start of this paragraph if we're in one, or the start
	 * of the next otherwise.  Save the quote length and paragraph
	 * length (number of lines).  Don't refresh the screen yet,
	 * since we'll do that after we justify.
	 *
	 * If the search failed, we do one of two things.  If we're
1236
1237
	 * justifying the whole file, and we've found at least one
	 * paragraph, it means that we should justify all the way to the
1238
1239
1240
1241
	 * last line of the file, so set the last line of the text to be
	 * justified to the last line of the file and break out of the
	 * loop.  Otherwise, it means that there are no paragraph(s) to
	 * justify, so refresh the screen and get out. */
1242
	if (!find_paragraph(&quote_len, &par_len)) {
1243
	    if (full_justify && first_par_line != NULL) {
1244
		last_par_line = openfile->filebot;
1245
		break;
1246
1247
1248
1249
	    } else {
		edit_refresh();
		return;
	    }
1250
1251
	}

1252
1253
1254
1255
1256
1257
	/* par_len will be one greater than the number of lines between
	 * current and filebot if filebot is the last line in the
	 * paragraph.  Set filebot_inpar to TRUE if this is the case. */
	filebot_inpar = (openfile->current->lineno + par_len ==
		openfile->filebot->lineno + 1);

1258
1259
1260
1261
1262
1263
	/* If we haven't already done it, move the original paragraph(s)
	 * to the justify buffer, splice a copy of the original
	 * paragraph(s) into the file in the same place, and set
	 * first_par_line to the first line of the copy. */
	if (first_par_line == NULL) {
	    backup_lines(openfile->current, full_justify ?
1264
1265
		openfile->filebot->lineno - openfile->current->lineno +
		((openfile->filebot->data[0] != '\0') ? 1 : 0) :
1266
		par_len);
1267
1268
	    first_par_line = openfile->current;
	}
1269

1270
1271
1272
1273
	/* Set curr_first_par_line to the first line of the current
	 * paragraph. */
	curr_first_par_line = openfile->current;

1274
1275
	/* Initialize indent_string to a blank string. */
	indent_string = mallocstrcpy(NULL, "");
1276

1277
1278
1279
1280
1281
1282
1283
	/* Find the first indentation in the paragraph that doesn't
	 * match the indentation of the first line, and save itin
	 * indent_string.  If all the indentations are the same, save
	 * the indentation of the first line in indent_string. */
	{
	    const filestruct *indent_line = openfile->current;
	    bool past_first_line = FALSE;
1284

1285
1286
1287
	    for (i = 0; i < par_len; i++) {
		indent_len = quote_len +
			indent_length(indent_line->data + quote_len);
1288

1289
1290
1291
1292
		if (indent_len != strlen(indent_string)) {
		    indent_string = mallocstrncpy(indent_string,
			indent_line->data, indent_len + 1);
		    indent_string[indent_len] = '\0';
1293

1294
1295
1296
		    if (past_first_line)
			break;
		}
1297

1298
1299
		if (indent_line == openfile->current)
		    past_first_line = TRUE;
1300

1301
1302
1303
		indent_line = indent_line->next;
	    }
	}
1304

1305
1306
1307
1308
1309
1310
1311
	/* Now tack all the lines of the paragraph together, skipping
	 * the quoting and indentation on all lines after the first. */
	for (i = 0; i < par_len - 1; i++) {
	    filestruct *next_line = openfile->current->next;
	    size_t line_len = strlen(openfile->current->data);
	    size_t next_line_len =
		strlen(openfile->current->next->data);
1312

1313
1314
1315
	    indent_len = quote_len +
		indent_length(openfile->current->next->data +
		quote_len);
1316

1317
1318
	    next_line_len -= indent_len;
	    openfile->totsize -= indent_len;
1319

1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
	    /* We're just about to tack the next line onto this one.  If
	     * this line isn't empty, make sure it ends in a space. */
	    if (line_len > 0 &&
		openfile->current->data[line_len - 1] != ' ') {
		line_len++;
		openfile->current->data =
			charealloc(openfile->current->data,
			line_len + 1);
		openfile->current->data[line_len - 1] = ' ';
		openfile->current->data[line_len] = '\0';
		openfile->totsize++;
	    }
1332

1333
1334
1335
1336
1337
	    openfile->current->data =
		charealloc(openfile->current->data, line_len +
		next_line_len + 1);
	    strcat(openfile->current->data, next_line->data +
		indent_len);
1338

1339
	    /* Don't destroy edittop or filebot! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1340
	    if (next_line == openfile->edittop)
1341
		openfile->edittop = openfile->current;
1342
1343
	    if (next_line == openfile->filebot)
		openfile->filebot = openfile->current;
1344

1345
#ifndef NANO_TINY
1346
1347
1348
1349
1350
1351
1352
1353
	    /* Adjust the mark coordinates to compensate for the change
	     * in the next line. */
	    if (openfile->mark_set && openfile->mark_begin ==
		next_line) {
		openfile->mark_begin = openfile->current;
		openfile->mark_begin_x += line_len - indent_len;
	    }
#endif
1354

1355
1356
	    unlink_node(next_line);
	    delete_node(next_line);
1357

1358
1359
1360
	    /* If we've removed the next line, we need to go through
	     * this line again. */
	    i--;
1361

1362
1363
1364
	    par_len--;
	    openfile->totsize--;
	}
1365

1366
1367
1368
1369
1370
	/* Call justify_format() on the paragraph, which will remove
	 * excess spaces from it and change all blank characters to
	 * spaces. */
	justify_format(openfile->current, quote_len +
		indent_length(openfile->current->data + quote_len));
1371

1372
1373
	while (par_len > 0 && strlenpt(openfile->current->data) >
		fill) {
1374
	    size_t line_len = strlen(openfile->current->data);
1375

1376
	    indent_len = strlen(indent_string);
1377

1378
1379
	    /* If this line is too long, try to wrap it to the next line
	     * to make it short enough. */
1380
	    break_pos = break_line(openfile->current->data + indent_len,
1381
1382
1383
1384
1385
		fill - strnlenpt(openfile->current->data, indent_len)
#ifndef DISABLE_HELP
		, FALSE
#endif
		);
1386

1387
1388
1389
	    /* We can't break the line, or don't need to, so get out. */
	    if (break_pos == -1 || break_pos + indent_len == line_len)
		break;
1390

1391
1392
1393
	    /* Move forward to the character after the indentation and
	     * just after the space. */
	    break_pos += indent_len + 1;
1394

1395
	    assert(break_pos <= line_len);
1396

1397
1398
1399
1400
1401
1402
	    /* Make a new line, and copy the text after where we're
	     * going to break this line to the beginning of the new
	     * line. */
	    splice_node(openfile->current,
		make_new_node(openfile->current),
		openfile->current->next);
1403

1404
1405
1406
1407
	    /* If this paragraph is non-quoted, and autoindent isn't
	     * turned on, set the indentation length to zero so that the
	     * indentation is treated as part of the line. */
	    if (quote_len == 0
1408
#ifndef NANO_TINY
1409
		&& !ISSET(AUTOINDENT)
1410
#endif
1411
1412
		)
		indent_len = 0;
1413

1414
1415
1416
1417
1418
1419
1420
1421
	    /* Copy the text after where we're going to break the
	     * current line to the next line. */
	    openfile->current->next->data = charalloc(indent_len + 1 +
		line_len - break_pos);
	    strncpy(openfile->current->next->data, indent_string,
		indent_len);
	    strcpy(openfile->current->next->data + indent_len,
		openfile->current->data + break_pos);
1422

1423
1424
	    par_len++;
	    openfile->totsize += indent_len + 1;
1425

1426
#ifndef NANO_TINY
1427
1428
1429
1430
1431
1432
1433
	    /* Adjust the mark coordinates to compensate for the change
	     * in the current line. */
	    if (openfile->mark_set && openfile->mark_begin ==
		openfile->current && openfile->mark_begin_x >
		break_pos) {
		openfile->mark_begin = openfile->current->next;
		openfile->mark_begin_x -= break_pos - indent_len;
1434
	    }
1435
#endif
1436

1437
1438
	    /* Break the current line. */
	    null_at(&openfile->current->data, break_pos);
1439

1440
	    /* If the current line is the last line of the file, move
1441
	     * the last line of the file down to the next line. */
1442
1443
1444
	    if (openfile->filebot == openfile->current)
		openfile->filebot = openfile->filebot->next;

1445
1446
	    /* Go to the next line. */
	    par_len--;
1447
1448
	    openfile->current_y++;
	    openfile->current = openfile->current->next;
1449
	}
1450

1451
1452
1453
	/* We're done breaking lines, so we don't need indent_string
	 * anymore. */
	free(indent_string);
1454

1455
1456
	/* Go to the next line, if possible.  If there is no next line,
	 * move to the end of the current line. */
1457
1458
1459
1460
1461
	if (openfile->current != openfile->filebot) {
	    openfile->current_y++;
	    openfile->current = openfile->current->next;
	} else
	    openfile->current_x = strlen(openfile->current->data);
1462

1463
1464
1465
1466
	/* Renumber the lines of the now-justified current paragraph,
	 * since both find_paragraph() and edit_refresh() need the line
	 * numbers to be right. */
	renumber(curr_first_par_line);
1467
1468
1469
1470
1471

	/* We've just finished justifying the paragraph.  If we're not
	 * justifying the entire file, break out of the loop.
	 * Otherwise, continue the loop so that we justify all the
	 * paragraphs in the file. */
1472
1473
	if (!full_justify)
	    break;
1474
1475
    }

1476
    /* We are now done justifying the paragraph or the file, so clean
1477
     * up.  current_y and totsize have been maintained above.  If we
1478
1479
1480
     * actually justified something, set last_par_line to the new end of
     * the paragraph. */
    if (first_par_line != NULL)
1481
	last_par_line = openfile->current;
1482

1483
    edit_refresh();
1484

1485
    statusbar(_("Can now UnJustify!"));
1486

1487
1488
1489
1490
    /* If constant cursor position display is on, make sure the current
     * cursor position will be properly displayed on the statusbar. */
    if (ISSET(CONST_UPDATE))
	do_cursorpos(TRUE);
1491

1492
1493
1494
    /* Display the shortcut list with UnJustify. */
    shortcut_init(TRUE);
    display_main_list();
1495

1496
1497
1498
1499
    /* Now get a keystroke and see if it's unjustify.  If not, put back
     * the keystroke and return. */
    kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
	&finished, FALSE);
1500

1501
1502
1503
1504
1505
1506
    if (!meta_key && !func_key && s_or_t &&
	kbinput == NANO_UNJUSTIFY_KEY) {
	/* Splice the justify buffer back into the file, but only if we
	 * actually justified something. */
	if (first_par_line != NULL) {
	    filestruct *top_save;
1507

1508
1509
1510
	    /* Partition the filestruct so that it contains only the
	     * text of the justified paragraph. */
	    filepart = partition_filestruct(first_par_line, 0,
1511
1512
		last_par_line, filebot_inpar ?
		strlen(last_par_line->data) : 0);
1513

1514
	    /* Remove the text of the justified paragraph, and
1515
	     * replace it with the text in the justify buffer. */
1516
1517
1518
	    free_filestruct(openfile->fileage);
	    openfile->fileage = jusbuffer;
	    openfile->filebot = jusbottom;
1519

1520
	    top_save = openfile->fileage;
1521

1522
1523
1524
1525
	    /* Unpartition the filestruct so that it contains all the
	     * text again.  Note that the justified paragraph has been
	     * replaced with the unjustified paragraph. */
	    unpartition_filestruct(&filepart);
1526

1527
1528
1529
	     /* Renumber starting with the beginning line of the old
	      * partition. */
	    renumber(top_save);
1530

1531
1532
1533
1534
1535
	    /* Restore the justify we just did (ungrateful user!). */
	    openfile->edittop = edittop_save;
	    openfile->current = current_save;
	    openfile->current_x = current_x_save;
	    openfile->placewewant = pww_save;
1536
	    openfile->totsize = totsize_save;
1537
#ifndef NANO_TINY
1538
1539
1540
1541
1542
1543
	    if (openfile->mark_set) {
		openfile->mark_begin = mark_begin_save;
		openfile->mark_begin_x = mark_begin_x_save;
	    }
#endif
	    openfile->modified = modified_save;
1544

1545
1546
	    /* Clear the justify buffer. */
	    jusbuffer = NULL;
1547

1548
1549
1550
1551
1552
1553
	    if (!openfile->modified)
		titlebar(NULL);
	    edit_refresh();
	}
    } else {
	unget_kbinput(kbinput, meta_key, func_key);
1554

1555
1556
1557
1558
	/* Blow away the text in the justify buffer. */
	free_filestruct(jusbuffer);
	jusbuffer = NULL;
    }
1559

1560
1561
1562
1563
1564
    blank_statusbar();

    /* Display the shortcut list with UnCut. */
    shortcut_init(FALSE);
    display_main_list();
1565
1566
}

1567
/* Justify the current paragraph. */
1568
void do_justify_void(void)
1569
{
1570
1571
    do_justify(FALSE);
}
1572

1573
/* Justify the entire file. */
1574
1575
1576
void do_full_justify(void)
{
    do_justify(TRUE);
1577
}
1578
#endif /* !DISABLE_JUSTIFY */
1579

1580
1581
1582
1583
#ifndef DISABLE_SPELLER
/* A word is misspelled in the file.  Let the user replace it.  We
 * return FALSE if the user cancels. */
bool do_int_spell_fix(const char *word)
1584
{
1585
1586
1587
1588
1589
1590
1591
1592
1593
    char *save_search, *save_replace;
    size_t match_len, current_x_save = openfile->current_x;
    size_t pww_save = openfile->placewewant;
    filestruct *edittop_save = openfile->edittop;
    filestruct *current_save = openfile->current;
	/* Save where we are. */
    bool canceled = FALSE;
	/* The return value. */
    bool case_sens_set = ISSET(CASE_SENSITIVE);
1594
#ifndef NANO_TINY
1595
1596
1597
1598
1599
    bool backwards_search_set = ISSET(BACKWARDS_SEARCH);
#endif
#ifdef HAVE_REGEX_H
    bool regexp_set = ISSET(USE_REGEXP);
#endif
1600
#ifndef NANO_TINY
1601
    bool old_mark_set = openfile->mark_set;
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
    bool right_side_up = FALSE;
	/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
	 * FALSE if (current, current_x) is. */
    filestruct *top, *bot;
    size_t top_x, bot_x;
#endif

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

1614
#ifndef NANO_TINY
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
    /* Make sure spell-check goes forward only. */
    UNSET(BACKWARDS_SEARCH);
#endif
#ifdef HAVE_REGEX_H
    /* Make sure spell-check doesn't use regular expressions. */
    UNSET(USE_REGEXP);
#endif

    /* Save the current search/replace strings. */
    search_init_globals();
    save_search = last_search;
    save_replace = last_replace;

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

1632
#ifndef NANO_TINY
1633
    if (old_mark_set) {
1634
	/* If the mark is on, partition the filestruct so that it
1635
1636
1637
1638
	 * contains only the marked text; if the NO_NEWLINES flag isn't
	 * set, keep track of whether the text will have a magicline
	 * added when we're done correcting misspelled words; and
	 * turn the mark off. */
1639
1640
1641
	mark_order((const filestruct **)&top, &top_x,
	    (const filestruct **)&bot, &bot_x, &right_side_up);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
1642
1643
	if (!ISSET(NO_NEWLINES))
	    added_magicline = (openfile->filebot->data[0] != '\0');
1644
	openfile->mark_set = FALSE;
1645
1646
1647
    }
#endif

1648
1649
1650
1651
1652
    /* Start from the top of the file. */
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;
    openfile->current_x = (size_t)-1;
    openfile->placewewant = 0;
1653

1654
    /* Find the first whole occurrence of word. */
1655
    findnextstr_wrap_reset();
1656
    while (findnextstr(TRUE, FALSE, openfile->fileage, 0, word,
1657
1658
1659
1660
1661
1662
1663
	&match_len)) {
	if (is_whole_word(openfile->current_x, openfile->current->data,
		word)) {
	    size_t xpt = xplustabs();
	    char *exp_word = display_string(openfile->current->data,
		xpt, strnlenpt(openfile->current->data,
		openfile->current_x + match_len) - xpt, FALSE);
1664

1665
	    edit_refresh();
1666

1667
1668
1669
	    do_replace_highlight(TRUE, exp_word);

	    /* Allow all instances of the word to be corrected. */
1670
	    canceled = (do_prompt(FALSE, spell_list, word,
1671
#ifndef NANO_TINY
1672
			NULL,
1673
#endif
1674
			 _("Edit a replacement")) == -1);
1675

1676
	    do_replace_highlight(FALSE, exp_word);
1677

1678
	    free(exp_word);
1679

1680
1681
	    if (!canceled && strcmp(word, answer) != 0) {
		openfile->current_x--;
1682
1683
		do_replace_loop(TRUE, &canceled, openfile->current,
			&openfile->current_x, word);
1684
	    }
1685

1686
1687
	    break;
	}
1688
1689
    }

1690
#ifndef NANO_TINY
1691
    if (old_mark_set) {
1692
1693
1694
	/* If the mark was on, the NO_NEWLINES flag isn't set, and we
	 * added a magicline, remove it now. */
	if (!ISSET(NO_NEWLINES) && added_magicline)
1695
	    remove_magicline();
1696

1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
	/* Put the beginning and the end of the mark at the beginning
	 * and the end of the spell-checked text. */
	if (openfile->fileage == openfile->filebot)
	    bot_x += top_x;
	if (right_side_up) {
	    openfile->mark_begin_x = top_x;
	    current_x_save = bot_x;
	} else {
	    current_x_save = top_x;
	    openfile->mark_begin_x = bot_x;
	}
1708

1709
1710
1711
1712
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
	unpartition_filestruct(&filepart);
	openfile->mark_set = TRUE;
1713
    }
1714
#endif
1715

1716
1717
1718
1719
1720
1721
1722
1723
    /* Restore the search/replace strings. */
    free(last_search);
    last_search = save_search;
    free(last_replace);
    last_replace = save_replace;

    /* Restore where we were. */
    openfile->edittop = edittop_save;
1724
    openfile->current = current_save;
1725
1726
    openfile->current_x = current_x_save;
    openfile->placewewant = pww_save;
1727

1728
1729
1730
    /* Restore case sensitivity setting. */
    if (!case_sens_set)
	UNSET(CASE_SENSITIVE);
1731

1732
#ifndef NANO_TINY
1733
1734
1735
1736
1737
1738
1739
1740
1741
    /* Restore search/replace direction. */
    if (backwards_search_set)
	SET(BACKWARDS_SEARCH);
#endif
#ifdef HAVE_REGEX_H
    /* Restore regular expression usage setting. */
    if (regexp_set)
	SET(USE_REGEXP);
#endif
1742

1743
    return !canceled;
1744
1745
}

1746
1747
1748
/* Internal (integrated) spell checking using the spell program,
 * filtered through the sort and uniq programs.  Return NULL for normal
 * termination, and the error string otherwise. */
1749
const char *do_int_speller(const char *tempfile_name)
1750
{
1751
1752
1753
1754
1755
    char *read_buff, *read_buff_ptr, *read_buff_word;
    size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
    int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
    pid_t pid_spell, pid_sort, pid_uniq;
    int spell_status, sort_status, uniq_status;
1756

1757
1758
1759
1760
    /* Create all three pipes up front. */
    if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
	pipe(uniq_fd) == -1)
	return _("Could not create pipe");
1761

1762
    statusbar(_("Creating misspelled word list, please wait..."));
1763

1764
1765
1766
1767
    /* A new process to run spell in. */
    if ((pid_spell = fork()) == 0) {
	/* Child continues (i.e, future spell process). */
	close(spell_fd[0]);
1768

1769
1770
1771
	/* Replace the standard input with the temp file. */
	if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
	    goto close_pipes_and_exit;
1772

1773
1774
	if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;
1775

1776
	close(tempfile_fd);
1777

1778
1779
1780
	/* Send spell's standard output to the pipe. */
	if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1781

1782
1783
	close(spell_fd[1]);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1784
	/* Start the spell program; we are using $PATH. */
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
	execlp("spell", "spell", NULL);

	/* This should not be reached if spell is found. */
	exit(1);
    }

    /* Parent continues here. */
    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 input with the standard output of the old pipe. */
	if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;

	close(spell_fd[0]);

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

	close(sort_fd[1]);

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

	/* This should not be reached if sort is found. */
	exit(1);
    }
1816

1817
1818
    close(spell_fd[0]);
    close(sort_fd[1]);
1819

1820
1821
1822
1823
1824
1825
    /* A new process to run uniq in. */
    if ((pid_uniq = fork()) == 0) {
	/* Child continues (i.e, future uniq process).  Replace the
	 * standard input with the standard output of the old pipe. */
	if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
	    goto close_pipes_and_exit;
1826

1827
	close(sort_fd[0]);
1828

1829
1830
1831
	/* Send uniq's standard output to the new pipe. */
	if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
	    goto close_pipes_and_exit;
1832

1833
	close(uniq_fd[1]);
1834

1835
1836
	/* Start the uniq program; we are using PATH. */
	execlp("uniq", "uniq", NULL);
1837

1838
1839
1840
	/* This should not be reached if uniq is found. */
	exit(1);
    }
1841

1842
1843
    close(sort_fd[0]);
    close(uniq_fd[1]);
1844

1845
1846
1847
1848
1849
    /* The child process was not forked successfully. */
    if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
	close(uniq_fd[0]);
	return _("Could not fork");
    }
1850

1851
1852
1853
1854
1855
    /* Get the system pipe buffer size. */
    if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
	close(uniq_fd[0]);
	return _("Could not get size of pipe buffer");
    }
1856

1857
1858
1859
1860
    /* 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);
1861

1862
1863
1864
1865
1866
1867
1868
1869
    while ((bytesread = read(uniq_fd[0], read_buff_ptr,
	pipe_buff_size)) > 0) {
	read_buff_read += bytesread;
	read_buff_size += pipe_buff_size;
	read_buff = read_buff_ptr = charealloc(read_buff,
		read_buff_size);
	read_buff_ptr += read_buff_read;
    }
1870

1871
1872
    *read_buff_ptr = '\0';
    close(uniq_fd[0]);
1873

1874
1875
    /* Process the spelling errors. */
    read_buff_word = read_buff_ptr = read_buff;
1876

1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
    while (*read_buff_ptr != '\0') {
	if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
	    *read_buff_ptr = '\0';
	    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;
1887
	}
1888
1889
	read_buff_ptr++;
    }
1890

1891
1892
1893
    /* Special case: the last word doesn't end with '\r' or '\n'. */
    if (read_buff_word != read_buff_ptr)
	do_int_spell_fix(read_buff_word);
1894

1895
    free(read_buff);
1896
    search_replace_abort();
1897
    edit_refresh();
1898

1899
1900
1901
1902
    /* Process the end of the spell process. */
    waitpid(pid_spell, &spell_status, 0);
    waitpid(pid_sort, &sort_status, 0);
    waitpid(pid_uniq, &uniq_status, 0);
1903

1904
1905
    if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
	return _("Error invoking \"spell\"");
1906

1907
1908
    if (WIFEXITED(sort_status)  == 0 || WEXITSTATUS(sort_status))
	return _("Error invoking \"sort -f\"");
1909

1910
1911
    if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
	return _("Error invoking \"uniq\"");
1912

1913
1914
    /* Otherwise... */
    return NULL;
1915

1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
  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);
}
1927

1928
1929
/* External (alternate) spell checking.  Return NULL for normal
 * termination, and the error string otherwise. */
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
const char *do_alt_speller(char *tempfile_name)
{
    int alt_spell_status;
    size_t current_x_save = openfile->current_x;
    size_t pww_save = openfile->placewewant;
    ssize_t current_y_save = openfile->current_y;
    ssize_t lineno_save = openfile->current->lineno;
    pid_t pid_spell;
    char *ptr;
    static int arglen = 3;
    static char **spellargs = NULL;
1941
#ifndef NANO_TINY
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
    bool old_mark_set = openfile->mark_set;
    bool added_magicline = FALSE;
	/* Whether we added a magicline after filebot. */
    bool right_side_up = FALSE;
	/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
	 * FALSE if (current, current_x) is. */
    filestruct *top, *bot;
    size_t top_x, bot_x;
    ssize_t mb_lineno_save = 0;
	/* We're going to close the current file, and open the output of
	 * the alternate spell command.  The line that mark_begin points
	 * to will be freed, so we save the line number and restore it
	 * afterwards. */
    size_t totsize_save = openfile->totsize;
	/* Our saved value of totsize, used when we spell-check a marked
	 * selection. */
1958

1959
1960
1961
1962
1963
1964
    if (old_mark_set) {
	/* If the mark is on, save the number of the line it starts on,
	 * and then turn the mark off. */
	mb_lineno_save = openfile->mark_begin->lineno;
	openfile->mark_set = FALSE;
    }
1965
1966
#endif

1967
    endwin();
1968

1969
1970
1971
    /* Set up an argument list to pass execvp(). */
    if (spellargs == NULL) {
	spellargs = (char **)nmalloc(arglen * sizeof(char *));
1972

1973
1974
1975
1976
1977
1978
1979
1980
	spellargs[0] = strtok(alt_speller, " ");
	while ((ptr = strtok(NULL, " ")) != NULL) {
	    arglen++;
	    spellargs = (char **)nrealloc(spellargs, arglen *
		sizeof(char *));
	    spellargs[arglen - 3] = ptr;
	}
	spellargs[arglen - 1] = NULL;
1981
    }
1982
    spellargs[arglen - 2] = tempfile_name;
1983

1984
1985
    /* Start a new process for the alternate speller. */
    if ((pid_spell = fork()) == 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1986
	/* Start alternate spell program; we are using $PATH. */
1987
1988
1989
1990
	execvp(spellargs[0], spellargs);

	/* Should not be reached, if alternate speller is found!!! */
	exit(1);
1991
1992
    }

1993
1994
1995
    /* If we couldn't fork, get out. */
    if (pid_spell < 0)
	return _("Could not fork");
1996

1997
#ifndef NANO_TINY
1998
    /* Don't handle a pending SIGWINCH until the alternate spell checker
1999
     * is finished and we've loaded the spell-checked file back in. */
2000
2001
2002
2003
    allow_pending_sigwinch(FALSE);
#endif

    /* Wait for the alternate spell checker to finish. */
2004
    wait(&alt_spell_status);
2005

2006
2007
    /* Reenter curses mode. */
    doupdate();
2008

2009
2010
    /* Restore the terminal to its previous state. */
    terminal_init();
2011

2012
2013
    /* Turn the cursor back on for sure. */
    curs_set(1);
2014

2015
2016
2017
2018
    /* The screen might have been resized.  If it has, reinitialize all
     * the windows based on the new screen dimensions. */
    window_init();

2019
2020
2021
2022
    if (!WIFEXITED(alt_spell_status) ||
		WEXITSTATUS(alt_spell_status) != 0) {
	char *altspell_error;
	char *invoke_error = _("Error invoking \"%s\"");
2023

2024
#ifndef NANO_TINY
2025
2026
2027
	/* Turn the mark back on if it was on before. */
	openfile->mark_set = old_mark_set;
#endif
2028

2029
2030
2031
2032
2033
2034
	altspell_error =
		charalloc(strlen(invoke_error) +
		strlen(alt_speller) + 1);
	sprintf(altspell_error, invoke_error, alt_speller);
	return altspell_error;
    }
2035

2036
#ifndef NANO_TINY
2037
    if (old_mark_set) {
2038
2039
2040
2041
2042
	/* If the mark is on, partition the filestruct so that it
	 * contains only the marked text; if the NO_NEWLINES flag isn't
	 * set, keep track of whether the text will have a magicline
	 * added when we're done correcting misspelled words; and
	 * turn the mark off. */
2043
2044
2045
	mark_order((const filestruct **)&top, &top_x,
		(const filestruct **)&bot, &bot_x, &right_side_up);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
2046
2047
	if (!ISSET(NO_NEWLINES))
	    added_magicline = (openfile->filebot->data[0] != '\0');
2048

2049
2050
2051
2052
2053
	/* Get the number of characters in the marked text, and subtract
	 * it from the saved value of totsize. */
	totsize_save -= get_totsize(top, bot);
    }
#endif
2054

2055
2056
2057
    /* Replace the text of the current buffer with the spell-checked
     * text. */
    replace_buffer(tempfile_name);
2058

2059
#ifndef NANO_TINY
2060
2061
    if (old_mark_set) {
	filestruct *top_save = openfile->fileage;
2062

2063
2064
2065
	/* If the mark was on, the NO_NEWLINES flag isn't set, and we
	 * added a magicline, remove it now. */
	if (!ISSET(NO_NEWLINES) && added_magicline)
2066
	    remove_magicline();
2067

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

2080
2081
2082
2083
2084
2085
2086
	/* Unpartition the filestruct so that it contains all the text
	 * again.  Note that we've replaced the marked text originally
	 * in the partition with the spell-checked marked text in the
	 * temp file. */
	unpartition_filestruct(&filepart);

	/* Renumber starting with the beginning line of the old
2087
2088
2089
	 * partition.  Also add the number of characters in the
	 * spell-checked marked text to the saved value of totsize, and
	 * then make that saved value the actual value. */
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
	renumber(top_save);
	totsize_save += openfile->totsize;
	openfile->totsize = totsize_save;

	/* Assign mark_begin to the line where the mark began before. */
	do_gotopos(mb_lineno_save, openfile->mark_begin_x,
		current_y_save, 0);
	openfile->mark_begin = openfile->current;

	/* Assign mark_begin_x to the location in mark_begin where the
	 * mark began before, adjusted for any shortening of the
	 * line. */
	openfile->mark_begin_x = openfile->current_x;

	/* Turn the mark back on. */
	openfile->mark_set = TRUE;
2106
    }
2107
#endif
2108

2109
2110
2111
    /* Go back to the old position, and mark the file as modified. */
    do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
    set_modified();
2112

2113
#ifndef NANO_TINY
2114
2115
2116
2117
    /* Handle a pending SIGWINCH again. */
    allow_pending_sigwinch(TRUE);
#endif

2118
    return NULL;
2119
2120
}

2121
2122
/* Spell check the current file.  If an alternate spell checker is
 * specified, use it.  Otherwise, use the internal spell checker. */
2123
void do_spell(void)
2124
{
2125
2126
2127
2128
    int i;
    FILE *temp_file;
    char *temp = safe_tempfile(&temp_file);
    const char *spell_msg;
2129

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

2135
#ifndef NANO_TINY
2136
    if (openfile->mark_set)
2137
	i = write_marked_file(temp, temp_file, TRUE, OVERWRITE);
2138
2139
    else
#endif
2140
	i = write_file(temp, temp_file, TRUE, OVERWRITE, FALSE);
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165

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

    spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
	do_int_speller(temp);
    unlink(temp);
    free(temp);

    /* If the spell-checker printed any error messages onscreen, make
     * sure that they're cleared off. */
    total_refresh();

    if (spell_msg != NULL) {
	if (errno == 0)
	    /* Don't display an error message of "Success". */
	    statusbar(_("Spell checking failed: %s"), spell_msg);
	else
	    statusbar(_("Spell checking failed: %s: %s"), spell_msg,
		strerror(errno));
    } else
	statusbar(_("Finished checking spelling"));
2166
}
2167
#endif /* !DISABLE_SPELLER */
2168

2169
#ifndef NANO_TINY
2170
2171
/* Our own version of "wc".  Note that its character counts are in
 * multibyte characters instead of single-byte characters. */
2172
void do_wordlinechar_count(void)
2173
{
2174
2175
    size_t words = 0, chars = 0;
    ssize_t lines = 0;
2176
    size_t current_x_save = openfile->current_x;
2177
2178
2179
2180
2181
2182
2183
2184
    size_t pww_save = openfile->placewewant;
    filestruct *current_save = openfile->current;
    bool old_mark_set = openfile->mark_set;
    filestruct *top, *bot;
    size_t top_x, bot_x;

    if (old_mark_set) {
	/* If the mark is on, partition the filestruct so that it
2185
	 * contains only the marked text, and turn the mark off. */
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
	mark_order((const filestruct **)&top, &top_x,
	    (const filestruct **)&bot, &bot_x, NULL);
	filepart = partition_filestruct(top, top_x, bot, bot_x);
	openfile->mark_set = FALSE;
    }

    /* Start at the top of the file. */
    openfile->current = openfile->fileage;
    openfile->current_x = 0;
    openfile->placewewant = 0;

    /* Keep moving to the next word (counting punctuation characters as
2198
2199
2200
     * part of a word, as "wc -w" does), without updating the screen,
     * until we reach the end of the file, incrementing the total word
     * count whenever we're on a word just before moving. */
2201
    while (openfile->current != openfile->filebot ||
2202
	openfile->current->data[openfile->current_x] != '\0') {
2203
2204
2205
2206
	if (do_next_word(TRUE, FALSE))
	    words++;
    }

2207
2208
    /* Get the total line and character counts, as "wc -l"  and "wc -c"
     * do, but get the latter in multibyte characters. */
2209
    if (old_mark_set) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2210
2211
	lines = openfile->filebot->lineno -
		openfile->fileage->lineno + 1;
2212
2213
	chars = get_totsize(openfile->fileage, openfile->filebot);

2214
2215
2216
2217
	/* Unpartition the filestruct so that it contains all the text
	 * again, and turn the mark back on. */
	unpartition_filestruct(&filepart);
	openfile->mark_set = TRUE;
2218
    } else {
2219
	lines = openfile->filebot->lineno;
2220
	chars = openfile->totsize;
2221
2222
2223
2224
2225
2226
2227
    }

    /* Restore where we were. */
    openfile->current = current_save;
    openfile->current_x = current_x_save;
    openfile->placewewant = pww_save;

2228
2229
    /* Display the total word, line, and character counts on the
     * statusbar. */
2230
    statusbar(_("%sWords: %lu  Lines: %ld  Chars: %lu"), old_mark_set ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2231
	_("In Selection:  ") : "", (unsigned long)words, (long)lines,
2232
	(unsigned long)chars);
2233
}
2234
#endif /* !NANO_TINY */
2235

2236
/* Get verbatim input. */
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
void do_verbatim_input(void)
{
    int *kbinput;
    size_t kbinput_len, i;
    char *output;

    statusbar(_("Verbatim Input"));

    /* If constant cursor position display is on, make sure the current
     * cursor position will be properly displayed on the statusbar. */
    if (ISSET(CONST_UPDATE))
	do_cursorpos(TRUE);

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

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

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

    do_output(output, kbinput_len, TRUE);

    free(output);
}