blob: 460225c74901a33c44de5d64f8059d9c2c293569 [file] [log] [blame]
David Lawrence Ramsey691698a2005-07-24 19:57:51 +00001/* $Id$ */
2/**************************************************************************
3 * text.c *
4 * *
5 * Copyright (C) 2005 Chris Allegretta *
6 * This program is free software; you can redistribute it and/or modify *
7 * 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) *
9 * any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
19 * 02110-1301, USA. *
20 * *
21 **************************************************************************/
22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
27#include <signal.h>
28#include <unistd.h>
29#include <string.h>
30#include <fcntl.h>
31#include <sys/wait.h>
32#include <errno.h>
33#include "proto.h"
34
35#ifndef NANO_SMALL
36static pid_t pid; /* The PID of the newly forked process
37 * in execute_command(). It must be
38 * global because the cancel_command()
39 * signal handler needs it. */
40#endif
41#ifndef DISABLE_WRAPPING
42static bool same_line_wrap = FALSE;
43 /* Should we prepend wrapped text to the next line? */
44#endif
45#ifndef DISABLE_JUSTIFY
46static filestruct *jusbottom = NULL;
47 /* Pointer to end of justify buffer. */
48#endif
49
50#ifndef NANO_SMALL
David Lawrence Ramsey7ea09e52005-07-25 02:41:59 +000051void do_mark(void)
52{
53 openfile->mark_set = !openfile->mark_set;
54 if (openfile->mark_set) {
55 statusbar(_("Mark Set"));
56 openfile->mark_begin = openfile->current;
57 openfile->mark_begin_x = openfile->current_x;
58 } else {
59 statusbar(_("Mark UNset"));
60 openfile->mark_begin = NULL;
61 openfile->mark_begin_x = 0;
62 edit_refresh();
63 }
64}
65#endif /* !NANO_SMALL */
66
67void do_delete(void)
68{
69 bool do_refresh = FALSE;
70 /* Do we have to call edit_refresh(), or can we get away with
71 * update_line()? */
72
73 assert(openfile->current != NULL && openfile->current->data != NULL && openfile->current_x <= strlen(openfile->current->data));
74
75 openfile->placewewant = xplustabs();
76
77 if (openfile->current->data[openfile->current_x] != '\0') {
78 int char_buf_len = parse_mbchar(openfile->current->data +
79 openfile->current_x, NULL, NULL, NULL);
80 size_t line_len = strlen(openfile->current->data +
81 openfile->current_x);
82
83 assert(openfile->current_x < strlen(openfile->current->data));
84
85 /* Let's get dangerous. */
86 charmove(&openfile->current->data[openfile->current_x],
87 &openfile->current->data[openfile->current_x +
88 char_buf_len], line_len - char_buf_len + 1);
89
90 null_at(&openfile->current->data, openfile->current_x +
91 line_len - char_buf_len);
92#ifndef NANO_SMALL
93 if (openfile->mark_set && openfile->mark_begin ==
94 openfile->current && openfile->current_x <
95 openfile->mark_begin_x)
96 openfile->mark_begin_x -= char_buf_len;
97#endif
98 openfile->totsize--;
99 } else if (openfile->current != openfile->filebot &&
100 (openfile->current->next != openfile->filebot ||
101 openfile->current->data[0] == '\0')) {
102 /* We can delete the line before filebot only if it is blank: it
103 * becomes the new magicline then. */
104 filestruct *foo = openfile->current->next;
105
106 assert(openfile->current_x == strlen(openfile->current->data));
107
108 /* If we're deleting at the end of a line, we need to call
109 * edit_refresh(). */
110 if (openfile->current->data[openfile->current_x] == '\0')
111 do_refresh = TRUE;
112
113 openfile->current->data = charealloc(openfile->current->data,
114 openfile->current_x + strlen(foo->data) + 1);
115 strcpy(openfile->current->data + openfile->current_x,
116 foo->data);
117#ifndef NANO_SMALL
118 if (openfile->mark_set && openfile->mark_begin ==
119 openfile->current->next) {
120 openfile->mark_begin = openfile->current;
121 openfile->mark_begin_x += openfile->current_x;
122 }
123#endif
124 if (openfile->filebot == foo)
125 openfile->filebot = openfile->current;
126
127 unlink_node(foo);
128 delete_node(foo);
129 renumber(openfile->current);
130 openfile->totlines--;
131 openfile->totsize--;
132#ifndef DISABLE_WRAPPING
133 wrap_reset();
134#endif
135 } else
136 return;
137
138 set_modified();
139
140#ifdef ENABLE_COLOR
141 /* If color syntaxes are available and turned on, we need to call
142 * edit_refresh(). */
143 if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX))
144 do_refresh = TRUE;
145#endif
146
147 if (do_refresh)
148 edit_refresh();
149 else
150 update_line(openfile->current, openfile->current_x);
151}
152
153void do_backspace(void)
154{
155 if (openfile->current != openfile->fileage ||
156 openfile->current_x > 0) {
157 do_left(FALSE);
158 do_delete();
159 }
160}
161
162void do_tab(void)
163{
164#ifndef NANO_SMALL
165 if (ISSET(TABS_TO_SPACES)) {
166 char *output;
167 size_t output_len = 0, new_pww = openfile->placewewant;
168
169 do {
170 new_pww++;
171 output_len++;
172 } while (new_pww % tabsize != 0);
173
174 output = charalloc(output_len + 1);
175
176 charset(output, ' ', output_len);
177 output[output_len] = '\0';
178
179 do_output(output, output_len, TRUE);
180
181 free(output);
182 } else {
183#endif
184 do_output("\t", 1, TRUE);
185#ifndef NANO_SMALL
186 }
187#endif
188}
189
190/* Someone hits Return *gasp!* */
191void do_enter(void)
192{
193 filestruct *newnode = make_new_node(openfile->current);
194 size_t extra = 0;
195
196 assert(openfile->current != NULL && openfile->current->data != NULL);
197
198#ifndef NANO_SMALL
199 /* Do auto-indenting, like the neolithic Turbo Pascal editor. */
200 if (ISSET(AUTOINDENT)) {
201 /* If we are breaking the line in the indentation, the new
202 * indentation should have only current_x characters, and
203 * current_x should not change. */
204 extra = indent_length(openfile->current->data);
205 if (extra > openfile->current_x)
206 extra = openfile->current_x;
207 }
208#endif
209 newnode->data = charalloc(strlen(openfile->current->data +
210 openfile->current_x) + extra + 1);
211 strcpy(&newnode->data[extra], openfile->current->data +
212 openfile->current_x);
213#ifndef NANO_SMALL
214 if (ISSET(AUTOINDENT)) {
215 strncpy(newnode->data, openfile->current->data, extra);
216 openfile->totsize += mbstrlen(newnode->data);
217 }
218#endif
219 null_at(&openfile->current->data, openfile->current_x);
220#ifndef NANO_SMALL
221 if (openfile->mark_set && openfile->current ==
222 openfile->mark_begin && openfile->current_x <
223 openfile->mark_begin_x) {
224 openfile->mark_begin = newnode;
225 openfile->mark_begin_x += extra - openfile->current_x;
226 }
227#endif
228 openfile->current_x = extra;
229
230 if (openfile->current == openfile->filebot)
231 openfile->filebot = newnode;
232 splice_node(openfile->current, newnode,
233 openfile->current->next);
234
235 renumber(openfile->current);
236 openfile->current = newnode;
237
238 edit_refresh();
239
240 openfile->totlines++;
241 openfile->totsize++;
242 set_modified();
243 openfile->placewewant = xplustabs();
244}
245
246#ifndef NANO_SMALL
David Lawrence Ramsey691698a2005-07-24 19:57:51 +0000247void cancel_command(int signal)
248{
249 if (kill(pid, SIGKILL) == -1)
250 nperror("kill");
251}
252
253/* Return TRUE on success. */
254bool execute_command(const char *command)
255{
256 int fd[2];
257 FILE *f;
258 struct sigaction oldaction, newaction;
259 /* Original and temporary handlers for SIGINT. */
260 bool sig_failed = FALSE;
261 /* Did sigaction() fail without changing the signal handlers? */
262
263 /* Make our pipes. */
264 if (pipe(fd) == -1) {
265 statusbar(_("Could not pipe"));
266 return FALSE;
267 }
268
269 /* Fork a child. */
270 if ((pid = fork()) == 0) {
271 close(fd[0]);
272 dup2(fd[1], fileno(stdout));
273 dup2(fd[1], fileno(stderr));
274
275 /* If execl() returns at all, there was an error. */
276 execl("/bin/sh", "sh", "-c", command, NULL);
277 exit(0);
278 }
279
280 /* Else continue as parent. */
281 close(fd[1]);
282
283 if (pid == -1) {
284 close(fd[0]);
285 statusbar(_("Could not fork"));
286 return FALSE;
287 }
288
289 /* Before we start reading the forked command's output, we set
290 * things up so that Ctrl-C will cancel the new process. */
291
292 /* Enable interpretation of the special control keys so that we get
293 * SIGINT when Ctrl-C is pressed. */
294 enable_signals();
295
296 if (sigaction(SIGINT, NULL, &newaction) == -1) {
297 sig_failed = TRUE;
298 nperror("sigaction");
299 } else {
300 newaction.sa_handler = cancel_command;
301 if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
302 sig_failed = TRUE;
303 nperror("sigaction");
304 }
305 }
306
307 /* Note that now oldaction is the previous SIGINT signal handler,
308 * to be restored later. */
309
310 f = fdopen(fd[0], "rb");
311 if (f == NULL)
312 nperror("fdopen");
313
314 read_file(f, "stdin");
315
316 /* If multibuffer mode is on, we could be here in view mode. If so,
317 * don't set the modification flag. */
318 if (!ISSET(VIEW_MODE))
319 set_modified();
320
321 if (wait(NULL) == -1)
322 nperror("wait");
323
324 if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
325 nperror("sigaction");
326
327 /* Disable interpretation of the special control keys so that we can
328 * use Ctrl-C for other things. */
329 disable_signals();
330
331 return TRUE;
332}
333#endif /* !NANO_SMALL */
334
335#ifndef DISABLE_WRAPPING
336void wrap_reset(void)
337{
338 same_line_wrap = FALSE;
339}
340
341/* We wrap the given line. Precondition: we assume the cursor has been
342 * moved forward since the last typed character. Return value: whether
343 * we wrapped. */
344bool do_wrap(filestruct *line)
345{
346 size_t line_len;
347 /* Length of the line we wrap. */
348 ssize_t wrap_loc;
349 /* Index of line->data where we wrap. */
350#ifndef NANO_SMALL
351 const char *indent_string = NULL;
352 /* Indentation to prepend to the new line. */
353 size_t indent_len = 0; /* The length of indent_string. */
354#endif
355 const char *after_break; /* The text after the wrap point. */
356 size_t after_break_len; /* The length of after_break. */
357 bool wrapping = FALSE; /* Do we prepend to the next line? */
358 const char *next_line = NULL;
359 /* The next line, minus indentation. */
360 size_t next_line_len = 0; /* The length of next_line. */
361 char *new_line = NULL; /* The line we create. */
362 size_t new_line_len = 0; /* The eventual length of new_line. */
363
364 /* There are three steps. First, we decide where to wrap. Then, we
365 * create the new wrap line. Finally, we clean up. */
366
367 /* Step 1, finding where to wrap. We are going to add a new line
368 * after a blank character. In this step, we call break_line() to
369 * get the location of the last blank we can break the line at, and
370 * and set wrap_loc to the location of the character after it, so
371 * that the blank is preserved at the end of the line.
372 *
373 * If there is no legal wrap point, or we reach the last character
374 * of the line while trying to find one, we should return without
375 * wrapping. Note that if autoindent is turned on, we don't break
376 * at the end of it! */
377
378 assert(line != NULL && line->data != NULL);
379
380 /* Save the length of the line. */
381 line_len = strlen(line->data);
382
383 /* Find the last blank where we can break the line. */
384 wrap_loc = break_line(line->data, fill, FALSE);
385
386 /* If we couldn't break the line, or we've reached the end of it, we
387 * don't wrap. */
388 if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
389 return FALSE;
390
391 /* Otherwise, move forward to the character just after the blank. */
392 wrap_loc += move_mbright(line->data + wrap_loc, 0);
393
394 /* If we've reached the end of the line, we don't wrap. */
395 if (line->data[wrap_loc] == '\0')
396 return FALSE;
397
398#ifndef NANO_SMALL
399 /* If autoindent is turned on, and we're on the character just after
400 * the indentation, we don't wrap. */
401 if (ISSET(AUTOINDENT)) {
402 /* Get the indentation of this line. */
403 indent_string = line->data;
404 indent_len = indent_length(indent_string);
405
406 if (wrap_loc == indent_len)
407 return FALSE;
408 }
409#endif
410
411 /* Step 2, making the new wrap line. It will consist of indentation
412 * followed by the text after the wrap point, optionally followed by
413 * a space (if the text after the wrap point doesn't end in a blank)
414 * and the text of the next line, if they can fit without
415 * wrapping, the next line exists, and the same_line_wrap flag is
416 * set. */
417
418 /* after_break is the text that will be wrapped to the next line. */
419 after_break = line->data + wrap_loc;
420 after_break_len = line_len - wrap_loc;
421
422 assert(strlen(after_break) == after_break_len);
423
424 /* We prepend the wrapped text to the next line, if the
425 * same_line_wrap flag is set, there is a next line, and prepending
426 * would not make the line too long. */
427 if (same_line_wrap && line->next != NULL) {
428 const char *end = after_break + move_mbleft(after_break,
429 after_break_len);
430
431 /* If after_break doesn't end in a blank, make sure it ends in a
432 * space. */
433 if (!is_blank_mbchar(end)) {
434 line_len++;
435 line->data = charealloc(line->data, line_len + 1);
436 line->data[line_len - 1] = ' ';
437 line->data[line_len] = '\0';
438 after_break = line->data + wrap_loc;
439 after_break_len++;
440 openfile->totsize++;
441 }
442
443 next_line = line->next->data;
444 next_line_len = strlen(next_line);
445
446 if (after_break_len + next_line_len <= fill) {
447 wrapping = TRUE;
448 new_line_len += next_line_len;
449 }
450 }
451
452 /* new_line_len is now the length of the text that will be wrapped
453 * to the next line, plus (if we're prepending to it) the length of
454 * the text of the next line. */
455 new_line_len += after_break_len;
456
457#ifndef NANO_SMALL
458 if (ISSET(AUTOINDENT)) {
459 if (wrapping) {
460 /* If we're wrapping, the indentation will come from the
461 * next line. */
462 indent_string = next_line;
463 indent_len = indent_length(indent_string);
464 next_line += indent_len;
465 } else {
466 /* Otherwise, it will come from this line, in which case
467 * we should increase new_line_len to make room for it. */
468 new_line_len += indent_len;
469 openfile->totsize += mbstrnlen(indent_string, indent_len);
470 }
471 }
472#endif
473
474 /* Now we allocate the new line and copy the text into it. */
475 new_line = charalloc(new_line_len + 1);
476 new_line[0] = '\0';
477
478#ifndef NANO_SMALL
479 if (ISSET(AUTOINDENT)) {
480 /* Copy the indentation. */
481 strncpy(new_line, indent_string, indent_len);
482 new_line[indent_len] = '\0';
483 new_line_len += indent_len;
484 }
485#endif
486
487 /* Copy all the text after the wrap point of the current line. */
488 strcat(new_line, after_break);
489
490 /* Break the current line at the wrap point. */
491 null_at(&line->data, wrap_loc);
492
493 if (wrapping) {
494 /* If we're wrapping, copy the text from the next line, minus
495 * the indentation that we already copied above. */
496 strcat(new_line, next_line);
497
498 free(line->next->data);
499 line->next->data = new_line;
500 } else {
501 /* Otherwise, make a new line and copy the text after where we
502 * broke this line to the beginning of the new line. */
503 splice_node(openfile->current, make_new_node(openfile->current),
504 openfile->current->next);
505
506 openfile->current->next->data = new_line;
507
508 openfile->totlines++;
509 openfile->totsize++;
510 }
511
512 /* Step 3, clean up. Reposition the cursor and mark, and do some
513 * other sundry things. */
514
515 /* Set the same_line_wrap flag, so that later wraps of this line
516 * will be prepended to the next line. */
517 same_line_wrap = TRUE;
518
519 /* Each line knows its line number. We recalculate these if we
520 * inserted a new line. */
521 if (!wrapping)
522 renumber(line);
523
524 /* If the cursor was after the break point, we must move it. We
525 * also clear the same_line_wrap flag in this case. */
526 if (openfile->current_x > wrap_loc) {
527 same_line_wrap = FALSE;
528 openfile->current = openfile->current->next;
529 openfile->current_x -= wrap_loc
530#ifndef NANO_SMALL
531 - indent_len
532#endif
533 ;
534 openfile->placewewant = xplustabs();
535 }
536
537#ifndef NANO_SMALL
538 /* If the mark was on this line after the wrap point, we move it
539 * down. If it was on the next line and we wrapped onto that line,
540 * we move it right. */
541 if (openfile->mark_set) {
542 if (openfile->mark_begin == line && openfile->mark_begin_x >
543 wrap_loc) {
544 openfile->mark_begin = line->next;
545 openfile->mark_begin_x -= wrap_loc - indent_len + 1;
546 } else if (wrapping && openfile->mark_begin == line->next)
547 openfile->mark_begin_x += after_break_len;
548 }
549#endif
550
551 return TRUE;
552}
553#endif /* !DISABLE_WRAPPING */
554
David Lawrence Ramsey691698a2005-07-24 19:57:51 +0000555#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING)
556/* We are trying to break a chunk off line. We find the last blank such
557 * that the display length to there is at most goal + 1. If there is no
558 * such blank, then we find the first blank. We then take the last
559 * blank in that group of blanks. The terminating '\0' counts as a
560 * blank, as does a '\n' if newline is TRUE. */
561ssize_t break_line(const char *line, ssize_t goal, bool newline)
562{
563 ssize_t blank_loc = -1;
564 /* Current tentative return value. Index of the last blank we
565 * found with short enough display width. */
566 ssize_t cur_loc = 0;
567 /* Current index in line. */
568 int line_len;
569
570 assert(line != NULL);
571
572 while (*line != '\0' && goal >= 0) {
573 size_t pos = 0;
574
575 line_len = parse_mbchar(line, NULL, NULL, &pos);
576
577 if (is_blank_mbchar(line) || (newline && *line == '\n')) {
578 blank_loc = cur_loc;
579
580 if (newline && *line == '\n')
581 break;
582 }
583
584 goal -= pos;
585 line += line_len;
586 cur_loc += line_len;
587 }
588
589 if (goal >= 0)
590 /* In fact, the whole line displays shorter than goal. */
591 return cur_loc;
592
593 if (blank_loc == -1) {
594 /* No blank was found that was short enough. */
595 bool found_blank = FALSE;
596
597 while (*line != '\0') {
598 line_len = parse_mbchar(line, NULL, NULL, NULL);
599
600 if (is_blank_mbchar(line) || (newline && *line == '\n')) {
601 if (!found_blank)
602 found_blank = TRUE;
603 } else if (found_blank)
604 return cur_loc - line_len;
605
606 line += line_len;
607 cur_loc += line_len;
608 }
609
610 return -1;
611 }
612
613 /* Move to the last blank after blank_loc, if there is one. */
614 line -= cur_loc;
615 line += blank_loc;
616 line_len = parse_mbchar(line, NULL, NULL, NULL);
617 line += line_len;
618
619 while (*line != '\0' && (is_blank_mbchar(line) ||
620 (newline && *line == '\n'))) {
621 line_len = parse_mbchar(line, NULL, NULL, NULL);
622
623 line += line_len;
624 blank_loc += line_len;
625 }
626
627 return blank_loc;
628}
629#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */
630
631#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY)
632/* The "indentation" of a line is the whitespace between the quote part
633 * and the non-whitespace of the line. */
634size_t indent_length(const char *line)
635{
636 size_t len = 0;
637 char *blank_mb;
638 int blank_mb_len;
639
640 assert(line != NULL);
641
642 blank_mb = charalloc(mb_cur_max());
643
644 while (*line != '\0') {
645 blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL);
646
647 if (!is_blank_mbchar(blank_mb))
648 break;
649
650 line += blank_mb_len;
651 len += blank_mb_len;
652 }
653
654 free(blank_mb);
655
656 return len;
657}
658#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */
659
660#ifndef DISABLE_JUSTIFY
661/* justify_format() replaces blanks with spaces and multiple spaces by 1
662 * (except it maintains up to 2 after a character in punct optionally
663 * followed by a character in brackets, and removes all from the end).
664 *
665 * justify_format() might make paragraph->data shorter, and change the
666 * actual pointer with null_at().
667 *
668 * justify_format() will not look at the first skip characters of
669 * paragraph. skip should be at most strlen(paragraph->data). The
670 * character at paragraph[skip + 1] must not be blank. */
671void justify_format(filestruct *paragraph, size_t skip)
672{
673 char *end, *new_end, *new_paragraph_data;
674 size_t shift = 0;
675#ifndef NANO_SMALL
676 size_t mark_shift = 0;
677#endif
678
679 /* These four asserts are assumptions about the input data. */
680 assert(paragraph != NULL);
681 assert(paragraph->data != NULL);
682 assert(skip < strlen(paragraph->data));
683 assert(!is_blank_mbchar(paragraph->data + skip));
684
685 end = paragraph->data + skip;
686 new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
687 strncpy(new_paragraph_data, paragraph->data, skip);
688 new_end = new_paragraph_data + skip;
689
690 while (*end != '\0') {
691 int end_len;
692
693 /* If this character is blank, make sure that it's a space with
694 * no blanks after it. */
695 if (is_blank_mbchar(end)) {
696 end_len = parse_mbchar(end, NULL, NULL, NULL);
697
698 *new_end = ' ';
699 new_end++;
700 end += end_len;
701
702 while (*end != '\0' && is_blank_mbchar(end)) {
703 end_len = parse_mbchar(end, NULL, NULL, NULL);
704
705 end += end_len;
706 shift += end_len;
707
708#ifndef NANO_SMALL
709 /* Keep track of the change in the current line. */
710 if (openfile->mark_set && openfile->mark_begin ==
711 paragraph && openfile->mark_begin_x >= end -
712 paragraph->data)
713 mark_shift += end_len;
714#endif
715 }
716 /* If this character is punctuation optionally followed by a
717 * bracket and then followed by blanks, make sure there are no
718 * more than two blanks after it, and make sure that the blanks
719 * are spaces. */
720 } else if (mbstrchr(punct, end) != NULL) {
721 end_len = parse_mbchar(end, NULL, NULL, NULL);
722
723 while (end_len > 0) {
724 *new_end = *end;
725 new_end++;
726 end++;
727 end_len--;
728 }
729
730 if (*end != '\0' && mbstrchr(brackets, end) != NULL) {
731 end_len = parse_mbchar(end, NULL, NULL, NULL);
732
733 while (end_len > 0) {
734 *new_end = *end;
735 new_end++;
736 end++;
737 end_len--;
738 }
739 }
740
741 if (*end != '\0' && is_blank_mbchar(end)) {
742 end_len = parse_mbchar(end, NULL, NULL, NULL);
743
744 *new_end = ' ';
745 new_end++;
746 end += end_len;
747 }
748
749 if (*end != '\0' && is_blank_mbchar(end)) {
750 end_len = parse_mbchar(end, NULL, NULL, NULL);
751
752 *new_end = ' ';
753 new_end++;
754 end += end_len;
755 }
756
757 while (*end != '\0' && is_blank_mbchar(end)) {
758 end_len = parse_mbchar(end, NULL, NULL, NULL);
759
760 end += end_len;
761 shift += end_len;
762
763#ifndef NANO_SMALL
764 /* Keep track of the change in the current line. */
765 if (openfile->mark_set && openfile->mark_begin ==
766 paragraph && openfile->mark_begin_x >= end -
767 paragraph->data)
768 mark_shift += end_len;
769#endif
770 }
771 /* If this character is neither blank nor punctuation, leave it
772 * alone. */
773 } else {
774 end_len = parse_mbchar(end, NULL, NULL, NULL);
775
776 while (end_len > 0) {
777 *new_end = *end;
778 new_end++;
779 end++;
780 end_len--;
781 }
782 }
783 }
784
785 assert(*end == '\0');
786
787 *new_end = *end;
788
789 /* Make sure that there are no spaces at the end of the line. */
790 while (new_end > new_paragraph_data + skip &&
791 *(new_end - 1) == ' ') {
792 new_end--;
793 shift++;
794 }
795
796 if (shift > 0) {
797 openfile->totsize -= shift;
798 null_at(&new_paragraph_data, new_end - new_paragraph_data);
799 free(paragraph->data);
800 paragraph->data = new_paragraph_data;
801
802#ifndef NANO_SMALL
803 /* Adjust the mark coordinates to compensate for the change in
804 * the current line. */
805 if (openfile->mark_set && openfile->mark_begin == paragraph) {
806 openfile->mark_begin_x -= mark_shift;
807 if (openfile->mark_begin_x > new_end - new_paragraph_data)
808 openfile->mark_begin_x = new_end - new_paragraph_data;
809 }
810#endif
811 } else
812 free(new_paragraph_data);
813}
814
815/* The "quote part" of a line is the largest initial substring matching
816 * the quote string. This function returns the length of the quote part
817 * of the given line.
818 *
819 * Note that if !HAVE_REGEX_H then we match concatenated copies of
820 * quotestr. */
821size_t quote_length(const char *line)
822{
823#ifdef HAVE_REGEX_H
824 regmatch_t matches;
825 int rc = regexec(&quotereg, line, 1, &matches, 0);
826
827 if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
828 return 0;
829 /* matches.rm_so should be 0, since the quote string should start
830 * with the caret ^. */
831 return matches.rm_eo;
832#else /* !HAVE_REGEX_H */
833 size_t qdepth = 0;
834
835 /* Compute quote depth level. */
836 while (strncmp(line + qdepth, quotestr, quotelen) == 0)
837 qdepth += quotelen;
838 return qdepth;
839#endif /* !HAVE_REGEX_H */
840}
841
842/* a_line and b_line are lines of text. The quotation part of a_line is
843 * the first a_quote characters. Check that the quotation part of
844 * b_line is the same. */
845bool quotes_match(const char *a_line, size_t a_quote, const char
846 *b_line)
847{
848 /* Here is the assumption about a_quote. */
849 assert(a_quote == quote_length(a_line));
850
851 return (a_quote == quote_length(b_line) &&
852 strncmp(a_line, b_line, a_quote) == 0);
853}
854
855/* We assume a_line and b_line have no quote part. Then, we return
856 * whether b_line could follow a_line in a paragraph. */
857bool indents_match(const char *a_line, size_t a_indent, const char
858 *b_line, size_t b_indent)
859{
860 assert(a_indent == indent_length(a_line));
861 assert(b_indent == indent_length(b_line));
862
863 return (b_indent <= a_indent &&
864 strncmp(a_line, b_line, b_indent) == 0);
865}
866
867/* Is foo the beginning of a paragraph?
868 *
869 * A line of text consists of a "quote part", followed by an
870 * "indentation part", followed by text. The functions quote_length()
871 * and indent_length() calculate these parts.
872 *
873 * A line is "part of a paragraph" if it has a part not in the quote
874 * part or the indentation.
875 *
876 * A line is "the beginning of a paragraph" if it is part of a
877 * paragraph and
878 * 1) it is the top line of the file, or
879 * 2) the line above it is not part of a paragraph, or
880 * 3) the line above it does not have precisely the same quote
881 * part, or
882 * 4) the indentation of this line is not an initial substring of
883 * the indentation of the previous line, or
884 * 5) this line has no quote part and some indentation, and
885 * autoindent isn't turned on.
886 * The reason for number 5) is that if autoindent isn't turned on,
887 * then an indented line is expected to start a paragraph, as in
888 * books. Thus, nano can justify an indented paragraph only if
889 * autoindent is turned on. */
890bool begpar(const filestruct *const foo)
891{
892 size_t quote_len;
893 size_t indent_len;
894 size_t temp_id_len;
895
896 /* Case 1). */
897 if (foo->prev == NULL)
898 return TRUE;
899
900 quote_len = quote_length(foo->data);
901 indent_len = indent_length(foo->data + quote_len);
902
903 /* Not part of a paragraph. */
904 if (foo->data[quote_len + indent_len] == '\0')
905 return FALSE;
906
907 /* Case 3). */
908 if (!quotes_match(foo->data, quote_len, foo->prev->data))
909 return TRUE;
910
911 temp_id_len = indent_length(foo->prev->data + quote_len);
912
913 /* Case 2) or 5) or 4). */
914 if (foo->prev->data[quote_len + temp_id_len] == '\0' ||
915 (quote_len == 0 && indent_len > 0
916#ifndef NANO_SMALL
917 && !ISSET(AUTOINDENT)
918#endif
919 ) || !indents_match(foo->prev->data + quote_len, temp_id_len,
920 foo->data + quote_len, indent_len))
921 return TRUE;
922
923 return FALSE;
924}
925
926/* Is foo inside a paragraph? */
927bool inpar(const filestruct *const foo)
928{
929 size_t quote_len;
930
931 if (foo == NULL)
932 return FALSE;
933
934 quote_len = quote_length(foo->data);
935
936 return foo->data[quote_len + indent_length(foo->data +
937 quote_len)] != '\0';
938}
939
940/* Put the next par_len lines, starting with first_line, into the
941 * justify buffer, leaving copies of those lines in place. Assume there
942 * are enough lines after first_line. Return the new copy of
943 * first_line. */
944filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t
945 quote_len)
946{
947 filestruct *top = first_line;
948 /* The top of the paragraph we're backing up. */
949 filestruct *bot = first_line;
950 /* The bottom of the paragraph we're backing up. */
951 size_t i;
952 /* Generic loop variable. */
953 size_t current_x_save = openfile->current_x;
954 ssize_t fl_lineno_save = first_line->lineno;
955 ssize_t edittop_lineno_save = openfile->edittop->lineno;
956 ssize_t current_lineno_save = openfile->current->lineno;
957#ifndef NANO_SMALL
958 bool old_mark_set = openfile->mark_set;
959 ssize_t mb_lineno_save = 0;
960 size_t mark_begin_x_save = 0;
961
962 if (old_mark_set) {
963 mb_lineno_save = openfile->mark_begin->lineno;
964 mark_begin_x_save = openfile->mark_begin_x;
965 }
966#endif
967
968 /* Move bot down par_len lines to the newline after the last line of
969 * the paragraph. */
970 for (i = par_len; i > 0; i--)
971 bot = bot->next;
972
973 /* Move the paragraph from the current buffer's filestruct to the
974 * justify buffer. */
975 move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0);
976
977 /* Copy the paragraph back to the current buffer's filestruct from
978 * the justify buffer. */
979 copy_from_filestruct(jusbuffer, jusbottom);
980
981 /* Move upward from the last line of the paragraph to the first
982 * line, putting first_line, edittop, current, and mark_begin at the
983 * same lines in the copied paragraph that they had in the original
984 * paragraph. */
985 top = openfile->current->prev;
986 for (i = par_len; i > 0; i--) {
987 if (top->lineno == fl_lineno_save)
988 first_line = top;
989 if (top->lineno == edittop_lineno_save)
990 openfile->edittop = top;
991 if (top->lineno == current_lineno_save)
992 openfile->current = top;
993#ifndef NANO_SMALL
994 if (old_mark_set && top->lineno == mb_lineno_save) {
995 openfile->mark_begin = top;
996 openfile->mark_begin_x = mark_begin_x_save;
997 }
998#endif
999 top = top->prev;
1000 }
1001
1002 /* Put current_x at the same place in the copied paragraph that it
1003 * had in the original paragraph. */
1004 openfile->current_x = current_x_save;
1005
1006 set_modified();
1007
1008 return first_line;
1009}
1010
1011/* Find the beginning of the current paragraph if we're in one, or the
1012 * beginning of the next paragraph if we're not. Afterwards, save the
1013 * quote length and paragraph length in *quote and *par. Return TRUE if
1014 * we found a paragraph, or FALSE if there was an error or we didn't
1015 * find a paragraph.
1016 *
1017 * See the comment at begpar() for more about when a line is the
1018 * beginning of a paragraph. */
1019bool find_paragraph(size_t *const quote, size_t *const par)
1020{
1021 size_t quote_len;
1022 /* Length of the initial quotation of the paragraph we search
1023 * for. */
1024 size_t par_len;
1025 /* Number of lines in the paragraph we search for. */
1026 filestruct *current_save;
1027 /* The line at the beginning of the paragraph we search for. */
1028 ssize_t current_y_save;
1029 /* The y-coordinate at the beginning of the paragraph we search
1030 * for. */
1031
1032#ifdef HAVE_REGEX_H
1033 if (quoterc != 0) {
1034 statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr);
1035 return FALSE;
1036 }
1037#endif
1038
1039 assert(openfile->current != NULL);
1040
1041 /* Move back to the beginning of the current line. */
1042 openfile->current_x = 0;
1043 openfile->placewewant = 0;
1044
1045 /* Find the first line of the current or next paragraph. First, if
1046 * the current line isn't in a paragraph, move forward to the line
1047 * after the last line of the next paragraph. If we end up on the
1048 * same line, or the line before that isn't in a paragraph, it means
1049 * that there aren't any paragraphs left, so get out. Otherwise,
1050 * move back to the last line of the paragraph. If the current line
1051 * is in a paragraph and it isn't the first line of that paragraph,
1052 * move back to the first line. */
1053 if (!inpar(openfile->current)) {
1054 current_save = openfile->current;
1055
1056 do_para_end(FALSE);
1057 if (openfile->current == current_save ||
1058 !inpar(openfile->current->prev))
1059 return FALSE;
1060 if (openfile->current->prev != NULL)
1061 openfile->current = openfile->current->prev;
1062 }
1063 if (!begpar(openfile->current))
1064 do_para_begin(FALSE);
1065
1066 /* Now current is the first line of the paragraph. Set quote_len to
1067 * the quotation length of that line, and set par_len to the number
1068 * of lines in this paragraph. */
1069 quote_len = quote_length(openfile->current->data);
1070 current_save = openfile->current;
1071 current_y_save = openfile->current_y;
1072 do_para_end(FALSE);
1073 par_len = openfile->current->lineno - current_save->lineno;
1074 openfile->current = current_save;
1075 openfile->current_y = current_y_save;
1076
1077 /* Save the values of quote_len and par_len. */
1078 assert(quote != NULL && par != NULL);
1079
1080 *quote = quote_len;
1081 *par = par_len;
1082
1083 return TRUE;
1084}
1085
1086/* If full_justify is TRUE, justify the entire file. Otherwise, justify
1087 * the current paragraph. */
1088void do_justify(bool full_justify)
1089{
1090 filestruct *first_par_line = NULL;
1091 /* Will be the first line of the resulting justified paragraph.
1092 * For restoring after unjustify. */
1093 filestruct *last_par_line;
1094 /* Will be the line containing the newline after the last line
1095 * of the result. Also for restoring after unjustify. */
1096
1097 /* We save these variables to be restored if the user unjustifies.
1098 * Note that we don't need to save totlines. */
1099 size_t current_x_save = openfile->current_x;
1100 size_t pww_save = openfile->placewewant;
1101 ssize_t current_y_save = openfile->current_y;
1102 bool modified_save = openfile->modified;
1103 size_t totsize_save = openfile->totsize;
1104 filestruct *edittop_save = openfile->edittop;
1105 filestruct *current_save = openfile->current;
1106#ifndef NANO_SMALL
1107 filestruct *mark_begin_save = openfile->mark_begin;
1108 size_t mark_begin_x_save = openfile->mark_begin_x;
1109#endif
1110 int kbinput;
1111 bool meta_key, func_key, s_or_t, ran_func, finished;
1112
1113 /* If we're justifying the entire file, start at the beginning. */
1114 if (full_justify)
1115 openfile->current = openfile->fileage;
1116
1117 last_par_line = openfile->current;
1118
1119 while (TRUE) {
1120 size_t i;
1121 /* Generic loop variable. */
1122 size_t quote_len;
1123 /* Length of the initial quotation of the paragraph we
1124 * justify. */
1125 size_t indent_len;
1126 /* Length of the initial indentation of the paragraph we
1127 * justify. */
1128 size_t par_len;
1129 /* Number of lines in the paragraph we justify. */
1130 ssize_t break_pos;
1131 /* Where we will break lines. */
1132 char *indent_string;
1133 /* The first indentation that doesn't match the initial
1134 * indentation of the paragraph we justify. This is put at
1135 * the beginning of every line broken off the first
1136 * justified line of the paragraph. (Note that this works
1137 * because a paragraph can only contain two indentations at
1138 * most: the initial one, and a different one starting on a
1139 * line after the first. See the comment at begpar() for
1140 * more about when a line is part of a paragraph.) */
1141
1142 /* Find the first line of the paragraph to be justified. That
1143 * is the start of this paragraph if we're in one, or the start
1144 * of the next otherwise. Save the quote length and paragraph
1145 * length (number of lines). Don't refresh the screen yet,
1146 * since we'll do that after we justify.
1147 *
1148 * If the search failed, we do one of two things. If we're
1149 * justifying the whole file, we've found at least one
1150 * paragraph, and the search didn't leave us on the last line of
1151 * the file, it means that we should justify all the way to the
1152 * last line of the file, so set the last line of the text to be
1153 * justified to the last line of the file and break out of the
1154 * loop. Otherwise, it means that there are no paragraph(s) to
1155 * justify, so refresh the screen and get out. */
1156 if (!find_paragraph(&quote_len, &par_len)) {
1157 if (full_justify && first_par_line != NULL &&
1158 first_par_line != openfile->filebot) {
1159 last_par_line = openfile->filebot;
1160 break;
1161 } else {
1162 edit_refresh();
1163 return;
1164 }
1165 }
1166
1167 /* If we haven't already done it, copy the original paragraph(s)
1168 * to the justify buffer. */
1169 if (first_par_line == NULL)
1170 first_par_line = backup_lines(openfile->current,
1171 full_justify ? openfile->filebot->lineno -
1172 openfile->current->lineno : par_len, quote_len);
1173
1174 /* Initialize indent_string to a blank string. */
1175 indent_string = mallocstrcpy(NULL, "");
1176
1177 /* Find the first indentation in the paragraph that doesn't
1178 * match the indentation of the first line, and save it in
1179 * indent_string. If all the indentations are the same, save
1180 * the indentation of the first line in indent_string. */
1181 {
1182 const filestruct *indent_line = openfile->current;
1183 bool past_first_line = FALSE;
1184
1185 for (i = 0; i < par_len; i++) {
1186 indent_len = quote_len +
1187 indent_length(indent_line->data + quote_len);
1188
1189 if (indent_len != strlen(indent_string)) {
1190 indent_string = mallocstrncpy(indent_string,
1191 indent_line->data, indent_len + 1);
1192 indent_string[indent_len] = '\0';
1193
1194 if (past_first_line)
1195 break;
1196 }
1197
1198 if (indent_line == openfile->current)
1199 past_first_line = TRUE;
1200
1201 indent_line = indent_line->next;
1202 }
1203 }
1204
1205 /* Now tack all the lines of the paragraph together, skipping
1206 * the quoting and indentation on all lines after the first. */
1207 for (i = 0; i < par_len - 1; i++) {
1208 filestruct *next_line = openfile->current->next;
1209 size_t line_len = strlen(openfile->current->data);
1210 size_t next_line_len =
1211 strlen(openfile->current->next->data);
1212
1213 indent_len = quote_len +
1214 indent_length(openfile->current->next->data +
1215 quote_len);
1216
1217 next_line_len -= indent_len;
1218 openfile->totsize -= indent_len;
1219
1220 /* We're just about to tack the next line onto this one. If
1221 * this line isn't empty, make sure it ends in a space. */
1222 if (line_len > 0 &&
1223 openfile->current->data[line_len - 1] != ' ') {
1224 line_len++;
1225 openfile->current->data =
1226 charealloc(openfile->current->data,
1227 line_len + 1);
1228 openfile->current->data[line_len - 1] = ' ';
1229 openfile->current->data[line_len] = '\0';
1230 openfile->totsize++;
1231 }
1232
1233 openfile->current->data =
1234 charealloc(openfile->current->data, line_len +
1235 next_line_len + 1);
1236 strcat(openfile->current->data, next_line->data +
1237 indent_len);
1238
1239 /* Don't destroy edittop! */
1240 if (openfile->edittop == next_line)
1241 openfile->edittop = openfile->current;
1242
1243#ifndef NANO_SMALL
1244 /* Adjust the mark coordinates to compensate for the change
1245 * in the next line. */
1246 if (openfile->mark_set && openfile->mark_begin ==
1247 next_line) {
1248 openfile->mark_begin = openfile->current;
1249 openfile->mark_begin_x += line_len - indent_len;
1250 }
1251#endif
1252
1253 unlink_node(next_line);
1254 delete_node(next_line);
1255
1256 /* If we've removed the next line, we need to go through
1257 * this line again. */
1258 i--;
1259
1260 par_len--;
1261 openfile->totlines--;
1262 openfile->totsize--;
1263 }
1264
1265 /* Call justify_format() on the paragraph, which will remove
1266 * excess spaces from it and change all blank characters to
1267 * spaces. */
1268 justify_format(openfile->current, quote_len +
1269 indent_length(openfile->current->data + quote_len));
1270
1271 while (par_len > 0 &&
1272 strlenpt(openfile->current->data) > fill) {
1273 size_t line_len = strlen(openfile->current->data);
1274
1275 indent_len = strlen(indent_string);
1276
1277 /* If this line is too long, try to wrap it to the next line
1278 * to make it short enough. */
1279 break_pos =
1280 break_line(openfile->current->data + indent_len, fill -
1281 strnlenpt(openfile->current->data, indent_len), FALSE);
1282
1283 /* We can't break the line, or don't need to, so get out. */
1284 if (break_pos == -1 || break_pos + indent_len == line_len)
1285 break;
1286
1287 /* Move forward to the character after the indentation and
1288 * just after the space. */
1289 break_pos += indent_len + 1;
1290
1291 assert(break_pos <= line_len);
1292
1293 /* Make a new line, and copy the text after where we're
1294 * going to break this line to the beginning of the new
1295 * line. */
1296 splice_node(openfile->current,
1297 make_new_node(openfile->current),
1298 openfile->current->next);
1299
1300 /* If this paragraph is non-quoted, and autoindent isn't
1301 * turned on, set the indentation length to zero so that the
1302 * indentation is treated as part of the line. */
1303 if (quote_len == 0
1304#ifndef NANO_SMALL
1305 && !ISSET(AUTOINDENT)
1306#endif
1307 )
1308 indent_len = 0;
1309
1310 /* Copy the text after where we're going to break the
1311 * current line to the next line. */
1312 openfile->current->next->data = charalloc(indent_len + 1 +
1313 line_len - break_pos);
1314 strncpy(openfile->current->next->data, indent_string,
1315 indent_len);
1316 strcpy(openfile->current->next->data + indent_len,
1317 openfile->current->data + break_pos);
1318
1319 par_len++;
1320 openfile->totlines++;
1321 openfile->totsize += indent_len + 1;
1322
1323#ifndef NANO_SMALL
1324 /* Adjust the mark coordinates to compensate for the change
1325 * in the current line. */
1326 if (openfile->mark_set && openfile->mark_begin ==
1327 openfile->current && openfile->mark_begin_x >
1328 break_pos) {
1329 openfile->mark_begin = openfile->current->next;
1330 openfile->mark_begin_x -= break_pos - indent_len;
1331 }
1332#endif
1333
1334 /* Break the current line. */
1335 null_at(&openfile->current->data, break_pos);
1336
1337 /* Go to the next line. */
1338 par_len--;
1339 openfile->current_y++;
1340 openfile->current = openfile->current->next;
1341 }
1342
1343 /* We're done breaking lines, so we don't need indent_string
1344 * anymore. */
1345 free(indent_string);
1346
1347 /* Go to the next line, the line after the last line of the
1348 * paragraph. */
1349 openfile->current_y++;
1350 openfile->current = openfile->current->next;
1351
1352 /* We've just justified a paragraph. If we're not justifying the
1353 * entire file, break out of the loop. Otherwise, continue the
1354 * loop so that we justify all the paragraphs in the file. */
1355 if (!full_justify)
1356 break;
1357 }
1358
1359 /* We are now done justifying the paragraph or the file, so clean
1360 * up. totlines, totsize, and current_y have been maintained above.
1361 * Set last_par_line to the new end of the paragraph, update
1362 * fileage, and renumber since edit_refresh() needs the line numbers
1363 * to be right (but only do the last two if we actually justified
1364 * something). */
1365 last_par_line = openfile->current;
1366 if (first_par_line != NULL) {
1367 if (first_par_line->prev == NULL)
1368 openfile->fileage = first_par_line;
1369 renumber(first_par_line);
1370 }
1371
1372 edit_refresh();
1373
1374 statusbar(_("Can now UnJustify!"));
1375
1376 /* If constant cursor position display is on, make sure the current
1377 * cursor position will be properly displayed on the statusbar. */
1378 if (ISSET(CONST_UPDATE))
1379 do_cursorpos(TRUE);
1380
1381 /* Display the shortcut list with UnJustify. */
1382 shortcut_init(TRUE);
1383 display_main_list();
1384
1385 /* Now get a keystroke and see if it's unjustify. If not, put back
1386 * the keystroke and return. */
1387 kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func,
1388 &finished, FALSE);
1389
1390 if (!meta_key && !func_key && s_or_t &&
1391 kbinput == NANO_UNJUSTIFY_KEY) {
1392 /* Restore the justify we just did (ungrateful user!). */
1393 openfile->current = current_save;
1394 openfile->current_x = current_x_save;
1395 openfile->placewewant = pww_save;
1396 openfile->current_y = current_y_save;
1397 openfile->edittop = edittop_save;
1398
1399 /* Splice the justify buffer back into the file, but only if we
1400 * actually justified something. */
1401 if (first_par_line != NULL) {
1402 filestruct *top_save;
1403
1404 /* Partition the filestruct so that it contains only the
1405 * text of the justified paragraph. */
1406 filepart = partition_filestruct(first_par_line, 0,
1407 last_par_line, 0);
1408
1409 /* Remove the text of the justified paragraph, and
1410 * put the text in the justify buffer in its place. */
1411 free_filestruct(openfile->fileage);
1412 openfile->fileage = jusbuffer;
1413 openfile->filebot = jusbottom;
1414
1415 top_save = openfile->fileage;
1416
1417 /* Unpartition the filestruct so that it contains all the
1418 * text again. Note that the justified paragraph has been
1419 * replaced with the unjustified paragraph. */
1420 unpartition_filestruct(&filepart);
1421
1422 /* Renumber starting with the beginning line of the old
1423 * partition. */
1424 renumber(top_save);
1425
1426 /* Restore variables from before the justify. */
1427 openfile->totsize = totsize_save;
1428 openfile->totlines = openfile->filebot->lineno;
1429#ifndef NANO_SMALL
1430 if (openfile->mark_set) {
1431 openfile->mark_begin = mark_begin_save;
1432 openfile->mark_begin_x = mark_begin_x_save;
1433 }
1434#endif
1435 openfile->modified = modified_save;
1436
1437 /* Clear the justify buffer. */
1438 jusbuffer = NULL;
1439
1440 if (!openfile->modified)
1441 titlebar(NULL);
1442 edit_refresh();
1443 }
1444 } else {
1445 unget_kbinput(kbinput, meta_key, func_key);
1446
1447 /* Blow away the text in the justify buffer. */
1448 free_filestruct(jusbuffer);
1449 jusbuffer = NULL;
1450 }
1451
1452 blank_statusbar();
1453
1454 /* Display the shortcut list with UnCut. */
1455 shortcut_init(FALSE);
1456 display_main_list();
1457}
1458
1459void do_justify_void(void)
1460{
1461 do_justify(FALSE);
1462}
1463
1464void do_full_justify(void)
1465{
1466 do_justify(TRUE);
1467}
1468#endif /* !DISABLE_JUSTIFY */
1469
David Lawrence Ramseycc8185f2005-07-25 02:33:45 +00001470#ifndef DISABLE_SPELLER
1471/* A word is misspelled in the file. Let the user replace it. We
1472 * return FALSE if the user cancels. */
1473bool do_int_spell_fix(const char *word)
1474{
1475 char *save_search, *save_replace;
1476 size_t match_len, current_x_save = openfile->current_x;
1477 size_t pww_save = openfile->placewewant;
1478 filestruct *edittop_save = openfile->edittop;
1479 filestruct *current_save = openfile->current;
1480 /* Save where we are. */
1481 bool canceled = FALSE;
1482 /* The return value. */
1483 bool case_sens_set = ISSET(CASE_SENSITIVE);
1484#ifndef NANO_SMALL
1485 bool backwards_search_set = ISSET(BACKWARDS_SEARCH);
1486#endif
1487#ifdef HAVE_REGEX_H
1488 bool regexp_set = ISSET(USE_REGEXP);
1489#endif
1490#ifndef NANO_SMALL
1491 bool old_mark_set = openfile->mark_set;
1492 bool added_magicline = FALSE;
1493 /* Whether we added a magicline after filebot. */
1494 bool right_side_up = FALSE;
1495 /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
1496 * FALSE if (current, current_x) is. */
1497 filestruct *top, *bot;
1498 size_t top_x, bot_x;
1499#endif
1500
1501 /* Make sure spell-check is case sensitive. */
1502 SET(CASE_SENSITIVE);
1503
1504#ifndef NANO_SMALL
1505 /* Make sure spell-check goes forward only. */
1506 UNSET(BACKWARDS_SEARCH);
1507#endif
1508#ifdef HAVE_REGEX_H
1509 /* Make sure spell-check doesn't use regular expressions. */
1510 UNSET(USE_REGEXP);
1511#endif
1512
1513 /* Save the current search/replace strings. */
1514 search_init_globals();
1515 save_search = last_search;
1516 save_replace = last_replace;
1517
1518 /* Set the search/replace strings to the misspelled word. */
1519 last_search = mallocstrcpy(NULL, word);
1520 last_replace = mallocstrcpy(NULL, word);
1521
1522#ifndef NANO_SMALL
1523 if (old_mark_set) {
1524 /* If the mark is on, partition the filestruct so that it
1525 * contains only the marked text, keep track of whether the text
1526 * will have a magicline added when we're done correcting
1527 * misspelled words, and turn the mark off. */
1528 mark_order((const filestruct **)&top, &top_x,
1529 (const filestruct **)&bot, &bot_x, &right_side_up);
1530 filepart = partition_filestruct(top, top_x, bot, bot_x);
1531 added_magicline = (openfile->filebot->data[0] != '\0');
1532 openfile->mark_set = FALSE;
1533 }
1534#endif
1535
1536 /* Start from the top of the file. */
1537 openfile->edittop = openfile->fileage;
1538 openfile->current = openfile->fileage;
1539 openfile->current_x = (size_t)-1;
1540 openfile->placewewant = 0;
1541
1542 /* Find the first whole-word occurrence of word. */
1543 findnextstr_wrap_reset();
1544 while (findnextstr(TRUE, TRUE, FALSE, openfile->fileage, 0, word,
1545 &match_len)) {
1546 if (is_whole_word(openfile->current_x, openfile->current->data,
1547 word)) {
1548 size_t xpt = xplustabs();
1549 char *exp_word = display_string(openfile->current->data,
1550 xpt, strnlenpt(openfile->current->data,
1551 openfile->current_x + match_len) - xpt, FALSE);
1552
1553 edit_refresh();
1554
1555 do_replace_highlight(TRUE, exp_word);
1556
1557 /* Allow all instances of the word to be corrected. */
1558 canceled = (statusq(FALSE, spell_list, word,
1559#ifndef NANO_SMALL
1560 NULL,
1561#endif
1562 _("Edit a replacement")) == -1);
1563
1564 do_replace_highlight(FALSE, exp_word);
1565
1566 free(exp_word);
1567
1568 if (!canceled && strcmp(word, answer) != 0) {
1569 openfile->current_x--;
1570 do_replace_loop(word, openfile->current,
1571 &openfile->current_x, TRUE, &canceled);
1572 }
1573
1574 break;
1575 }
1576 }
1577
1578#ifndef NANO_SMALL
1579 if (old_mark_set) {
1580 /* If the mark was on and we added a magicline, remove it
1581 * now. */
1582 if (added_magicline)
1583 remove_magicline();
1584
1585 /* Put the beginning and the end of the mark at the beginning
1586 * and the end of the spell-checked text. */
1587 if (openfile->fileage == openfile->filebot)
1588 bot_x += top_x;
1589 if (right_side_up) {
1590 openfile->mark_begin_x = top_x;
1591 current_x_save = bot_x;
1592 } else {
1593 current_x_save = top_x;
1594 openfile->mark_begin_x = bot_x;
1595 }
1596
1597 /* Unpartition the filestruct so that it contains all the text
1598 * again, and turn the mark back on. */
1599 unpartition_filestruct(&filepart);
1600 openfile->mark_set = TRUE;
1601 }
1602#endif
1603
1604 /* Restore the search/replace strings. */
1605 free(last_search);
1606 last_search = save_search;
1607 free(last_replace);
1608 last_replace = save_replace;
1609
1610 /* Restore where we were. */
1611 openfile->edittop = edittop_save;
1612 openfile->current = current_save;
1613 openfile->current_x = current_x_save;
1614 openfile->placewewant = pww_save;
1615
1616 /* Restore case sensitivity setting. */
1617 if (!case_sens_set)
1618 UNSET(CASE_SENSITIVE);
1619
1620#ifndef NANO_SMALL
1621 /* Restore search/replace direction. */
1622 if (backwards_search_set)
1623 SET(BACKWARDS_SEARCH);
1624#endif
1625#ifdef HAVE_REGEX_H
1626 /* Restore regular expression usage setting. */
1627 if (regexp_set)
1628 SET(USE_REGEXP);
1629#endif
1630
1631 return !canceled;
1632}
1633
1634/* Integrated spell checking using the spell program, filtered through
1635 * the sort and uniq programs. Return NULL for normal termination,
1636 * and the error string otherwise. */
1637const char *do_int_speller(const char *tempfile_name)
1638{
1639 char *read_buff, *read_buff_ptr, *read_buff_word;
1640 size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
1641 int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
1642 pid_t pid_spell, pid_sort, pid_uniq;
1643 int spell_status, sort_status, uniq_status;
1644
1645 /* Create all three pipes up front. */
1646 if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 ||
1647 pipe(uniq_fd) == -1)
1648 return _("Could not create pipe");
1649
1650 statusbar(_("Creating misspelled word list, please wait..."));
1651
1652 /* A new process to run spell in. */
1653 if ((pid_spell = fork()) == 0) {
1654 /* Child continues (i.e, future spell process). */
1655 close(spell_fd[0]);
1656
1657 /* Replace the standard input with the temp file. */
1658 if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
1659 goto close_pipes_and_exit;
1660
1661 if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO)
1662 goto close_pipes_and_exit;
1663
1664 close(tempfile_fd);
1665
1666 /* Send spell's standard output to the pipe. */
1667 if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
1668 goto close_pipes_and_exit;
1669
1670 close(spell_fd[1]);
1671
1672 /* Start the spell program; we are using PATH. */
1673 execlp("spell", "spell", NULL);
1674
1675 /* This should not be reached if spell is found. */
1676 exit(1);
1677 }
1678
1679 /* Parent continues here. */
1680 close(spell_fd[1]);
1681
1682 /* A new process to run sort in. */
1683 if ((pid_sort = fork()) == 0) {
1684 /* Child continues (i.e, future spell process). Replace the
1685 * standard input with the standard output of the old pipe. */
1686 if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
1687 goto close_pipes_and_exit;
1688
1689 close(spell_fd[0]);
1690
1691 /* Send sort's standard output to the new pipe. */
1692 if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
1693 goto close_pipes_and_exit;
1694
1695 close(sort_fd[1]);
1696
1697 /* Start the sort program. Use -f to remove mixed case. If
1698 * this isn't portable, let me know. */
1699 execlp("sort", "sort", "-f", NULL);
1700
1701 /* This should not be reached if sort is found. */
1702 exit(1);
1703 }
1704
1705 close(spell_fd[0]);
1706 close(sort_fd[1]);
1707
1708 /* A new process to run uniq in. */
1709 if ((pid_uniq = fork()) == 0) {
1710 /* Child continues (i.e, future uniq process). Replace the
1711 * standard input with the standard output of the old pipe. */
1712 if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
1713 goto close_pipes_and_exit;
1714
1715 close(sort_fd[0]);
1716
1717 /* Send uniq's standard output to the new pipe. */
1718 if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
1719 goto close_pipes_and_exit;
1720
1721 close(uniq_fd[1]);
1722
1723 /* Start the uniq program; we are using PATH. */
1724 execlp("uniq", "uniq", NULL);
1725
1726 /* This should not be reached if uniq is found. */
1727 exit(1);
1728 }
1729
1730 close(sort_fd[0]);
1731 close(uniq_fd[1]);
1732
1733 /* The child process was not forked successfully. */
1734 if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
1735 close(uniq_fd[0]);
1736 return _("Could not fork");
1737 }
1738
1739 /* Get the system pipe buffer size. */
1740 if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
1741 close(uniq_fd[0]);
1742 return _("Could not get size of pipe buffer");
1743 }
1744
1745 /* Read in the returned spelling errors. */
1746 read_buff_read = 0;
1747 read_buff_size = pipe_buff_size + 1;
1748 read_buff = read_buff_ptr = charalloc(read_buff_size);
1749
1750 while ((bytesread = read(uniq_fd[0], read_buff_ptr,
1751 pipe_buff_size)) > 0) {
1752 read_buff_read += bytesread;
1753 read_buff_size += pipe_buff_size;
1754 read_buff = read_buff_ptr = charealloc(read_buff,
1755 read_buff_size);
1756 read_buff_ptr += read_buff_read;
1757 }
1758
1759 *read_buff_ptr = '\0';
1760 close(uniq_fd[0]);
1761
1762 /* Process the spelling errors. */
1763 read_buff_word = read_buff_ptr = read_buff;
1764
1765 while (*read_buff_ptr != '\0') {
1766 if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
1767 *read_buff_ptr = '\0';
1768 if (read_buff_word != read_buff_ptr) {
1769 if (!do_int_spell_fix(read_buff_word)) {
1770 read_buff_word = read_buff_ptr;
1771 break;
1772 }
1773 }
1774 read_buff_word = read_buff_ptr + 1;
1775 }
1776 read_buff_ptr++;
1777 }
1778
1779 /* Special case: the last word doesn't end with '\r' or '\n'. */
1780 if (read_buff_word != read_buff_ptr)
1781 do_int_spell_fix(read_buff_word);
1782
1783 free(read_buff);
1784 replace_abort();
1785 edit_refresh();
1786
1787 /* Process the end of the spell process. */
1788 waitpid(pid_spell, &spell_status, 0);
1789 waitpid(pid_sort, &sort_status, 0);
1790 waitpid(pid_uniq, &uniq_status, 0);
1791
1792 if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
1793 return _("Error invoking \"spell\"");
1794
1795 if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
1796 return _("Error invoking \"sort -f\"");
1797
1798 if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
1799 return _("Error invoking \"uniq\"");
1800
1801 /* Otherwise... */
1802 return NULL;
1803
1804 close_pipes_and_exit:
1805 /* Don't leak any handles. */
1806 close(tempfile_fd);
1807 close(spell_fd[0]);
1808 close(spell_fd[1]);
1809 close(sort_fd[0]);
1810 close(sort_fd[1]);
1811 close(uniq_fd[0]);
1812 close(uniq_fd[1]);
1813 exit(1);
1814}
1815
1816/* External spell checking. Return value: NULL for normal termination,
1817 * otherwise the error string. */
1818const char *do_alt_speller(char *tempfile_name)
1819{
1820 int alt_spell_status;
1821 size_t current_x_save = openfile->current_x;
1822 size_t pww_save = openfile->placewewant;
1823 ssize_t current_y_save = openfile->current_y;
1824 ssize_t lineno_save = openfile->current->lineno;
1825 pid_t pid_spell;
1826 char *ptr;
1827 static int arglen = 3;
1828 static char **spellargs = NULL;
1829 FILE *f;
1830#ifndef NANO_SMALL
1831 bool old_mark_set = openfile->mark_set;
1832 bool added_magicline = FALSE;
1833 /* Whether we added a magicline after filebot. */
1834 bool right_side_up = FALSE;
1835 /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
1836 * FALSE if (current, current_x) is. */
1837 filestruct *top, *bot;
1838 size_t top_x, bot_x;
1839 ssize_t mb_lineno_save = 0;
1840 /* We're going to close the current file, and open the output of
1841 * the alternate spell command. The line that mark_begin points
1842 * to will be freed, so we save the line number and restore it
1843 * afterwards. */
1844 size_t totsize_save = openfile->totsize;
1845 /* Our saved value of totsize, used when we spell-check a marked
1846 * selection. */
1847
1848 if (old_mark_set) {
1849 /* If the mark is on, save the number of the line it starts on,
1850 * and then turn the mark off. */
1851 mb_lineno_save = openfile->mark_begin->lineno;
1852 openfile->mark_set = FALSE;
1853 }
1854#endif
1855
1856 endwin();
1857
1858 /* Set up an argument list to pass execvp(). */
1859 if (spellargs == NULL) {
1860 spellargs = (char **)nmalloc(arglen * sizeof(char *));
1861
1862 spellargs[0] = strtok(alt_speller, " ");
1863 while ((ptr = strtok(NULL, " ")) != NULL) {
1864 arglen++;
1865 spellargs = (char **)nrealloc(spellargs, arglen *
1866 sizeof(char *));
1867 spellargs[arglen - 3] = ptr;
1868 }
1869 spellargs[arglen - 1] = NULL;
1870 }
1871 spellargs[arglen - 2] = tempfile_name;
1872
1873 /* Start a new process for the alternate speller. */
1874 if ((pid_spell = fork()) == 0) {
1875 /* Start alternate spell program; we are using PATH. */
1876 execvp(spellargs[0], spellargs);
1877
1878 /* Should not be reached, if alternate speller is found!!! */
1879 exit(1);
1880 }
1881
1882 /* If we couldn't fork, get out. */
1883 if (pid_spell < 0)
1884 return _("Could not fork");
1885
1886 /* Wait for alternate speller to complete. */
1887 wait(&alt_spell_status);
1888
1889 refresh();
1890
1891 /* Restore the terminal to its previous state. */
1892 terminal_init();
1893
1894 /* Turn the cursor back on for sure. */
1895 curs_set(1);
1896
1897 if (!WIFEXITED(alt_spell_status) ||
1898 WEXITSTATUS(alt_spell_status) != 0) {
1899 char *altspell_error;
1900 char *invoke_error = _("Error invoking \"%s\"");
1901
1902#ifndef NANO_SMALL
1903 /* Turn the mark back on if it was on before. */
1904 openfile->mark_set = old_mark_set;
1905#endif
1906
1907 altspell_error =
1908 charalloc(strlen(invoke_error) +
1909 strlen(alt_speller) + 1);
1910 sprintf(altspell_error, invoke_error, alt_speller);
1911 return altspell_error;
1912 }
1913
1914#ifndef NANO_SMALL
1915 if (old_mark_set) {
1916 /* If the mark was on, partition the filestruct so that it
1917 * contains only the marked text, and keep track of whether the
1918 * temp file (which should contain the spell-checked marked
1919 * text) will have a magicline added when it's reloaded. */
1920 mark_order((const filestruct **)&top, &top_x,
1921 (const filestruct **)&bot, &bot_x, &right_side_up);
1922 filepart = partition_filestruct(top, top_x, bot, bot_x);
1923 added_magicline = (openfile->filebot->data[0] != '\0');
1924
1925 /* Get the number of characters in the marked text, and subtract
1926 * it from the saved value of totsize. */
1927 totsize_save -= get_totsize(top, bot);
1928 }
1929#endif
1930
1931 /* Set up the window size. */
1932 window_size_init();
1933
1934 /* Reinitialize the text of the current buffer. */
1935 free_filestruct(openfile->fileage);
1936 initialize_buffer_text();
1937
1938 /* Reload the temp file. Open it, read it into the current buffer,
1939 * and move back to the first line of the buffer. */
1940 open_file(tempfile_name, FALSE, &f);
1941 read_file(f, tempfile_name);
1942 openfile->current = openfile->fileage;
1943
1944#ifndef NANO_SMALL
1945 if (old_mark_set) {
1946 filestruct *top_save = openfile->fileage;
1947
1948 /* If the mark was on and we added a magicline, remove it
1949 * now. */
1950 if (added_magicline)
1951 remove_magicline();
1952
1953 /* Put the beginning and the end of the mark at the beginning
1954 * and the end of the spell-checked text. */
1955 if (openfile->fileage == openfile->filebot)
1956 bot_x += top_x;
1957 if (right_side_up) {
1958 openfile->mark_begin_x = top_x;
1959 current_x_save = bot_x;
1960 } else {
1961 current_x_save = top_x;
1962 openfile->mark_begin_x = bot_x;
1963 }
1964
1965 /* Unpartition the filestruct so that it contains all the text
1966 * again. Note that we've replaced the marked text originally
1967 * in the partition with the spell-checked marked text in the
1968 * temp file. */
1969 unpartition_filestruct(&filepart);
1970
1971 /* Renumber starting with the beginning line of the old
1972 * partition. Also set totlines to the new number of lines in
1973 * the file, add the number of characters in the spell-checked
1974 * marked text to the saved value of totsize, and then make that
1975 * saved value the actual value. */
1976 renumber(top_save);
1977 openfile->totlines = openfile->filebot->lineno;
1978 totsize_save += openfile->totsize;
1979 openfile->totsize = totsize_save;
1980
1981 /* Assign mark_begin to the line where the mark began before. */
1982 do_gotopos(mb_lineno_save, openfile->mark_begin_x,
1983 current_y_save, 0);
1984 openfile->mark_begin = openfile->current;
1985
1986 /* Assign mark_begin_x to the location in mark_begin where the
1987 * mark began before, adjusted for any shortening of the
1988 * line. */
1989 openfile->mark_begin_x = openfile->current_x;
1990
1991 /* Turn the mark back on. */
1992 openfile->mark_set = TRUE;
1993 }
1994#endif
1995
1996 /* Go back to the old position, and mark the file as modified. */
1997 do_gotopos(lineno_save, current_x_save, current_y_save, pww_save);
1998 set_modified();
1999
2000 return NULL;
2001}
2002
2003void do_spell(void)
2004{
2005 int i;
2006 FILE *temp_file;
2007 char *temp = safe_tempfile(&temp_file);
2008 const char *spell_msg;
2009
2010 if (temp == NULL) {
2011 statusbar(_("Could not create temp file: %s"), strerror(errno));
2012 return;
2013 }
2014
2015#ifndef NANO_SMALL
2016 if (openfile->mark_set)
2017 i = write_marked_file(temp, temp_file, TRUE, FALSE);
2018 else
2019#endif
2020 i = write_file(temp, temp_file, TRUE, FALSE, FALSE);
2021
2022 if (i == -1) {
2023 statusbar(_("Error writing temp file: %s"), strerror(errno));
2024 free(temp);
2025 return;
2026 }
2027
2028 spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) :
2029 do_int_speller(temp);
2030 unlink(temp);
2031 free(temp);
2032
2033 /* If the spell-checker printed any error messages onscreen, make
2034 * sure that they're cleared off. */
2035 total_refresh();
2036
2037 if (spell_msg != NULL) {
2038 if (errno == 0)
2039 /* Don't display an error message of "Success". */
2040 statusbar(_("Spell checking failed: %s"), spell_msg);
2041 else
2042 statusbar(_("Spell checking failed: %s: %s"), spell_msg,
2043 strerror(errno));
2044 } else
2045 statusbar(_("Finished checking spelling"));
2046}
2047#endif /* !DISABLE_SPELLER */
2048
David Lawrence Ramsey691698a2005-07-24 19:57:51 +00002049#ifndef NANO_SMALL
2050void do_word_count(void)
2051{
2052 size_t words = 0, current_x_save = openfile->current_x;
2053 size_t pww_save = openfile->placewewant;
2054 filestruct *current_save = openfile->current;
2055 bool old_mark_set = openfile->mark_set;
2056 bool added_magicline = FALSE;
2057 /* Whether we added a magicline after filebot. */
2058 filestruct *top, *bot;
2059 size_t top_x, bot_x;
2060
2061 if (old_mark_set) {
2062 /* If the mark is on, partition the filestruct so that it
2063 * contains only the marked text, keep track of whether the text
2064 * will need a magicline added while we're counting words, add
2065 * the magicline if necessary, and turn the mark off. */
2066 mark_order((const filestruct **)&top, &top_x,
2067 (const filestruct **)&bot, &bot_x, NULL);
2068 filepart = partition_filestruct(top, top_x, bot, bot_x);
2069 if ((added_magicline = (openfile->filebot->data[0] != '\0')))
2070 new_magicline();
2071 openfile->mark_set = FALSE;
2072 }
2073
2074 /* Start at the top of the file. */
2075 openfile->current = openfile->fileage;
2076 openfile->current_x = 0;
2077 openfile->placewewant = 0;
2078
2079 /* Keep moving to the next word (counting punctuation characters as
2080 * part of a word so that we match the output of "wc -w"), without
2081 * updating the screen, until we reach the end of the file,
2082 * incrementing the total word count whenever we're on a word just
2083 * before moving. */
2084 while (openfile->current != openfile->filebot ||
2085 openfile->current_x != 0) {
2086 if (do_next_word(TRUE, FALSE))
2087 words++;
2088 }
2089
2090 if (old_mark_set) {
2091 /* If the mark was on and we added a magicline, remove it
2092 * now. */
2093 if (added_magicline)
2094 remove_magicline();
2095
2096 /* Unpartition the filestruct so that it contains all the text
2097 * again, and turn the mark back on. */
2098 unpartition_filestruct(&filepart);
2099 openfile->mark_set = TRUE;
2100 }
2101
2102 /* Restore where we were. */
2103 openfile->current = current_save;
2104 openfile->current_x = current_x_save;
2105 openfile->placewewant = pww_save;
2106
2107 /* Display the total word count on the statusbar. */
2108 statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") :
2109 _("Word Count"), (unsigned long)words);
2110}
2111#endif /* !NANO_SMALL */