1 /**************************************************************************
2 * text.c -- This file is part of GNU nano. *
3 * *
4 * Copyright (C) 1999-2011, 2013-2021 Free Software Foundation, Inc. *
5 * Copyright (C) 2014-2015 Mark Majeres *
6 * Copyright (C) 2016 Mike Scalora *
7 * Copyright (C) 2016 Sumedh Pendurkar *
8 * Copyright (C) 2018 Marco Diego Aurélio Mesquita *
9 * Copyright (C) 2015-2021 Benno Schulenberg *
10 * *
11 * GNU nano is free software: you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published *
13 * by the Free Software Foundation, either version 3 of the License, *
14 * or (at your option) any later version. *
15 * *
16 * GNU nano is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty *
18 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
19 * See the GNU General Public License for more details. *
20 * *
21 * You should have received a copy of the GNU General Public License *
22 * along with this program. If not, see http://www.gnu.org/licenses/. *
23 * *
24 **************************************************************************/
25
26 #include "prototypes.h"
27
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33 #include <sys/wait.h>
34
35 #if defined(__APPLE__) && !defined(st_mtim)
36 #define st_mtim st_mtimespec
37 #endif
38
39 #ifdef ENABLE_WORDCOMPLETION
40 static int pletion_x = 0;
41 /* The x position in pletion_line of the last found completion. */
42 static completionstruct *list_of_completions;
43 /* A linked list of the completions that have been attempted. */
44 #endif
45
46 #ifndef NANO_TINY
47 /* Toggle the mark. */
do_mark(void)48 void do_mark(void)
49 {
50 if (!openfile->mark) {
51 openfile->mark = openfile->current;
52 openfile->mark_x = openfile->current_x;
53 openfile->softmark = FALSE;
54 statusbar(_("Mark Set"));
55 } else {
56 openfile->mark = NULL;
57 statusbar(_("Mark Unset"));
58 refresh_needed = TRUE;
59 }
60 }
61 #endif
62
63 /* Insert a tab. Or, if --tabstospaces is in effect, insert the number
64 * of spaces that a tab would normally take up at this position. */
do_tab(void)65 void do_tab(void)
66 {
67 #ifdef ENABLE_COLOR
68 if (openfile->syntax && openfile->syntax->tab)
69 inject(openfile->syntax->tab, strlen(openfile->syntax->tab));
70 else
71 #endif
72 #ifndef NANO_TINY
73 if (ISSET(TABS_TO_SPACES)) {
74 char *spaces = nmalloc(tabsize + 1);
75 size_t length = tabsize - (xplustabs() % tabsize);
76
77 memset(spaces, ' ', length);
78 spaces[length] = '\0';
79
80 inject(spaces, length);
81
82 free(spaces);
83 } else
84 #endif
85 inject((char *)"\t", 1);
86 }
87
88 #ifndef NANO_TINY
89 /* Add an indent to the given line. */
indent_a_line(linestruct * line,char * indentation)90 void indent_a_line(linestruct *line, char *indentation)
91 {
92 size_t length = strlen(line->data);
93 size_t indent_len = strlen(indentation);
94
95 /* If the requested indentation is empty, don't change the line. */
96 if (indent_len == 0)
97 return;
98
99 /* Add the fabricated indentation to the beginning of the line. */
100 line->data = nrealloc(line->data, length + indent_len + 1);
101 memmove(line->data + indent_len, line->data, length + 1);
102 memcpy(line->data, indentation, indent_len);
103
104 openfile->totsize += indent_len;
105
106 /* Compensate for the change in the current line. */
107 if (line == openfile->mark && openfile->mark_x > 0)
108 openfile->mark_x += indent_len;
109 if (line == openfile->current && openfile->current_x > 0) {
110 openfile->current_x += indent_len;
111 openfile->placewewant = xplustabs();
112 }
113 }
114
115 /* Indent the current line (or the marked lines) by tabsize columns.
116 * This inserts either a tab character or a tab's worth of spaces,
117 * depending on whether --tabstospaces is in effect. */
do_indent(void)118 void do_indent(void)
119 {
120 char *indentation;
121 linestruct *top, *bot, *line;
122
123 /* Use either all the marked lines or just the current line. */
124 get_range(&top, &bot);
125
126 /* Skip any leading empty lines. */
127 while (top != bot->next && top->data[0] == '\0')
128 top = top->next;
129
130 /* If all lines are empty, there is nothing to do. */
131 if (top == bot->next)
132 return;
133
134 indentation = nmalloc(tabsize + 1);
135
136 #ifdef ENABLE_COLOR
137 if (openfile->syntax && openfile->syntax->tab)
138 indentation = mallocstrcpy(indentation, openfile->syntax->tab);
139 else
140 #endif
141 /* Set the indentation to either a bunch of spaces or a single tab. */
142 if (ISSET(TABS_TO_SPACES)) {
143 memset(indentation, ' ', tabsize);
144 indentation[tabsize] = '\0';
145 } else {
146 indentation[0] = '\t';
147 indentation[1] = '\0';
148 }
149
150 add_undo(INDENT, NULL);
151
152 /* Go through each of the lines, adding an indent to the non-empty ones,
153 * and recording whatever was added in the undo item. */
154 for (line = top; line != bot->next; line = line->next) {
155 char *real_indent = (line->data[0] == '\0') ? "" : indentation;
156
157 indent_a_line(line, real_indent);
158 update_multiline_undo(line->lineno, real_indent);
159 }
160
161 free(indentation);
162
163 set_modified();
164 ensure_firstcolumn_is_aligned();
165 refresh_needed = TRUE;
166 shift_held = TRUE;
167 }
168
169 /* Return the number of bytes of whitespace at the start of the given text,
170 * but at most a tab's worth. */
length_of_white(const char * text)171 size_t length_of_white(const char *text)
172 {
173 size_t white_count = 0;
174
175 #ifdef ENABLE_COLOR
176 if (openfile->syntax && openfile->syntax->tab) {
177 size_t thelength = strlen(openfile->syntax->tab);
178
179 while (text[white_count] == openfile->syntax->tab[white_count])
180 if (++white_count == thelength)
181 return thelength;
182
183 white_count = 0;
184 }
185 #endif
186
187 while (TRUE) {
188 if (*text == '\t')
189 return white_count + 1;
190
191 if (*text != ' ')
192 return white_count;
193
194 if (++white_count == tabsize)
195 return tabsize;
196
197 text++;
198 }
199 }
200
201 /* Adjust the positions of mark and cursor when they are on the given line. */
compensate_leftward(linestruct * line,size_t leftshift)202 void compensate_leftward(linestruct *line, size_t leftshift)
203 {
204 if (line == openfile->mark) {
205 if (openfile->mark_x < leftshift)
206 openfile->mark_x = 0;
207 else
208 openfile->mark_x -= leftshift;
209 }
210
211 if (line == openfile->current) {
212 if (openfile->current_x < leftshift)
213 openfile->current_x = 0;
214 else
215 openfile->current_x -= leftshift;
216 openfile->placewewant = xplustabs();
217 }
218 }
219
220 /* Remove an indent from the given line. */
unindent_a_line(linestruct * line,size_t indent_len)221 void unindent_a_line(linestruct *line, size_t indent_len)
222 {
223 size_t length = strlen(line->data);
224
225 /* If the indent is empty, don't change the line. */
226 if (indent_len == 0)
227 return;
228
229 /* Remove the first tab's worth of whitespace from this line. */
230 memmove(line->data, line->data + indent_len, length - indent_len + 1);
231
232 openfile->totsize -= indent_len;
233
234 /* Adjust the positions of mark and cursor, when they are affected. */
235 compensate_leftward(line, indent_len);
236 }
237
238 /* Unindent the current line (or the marked lines) by tabsize columns.
239 * The removed indent can be a mixture of spaces plus at most one tab. */
do_unindent(void)240 void do_unindent(void)
241 {
242 linestruct *top, *bot, *line;
243
244 /* Use either all the marked lines or just the current line. */
245 get_range(&top, &bot);
246
247 /* Skip any leading lines that cannot be unindented. */
248 while (top != bot->next && length_of_white(top->data) == 0)
249 top = top->next;
250
251 /* If none of the lines can be unindented, there is nothing to do. */
252 if (top == bot->next)
253 return;
254
255 add_undo(UNINDENT, NULL);
256
257 /* Go through each of the lines, removing their leading indent where
258 * possible, and saving the removed whitespace in the undo item. */
259 for (line = top; line != bot->next; line = line->next) {
260 size_t indent_len = length_of_white(line->data);
261 char *indentation = measured_copy(line->data, indent_len);
262
263 unindent_a_line(line, indent_len);
264 update_multiline_undo(line->lineno, indentation);
265
266 free(indentation);
267 }
268
269 set_modified();
270 ensure_firstcolumn_is_aligned();
271 refresh_needed = TRUE;
272 shift_held = TRUE;
273 }
274
275 /* Perform an undo or redo for an indent or unindent action. */
handle_indent_action(undostruct * u,bool undoing,bool add_indent)276 void handle_indent_action(undostruct *u, bool undoing, bool add_indent)
277 {
278 groupstruct *group = u->grouping;
279 linestruct *line = line_from_number(group->top_line);
280
281 /* When redoing, reposition the cursor and let the indenter adjust it. */
282 if (!undoing)
283 goto_line_posx(u->head_lineno, u->head_x);
284
285 /* For each line in the group, add or remove the individual indent. */
286 while (line != NULL && line->lineno <= group->bottom_line) {
287 char *blanks = group->indentations[line->lineno - group->top_line];
288
289 if (undoing ^ add_indent)
290 indent_a_line(line, blanks);
291 else
292 unindent_a_line(line, strlen(blanks));
293
294 line = line->next;
295 }
296
297 /* When undoing, reposition the cursor to the recorded location. */
298 if (undoing)
299 goto_line_posx(u->head_lineno, u->head_x);
300
301 refresh_needed = TRUE;
302 }
303 #endif /* !NANO_TINY */
304
305 #ifdef ENABLE_COMMENT
306 /* Test whether the given line can be uncommented, or add or remove a comment,
307 * depending on action. Return TRUE if the line is uncommentable, or when
308 * anything was added or removed; FALSE otherwise. */
comment_line(undo_type action,linestruct * line,const char * comment_seq)309 bool comment_line(undo_type action, linestruct *line, const char *comment_seq)
310 {
311 size_t comment_seq_len = strlen(comment_seq);
312 const char *post_seq = strchr(comment_seq, '|');
313 /* The postfix, if this is a bracketing type comment sequence. */
314 size_t pre_len = post_seq ? post_seq++ - comment_seq : comment_seq_len;
315 /* Length of prefix. */
316 size_t post_len = post_seq ? comment_seq_len - pre_len - 1 : 0;
317 /* Length of postfix. */
318 size_t line_len = strlen(line->data);
319
320 if (!ISSET(NO_NEWLINES) && line == openfile->filebot)
321 return FALSE;
322
323 if (action == COMMENT) {
324 /* Make room for the comment sequence(s), move the text right and
325 * copy them in. */
326 line->data = nrealloc(line->data, line_len + pre_len + post_len + 1);
327 memmove(line->data + pre_len, line->data, line_len + 1);
328 memmove(line->data, comment_seq, pre_len);
329 if (post_len > 0)
330 memmove(line->data + pre_len + line_len, post_seq, post_len + 1);
331
332 openfile->totsize += pre_len + post_len;
333
334 /* If needed, adjust the position of the mark and of the cursor. */
335 if (line == openfile->mark && openfile->mark_x > 0)
336 openfile->mark_x += pre_len;
337 if (line == openfile->current && openfile->current_x > 0) {
338 openfile->current_x += pre_len;
339 openfile->placewewant = xplustabs();
340 }
341
342 return TRUE;
343 }
344
345 /* If the line is commented, report it as uncommentable, or uncomment it. */
346 if (strncmp(line->data, comment_seq, pre_len) == 0 && (post_len == 0 ||
347 strcmp(line->data + line_len - post_len, post_seq) == 0)) {
348
349 if (action == PREFLIGHT)
350 return TRUE;
351
352 /* Erase the comment prefix by moving the non-comment part. */
353 memmove(line->data, line->data + pre_len, line_len - pre_len);
354 /* Truncate the postfix if there was one. */
355 line->data[line_len - pre_len - post_len] = '\0';
356
357 openfile->totsize -= pre_len + post_len;
358
359 /* Adjust the positions of mark and cursor, when needed. */
360 compensate_leftward(line, pre_len);
361
362 return TRUE;
363 }
364
365 return FALSE;
366 }
367
368 /* Comment or uncomment the current line or the marked lines. */
do_comment(void)369 void do_comment(void)
370 {
371 const char *comment_seq = GENERAL_COMMENT_CHARACTER;
372 undo_type action = UNCOMMENT;
373 linestruct *top, *bot, *line;
374 bool empty, all_empty = TRUE;
375
376 #ifdef ENABLE_COLOR
377 if (openfile->syntax)
378 comment_seq = openfile->syntax->comment;
379
380 if (*comment_seq == '\0') {
381 statusline(AHEM, _("Commenting is not supported for this file type"));
382 return;
383 }
384 #endif
385
386 /* Determine which lines to work on. */
387 get_range(&top, &bot);
388
389 /* If only the magic line is selected, don't do anything. */
390 if (top == bot && bot == openfile->filebot && !ISSET(NO_NEWLINES)) {
391 statusline(AHEM, _("Cannot comment past end of file"));
392 return;
393 }
394
395 /* Figure out whether to comment or uncomment the selected line or lines. */
396 for (line = top; line != bot->next; line = line->next) {
397 empty = white_string(line->data);
398
399 /* If this line is not blank and not commented, we comment all. */
400 if (!empty && !comment_line(PREFLIGHT, line, comment_seq)) {
401 action = COMMENT;
402 break;
403 }
404 all_empty = all_empty && empty;
405 }
406
407 /* If all selected lines are blank, we comment them. */
408 action = all_empty ? COMMENT : action;
409
410 add_undo(action, NULL);
411
412 /* Store the comment sequence used for the operation, because it could
413 * change when the file name changes; we need to know what it was. */
414 openfile->current_undo->strdata = copy_of(comment_seq);
415
416 /* Comment/uncomment each of the selected lines when possible, and
417 * store undo data when a line changed. */
418 for (line = top; line != bot->next; line = line->next)
419 if (comment_line(action, line, comment_seq))
420 update_multiline_undo(line->lineno, "");
421
422 set_modified();
423 ensure_firstcolumn_is_aligned();
424 refresh_needed = TRUE;
425 shift_held = TRUE;
426 }
427
428 /* Perform an undo or redo for a comment or uncomment action. */
handle_comment_action(undostruct * u,bool undoing,bool add_comment)429 void handle_comment_action(undostruct *u, bool undoing, bool add_comment)
430 {
431 groupstruct *group = u->grouping;
432
433 /* When redoing, reposition the cursor and let the commenter adjust it. */
434 if (!undoing)
435 goto_line_posx(u->head_lineno, u->head_x);
436
437 while (group) {
438 linestruct *line = line_from_number(group->top_line);
439
440 while (line != NULL && line->lineno <= group->bottom_line) {
441 comment_line(undoing ^ add_comment ?
442 COMMENT : UNCOMMENT, line, u->strdata);
443 line = line->next;
444 }
445
446 group = group->next;
447 }
448
449 /* When undoing, reposition the cursor to the recorded location. */
450 if (undoing)
451 goto_line_posx(u->head_lineno, u->head_x);
452
453 refresh_needed = TRUE;
454 }
455 #endif /* ENABLE_COMMENT */
456
457 #ifndef NANO_TINY
458 #define redo_paste undo_cut
459 #define undo_paste redo_cut
460
461 /* Undo a cut, or redo a paste. */
undo_cut(undostruct * u)462 void undo_cut(undostruct *u)
463 {
464 goto_line_posx(u->head_lineno, (u->xflags & WAS_WHOLE_LINE) ? 0 : u->head_x);
465
466 /* Clear an inherited anchor but not a user-placed one. */
467 if (!(u->xflags & HAD_ANCHOR_AT_START))
468 openfile->current->has_anchor = FALSE;
469
470 if (u->cutbuffer)
471 copy_from_buffer(u->cutbuffer);
472
473 /* If originally the last line was cut too, remove an extra magic line. */
474 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
475 openfile->filebot != openfile->current &&
476 openfile->filebot->prev->data[0] == '\0')
477 remove_magicline();
478
479 if (u->xflags & CURSOR_WAS_AT_HEAD)
480 goto_line_posx(u->head_lineno, u->head_x);
481 }
482
483 /* Redo a cut, or undo a paste. */
redo_cut(undostruct * u)484 void redo_cut(undostruct *u)
485 {
486 linestruct *oldcutbuffer = cutbuffer;
487
488 cutbuffer = NULL;
489
490 openfile->mark = line_from_number(u->head_lineno);
491 openfile->mark_x = (u->xflags & WAS_WHOLE_LINE) ? 0 : u->head_x;
492
493 goto_line_posx(u->tail_lineno, u->tail_x);
494
495 do_snip(TRUE, FALSE, u->type == ZAP);
496
497 free_lines(cutbuffer);
498 cutbuffer = oldcutbuffer;
499 }
500
501 /* Undo the last thing(s) we did. */
do_undo(void)502 void do_undo(void)
503 {
504 undostruct *u = openfile->current_undo;
505 linestruct *line = NULL, *intruder;
506 linestruct *oldcutbuffer;
507 char *data, *undidmsg = NULL;
508 size_t original_x, regain_from_x;
509
510 if (u == NULL) {
511 statusline(AHEM, _("Nothing to undo"));
512 return;
513 }
514
515 if (u->type <= REPLACE)
516 line = line_from_number(u->tail_lineno);
517
518 switch (u->type) {
519 case ADD:
520 /* TRANSLATORS: The next thirteen strings describe actions
521 * that are undone or redone. They are all nouns, not verbs. */
522 undidmsg = _("addition");
523 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
524 remove_magicline();
525 memmove(line->data + u->head_x, line->data + u->head_x + strlen(u->strdata),
526 strlen(line->data + u->head_x) - strlen(u->strdata) + 1);
527 goto_line_posx(u->head_lineno, u->head_x);
528 break;
529 case ENTER:
530 undidmsg = _("line break");
531 /* An <Enter> at the end of leading whitespace while autoindenting has
532 * deleted the whitespace, and stored an x position of zero. In that
533 * case, adjust the positions to return to and to scoop data from. */
534 original_x = (u->head_x == 0) ? u->tail_x : u->head_x;
535 regain_from_x = (u->head_x == 0) ? 0 : u->tail_x;
536 line->data = nrealloc(line->data, strlen(line->data) +
537 strlen(&u->strdata[regain_from_x]) + 1);
538 strcat(line->data, &u->strdata[regain_from_x]);
539 line->has_anchor |= line->next->has_anchor;
540 unlink_node(line->next);
541 renumber_from(line);
542 goto_line_posx(u->head_lineno, original_x);
543 break;
544 case BACK:
545 case DEL:
546 undidmsg = _("deletion");
547 data = nmalloc(strlen(line->data) + strlen(u->strdata) + 1);
548 strncpy(data, line->data, u->head_x);
549 strcpy(&data[u->head_x], u->strdata);
550 strcpy(&data[u->head_x + strlen(u->strdata)], &line->data[u->head_x]);
551 free(line->data);
552 line->data = data;
553 goto_line_posx(u->tail_lineno, u->tail_x);
554 break;
555 case JOIN:
556 undidmsg = _("line join");
557 /* When the join was done by a Backspace at the tail of the file,
558 * and the nonewlines flag isn't set, do not re-add a newline that
559 * wasn't actually deleted; just position the cursor. */
560 if ((u->xflags & WAS_BACKSPACE_AT_EOF) && !ISSET(NO_NEWLINES)) {
561 goto_line_posx(openfile->filebot->lineno, 0);
562 break;
563 }
564 line->data[u->tail_x] = '\0';
565 intruder = make_new_node(line);
566 intruder->data = copy_of(u->strdata);
567 splice_node(line, intruder);
568 renumber_from(intruder);
569 goto_line_posx(u->head_lineno, u->head_x);
570 break;
571 case REPLACE:
572 undidmsg = _("replacement");
573 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
574 remove_magicline();
575 data = u->strdata;
576 u->strdata = line->data;
577 line->data = data;
578 goto_line_posx(u->head_lineno, u->head_x);
579 break;
580 #ifdef ENABLE_WRAPPING
581 case SPLIT_BEGIN:
582 undidmsg = _("addition");
583 break;
584 case SPLIT_END:
585 openfile->current_undo = openfile->current_undo->next;
586 while (openfile->current_undo->type != SPLIT_BEGIN)
587 do_undo();
588 u = openfile->current_undo;
589 break;
590 #endif
591 case ZAP:
592 undidmsg = _("erasure");
593 undo_cut(u);
594 break;
595 case CUT_TO_EOF:
596 case CUT:
597 /* TRANSLATORS: Remember: these are nouns, NOT verbs. */
598 undidmsg = _("cut");
599 undo_cut(u);
600 break;
601 case PASTE:
602 undidmsg = _("paste");
603 undo_paste(u);
604 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
605 openfile->filebot != openfile->current)
606 remove_magicline();
607 break;
608 case INSERT:
609 undidmsg = _("insertion");
610 oldcutbuffer = cutbuffer;
611 cutbuffer = NULL;
612 goto_line_posx(u->head_lineno, u->head_x);
613 openfile->mark = line_from_number(u->tail_lineno);
614 openfile->mark_x = u->tail_x;
615 cut_marked_region();
616 u->cutbuffer = cutbuffer;
617 cutbuffer = oldcutbuffer;
618 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
619 openfile->filebot != openfile->current)
620 remove_magicline();
621 break;
622 case COUPLE_BEGIN:
623 undidmsg = u->strdata;
624 goto_line_posx(u->head_lineno, u->head_x);
625 openfile->current_y = u->tail_lineno;
626 adjust_viewport(STATIONARY);
627 break;
628 case COUPLE_END:
629 /* Remember the row of the cursor for a possible redo. */
630 openfile->current_undo->head_lineno = openfile->current_y;
631 openfile->current_undo = openfile->current_undo->next;
632 do_undo();
633 do_undo();
634 do_undo();
635 return;
636 case INDENT:
637 handle_indent_action(u, TRUE, TRUE);
638 undidmsg = _("indent");
639 break;
640 case UNINDENT:
641 handle_indent_action(u, TRUE, FALSE);
642 undidmsg = _("unindent");
643 break;
644 #ifdef ENABLE_COMMENT
645 case COMMENT:
646 handle_comment_action(u, TRUE, TRUE);
647 undidmsg = _("comment");
648 break;
649 case UNCOMMENT:
650 handle_comment_action(u, TRUE, FALSE);
651 undidmsg = _("uncomment");
652 break;
653 #endif
654 default:
655 break;
656 }
657
658 if (undidmsg && !pletion_line)
659 statusline(HUSH, _("Undid %s"), undidmsg);
660
661 openfile->current_undo = openfile->current_undo->next;
662 openfile->last_action = OTHER;
663 openfile->mark = NULL;
664 openfile->placewewant = xplustabs();
665
666 openfile->totsize = u->wassize;
667
668 /* When at the point where the buffer was last saved, unset "Modified". */
669 if (openfile->current_undo == openfile->last_saved) {
670 openfile->modified = FALSE;
671 titlebar(NULL);
672 } else
673 set_modified();
674 }
675
676 /* Redo the last thing(s) we undid. */
do_redo(void)677 void do_redo(void)
678 {
679 linestruct *line = NULL, *intruder;
680 char *data, *redidmsg = NULL;
681 bool suppress_modification = FALSE;
682 undostruct *u = openfile->undotop;
683
684 if (u == NULL || u == openfile->current_undo) {
685 statusline(AHEM, _("Nothing to redo"));
686 return;
687 }
688
689 /* Find the item before the current one in the undo stack. */
690 while (u->next != openfile->current_undo)
691 u = u->next;
692
693 if (u->type <= REPLACE)
694 line = line_from_number(u->tail_lineno);
695
696 switch (u->type) {
697 case ADD:
698 redidmsg = _("addition");
699 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
700 new_magicline();
701 data = nmalloc(strlen(line->data) + strlen(u->strdata) + 1);
702 strncpy(data, line->data, u->head_x);
703 strcpy(&data[u->head_x], u->strdata);
704 strcpy(&data[u->head_x + strlen(u->strdata)], &line->data[u->head_x]);
705 free(line->data);
706 line->data = data;
707 goto_line_posx(u->tail_lineno, u->tail_x);
708 break;
709 case ENTER:
710 redidmsg = _("line break");
711 line->data[u->head_x] = '\0';
712 intruder = make_new_node(line);
713 intruder->data = copy_of(u->strdata);
714 splice_node(line, intruder);
715 renumber_from(intruder);
716 goto_line_posx(u->head_lineno + 1, u->tail_x);
717 break;
718 case BACK:
719 case DEL:
720 redidmsg = _("deletion");
721 memmove(line->data + u->head_x, line->data + u->head_x + strlen(u->strdata),
722 strlen(line->data + u->head_x) - strlen(u->strdata) + 1);
723 goto_line_posx(u->head_lineno, u->head_x);
724 break;
725 case JOIN:
726 redidmsg = _("line join");
727 /* When the join was done by a Backspace at the tail of the file,
728 * and the nonewlines flag isn't set, do not join anything, as
729 * nothing was actually deleted; just position the cursor. */
730 if ((u->xflags & WAS_BACKSPACE_AT_EOF) && !ISSET(NO_NEWLINES)) {
731 goto_line_posx(u->tail_lineno, u->tail_x);
732 break;
733 }
734 line->data = nrealloc(line->data, strlen(line->data) + strlen(u->strdata) + 1);
735 strcat(line->data, u->strdata);
736 unlink_node(line->next);
737 renumber_from(line);
738 goto_line_posx(u->tail_lineno, u->tail_x);
739 break;
740 case REPLACE:
741 redidmsg = _("replacement");
742 if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
743 new_magicline();
744 data = u->strdata;
745 u->strdata = line->data;
746 line->data = data;
747 goto_line_posx(u->head_lineno, u->head_x);
748 break;
749 #ifdef ENABLE_WRAPPING
750 case SPLIT_BEGIN:
751 openfile->current_undo = u;
752 while (openfile->current_undo->type != SPLIT_END)
753 do_redo();
754 u = openfile->current_undo;
755 goto_line_posx(u->head_lineno, u->head_x);
756 break;
757 case SPLIT_END:
758 redidmsg = _("addition");
759 break;
760 #endif
761 case ZAP:
762 redidmsg = _("erasure");
763 redo_cut(u);
764 break;
765 case CUT_TO_EOF:
766 case CUT:
767 redidmsg = _("cut");
768 redo_cut(u);
769 break;
770 case PASTE:
771 redidmsg = _("paste");
772 redo_paste(u);
773 break;
774 case INSERT:
775 redidmsg = _("insertion");
776 goto_line_posx(u->head_lineno, u->head_x);
777 if (u->cutbuffer)
778 copy_from_buffer(u->cutbuffer);
779 else
780 suppress_modification = TRUE;
781 free_lines(u->cutbuffer);
782 u->cutbuffer = NULL;
783 break;
784 case COUPLE_BEGIN:
785 openfile->current_undo = u;
786 do_redo();
787 do_redo();
788 do_redo();
789 return;
790 case COUPLE_END:
791 redidmsg = u->strdata;
792 goto_line_posx(u->tail_lineno, u->tail_x);
793 openfile->current_y = u->head_lineno;
794 adjust_viewport(STATIONARY);
795 break;
796 case INDENT:
797 handle_indent_action(u, FALSE, TRUE);
798 redidmsg = _("indent");
799 break;
800 case UNINDENT:
801 handle_indent_action(u, FALSE, FALSE);
802 redidmsg = _("unindent");
803 break;
804 #ifdef ENABLE_COMMENT
805 case COMMENT:
806 handle_comment_action(u, FALSE, TRUE);
807 redidmsg = _("comment");
808 break;
809 case UNCOMMENT:
810 handle_comment_action(u, FALSE, FALSE);
811 redidmsg = _("uncomment");
812 break;
813 #endif
814 default:
815 break;
816 }
817
818 if (redidmsg)
819 statusline(HUSH, _("Redid %s"), redidmsg);
820
821 openfile->current_undo = u;
822 openfile->last_action = OTHER;
823 openfile->mark = NULL;
824 openfile->placewewant = xplustabs();
825
826 openfile->totsize = u->newsize;
827
828 /* When at the point where the buffer was last saved, unset "Modified". */
829 if (openfile->current_undo == openfile->last_saved) {
830 openfile->modified = FALSE;
831 titlebar(NULL);
832 } else if (!suppress_modification)
833 set_modified();
834 }
835 #endif /* !NANO_TINY */
836
837 /* Break the current line at the cursor position. */
do_enter(void)838 void do_enter(void)
839 {
840 linestruct *newnode = make_new_node(openfile->current);
841 size_t extra = 0;
842 #ifndef NANO_TINY
843 linestruct *sampleline = openfile->current;
844 bool allblanks = FALSE;
845
846 if (ISSET(AUTOINDENT)) {
847 #ifdef ENABLE_JUSTIFY
848 /* When doing automatic long-line wrapping and the next line is
849 * in this same paragraph, use its indentation as the model. */
850 if (ISSET(BREAK_LONG_LINES) && sampleline->next != NULL &&
851 inpar(sampleline->next) && !begpar(sampleline->next, 0))
852 sampleline = sampleline->next;
853 #endif
854 extra = indent_length(sampleline->data);
855
856 /* When breaking in the indentation, limit the automatic one. */
857 if (extra > openfile->current_x)
858 extra = openfile->current_x;
859 else if (extra == openfile->current_x)
860 allblanks = TRUE;
861 }
862 #endif /* NANO_TINY */
863 newnode->data = nmalloc(strlen(openfile->current->data +
864 openfile->current_x) + extra + 1);
865 strcpy(&newnode->data[extra], openfile->current->data +
866 openfile->current_x);
867 #ifndef NANO_TINY
868 if (ISSET(AUTOINDENT)) {
869 /* Copy the whitespace from the sample line to the new one. */
870 strncpy(newnode->data, sampleline->data, extra);
871 /* If there were only blanks before the cursor, trim them. */
872 if (allblanks)
873 openfile->current_x = 0;
874 }
875 #endif
876
877 /* Make the current line end at the cursor position. */
878 openfile->current->data[openfile->current_x] = '\0';
879
880 #ifndef NANO_TINY
881 add_undo(ENTER, NULL);
882
883 /* Adjust the mark if it was on the current line after the cursor. */
884 if (openfile->mark == openfile->current &&
885 openfile->mark_x > openfile->current_x) {
886 openfile->mark = newnode;
887 openfile->mark_x += extra - openfile->current_x;
888 }
889 #endif
890
891 /* Insert the newly created line after the current one and renumber. */
892 splice_node(openfile->current, newnode);
893 renumber_from(newnode);
894
895 /* Put the cursor on the new line, after any automatic whitespace. */
896 openfile->current = newnode;
897 openfile->current_x = extra;
898 openfile->placewewant = xplustabs();
899
900 openfile->totsize++;
901 set_modified();
902
903 #ifndef NANO_TINY
904 if (ISSET(AUTOINDENT) && !allblanks)
905 openfile->totsize += extra;
906 update_undo(ENTER);
907 #endif
908
909 refresh_needed = TRUE;
910 focusing = FALSE;
911 }
912
913 #ifndef NANO_TINY
914 /* Discard undo items that are newer than the given one, or all if NULL. */
discard_until(const undostruct * thisitem)915 void discard_until(const undostruct *thisitem)
916 {
917 undostruct *dropit = openfile->undotop;
918 groupstruct *group;
919
920 while (dropit != NULL && dropit != thisitem) {
921 openfile->undotop = dropit->next;
922 free(dropit->strdata);
923 free_lines(dropit->cutbuffer);
924 group = dropit->grouping;
925 while (group != NULL) {
926 groupstruct *next = group->next;
927 free_chararray(group->indentations,
928 group->bottom_line - group->top_line + 1);
929 free(group);
930 group = next;
931 }
932 free(dropit);
933 dropit = openfile->undotop;
934 }
935
936 /* Adjust the pointer to the top of the undo stack. */
937 openfile->current_undo = (undostruct *)thisitem;
938
939 /* Prevent a chain of editing actions from continuing. */
940 openfile->last_action = OTHER;
941 }
942
943 /* Add a new undo item of the given type to the top of the current pile. */
add_undo(undo_type action,const char * message)944 void add_undo(undo_type action, const char *message)
945 {
946 undostruct *u = nmalloc(sizeof(undostruct));
947 linestruct *thisline = openfile->current;
948
949 /* Initialize the newly allocated undo item. */
950 u->type = action;
951 u->strdata = NULL;
952 u->cutbuffer = NULL;
953 u->head_lineno = thisline->lineno;
954 u->head_x = openfile->current_x;
955 u->tail_lineno = thisline->lineno;
956 u->tail_x = openfile->current_x;
957 u->wassize = openfile->totsize;
958 u->newsize = openfile->totsize;
959 u->grouping = NULL;
960 u->xflags = 0;
961
962 /* Blow away any undone items. */
963 discard_until(openfile->current_undo);
964
965 #ifdef ENABLE_WRAPPING
966 /* If some action caused automatic long-line wrapping, insert the
967 * SPLIT_BEGIN item underneath that action's undo item. Otherwise,
968 * just add the new item to the top of the undo stack. */
969 if (u->type == SPLIT_BEGIN) {
970 action = openfile->undotop->type;
971 u->wassize = openfile->undotop->wassize;
972 u->next = openfile->undotop->next;
973 openfile->undotop->next = u;
974 } else
975 #endif
976 {
977 u->next = openfile->undotop;
978 openfile->undotop = u;
979 openfile->current_undo = u;
980 }
981
982 /* Record the info needed to be able to undo each possible action. */
983 switch (u->type) {
984 case ADD:
985 /* If a new magic line will be added, an undo should remove it. */
986 if (thisline == openfile->filebot)
987 u->xflags |= INCLUDED_LAST_LINE;
988 break;
989 case ENTER:
990 break;
991 case BACK:
992 /* If the next line is the magic line, don't ever undo this
993 * backspace, as it won't actually have deleted anything. */
994 if (thisline->next == openfile->filebot && thisline->data[0] != '\0')
995 u->xflags |= WAS_BACKSPACE_AT_EOF;
996 /* Fall-through. */
997 case DEL:
998 /* When not at the end of a line, store the deleted character;
999 * otherwise, morph the undo item into a line join. */
1000 if (thisline->data[openfile->current_x] != '\0') {
1001 int charlen = char_length(thisline->data + u->head_x);
1002
1003 u->strdata = measured_copy(thisline->data + u->head_x, charlen);
1004 if (u->type == BACK)
1005 u->tail_x += charlen;
1006 break;
1007 }
1008 action = JOIN;
1009 if (thisline->next != NULL) {
1010 if (u->type == BACK) {
1011 u->head_lineno = thisline->next->lineno;
1012 u->head_x = 0;
1013 }
1014 u->strdata = copy_of(thisline->next->data);
1015 }
1016 u->type = JOIN;
1017 break;
1018 case REPLACE:
1019 u->strdata = copy_of(thisline->data);
1020 if (thisline == openfile->filebot && answer[0] != '\0')
1021 u->xflags |= INCLUDED_LAST_LINE;
1022 break;
1023 #ifdef ENABLE_WRAPPING
1024 case SPLIT_BEGIN:
1025 case SPLIT_END:
1026 break;
1027 #endif
1028 case CUT_TO_EOF:
1029 u->xflags |= (INCLUDED_LAST_LINE | CURSOR_WAS_AT_HEAD);
1030 if (openfile->current->has_anchor)
1031 u->xflags |= HAD_ANCHOR_AT_START;
1032 break;
1033 case ZAP:
1034 case CUT:
1035 if (openfile->mark) {
1036 if (mark_is_before_cursor()){
1037 u->head_lineno = openfile->mark->lineno;
1038 u->head_x = openfile->mark_x;
1039 u->xflags |= MARK_WAS_SET;
1040 } else {
1041 u->tail_lineno = openfile->mark->lineno;
1042 u->tail_x = openfile->mark_x;
1043 u->xflags |= (MARK_WAS_SET | CURSOR_WAS_AT_HEAD);
1044 }
1045 if (u->tail_lineno == openfile->filebot->lineno)
1046 u->xflags |= INCLUDED_LAST_LINE;
1047 } else if (!ISSET(CUT_FROM_CURSOR)) {
1048 /* The entire line is being cut regardless of the cursor position. */
1049 u->xflags |= (WAS_WHOLE_LINE | CURSOR_WAS_AT_HEAD);
1050 u->tail_x = 0;
1051 } else
1052 u->xflags |= CURSOR_WAS_AT_HEAD;
1053 if ((openfile->mark && mark_is_before_cursor() && openfile->mark->has_anchor) ||
1054 ((!openfile->mark || !mark_is_before_cursor()) && openfile->current->has_anchor))
1055 u->xflags |= HAD_ANCHOR_AT_START;
1056 break;
1057 case PASTE:
1058 u->cutbuffer = copy_buffer(cutbuffer);
1059 /* Fall-through. */
1060 case INSERT:
1061 if (thisline == openfile->filebot)
1062 u->xflags |= INCLUDED_LAST_LINE;
1063 break;
1064 case COUPLE_BEGIN:
1065 u->tail_lineno = openfile->current_y;
1066 /* Fall-through. */
1067 case COUPLE_END:
1068 u->strdata = copy_of(_(message));
1069 break;
1070 case INDENT:
1071 case UNINDENT:
1072 #ifdef ENABLE_COMMENT
1073 case COMMENT:
1074 case UNCOMMENT:
1075 #endif
1076 break;
1077 default:
1078 die("Bad undo type -- please report a bug\n");
1079 }
1080
1081 openfile->last_action = action;
1082 }
1083
1084 /* Update a multiline undo item. This should be called once for each line
1085 * affected by a multiple-line-altering feature. The indentation that is
1086 * added or removed is saved separately for each line in the undo item. */
update_multiline_undo(ssize_t lineno,char * indentation)1087 void update_multiline_undo(ssize_t lineno, char *indentation)
1088 {
1089 undostruct *u = openfile->current_undo;
1090
1091 /* If there already is a group and the current line is contiguous with it,
1092 * extend the group; otherwise, create a new group. */
1093 if (u->grouping && u->grouping->bottom_line + 1 == lineno) {
1094 size_t number_of_lines = lineno - u->grouping->top_line + 1;
1095
1096 u->grouping->bottom_line = lineno;
1097
1098 u->grouping->indentations = nrealloc(u->grouping->indentations,
1099 number_of_lines * sizeof(char *));
1100 u->grouping->indentations[number_of_lines - 1] = copy_of(indentation);
1101 } else {
1102 groupstruct *born = nmalloc(sizeof(groupstruct));
1103
1104 born->top_line = lineno;
1105 born->bottom_line = lineno;
1106
1107 born->indentations = nmalloc(sizeof(char *));
1108 born->indentations[0] = copy_of(indentation);
1109
1110 born->next = u->grouping;
1111 u->grouping = born;
1112 }
1113
1114 /* Store the file size after the change, to be used when redoing. */
1115 u->newsize = openfile->totsize;
1116 }
1117
1118 /* Update an undo item with (among other things) the file size and
1119 * cursor position after the given action. */
update_undo(undo_type action)1120 void update_undo(undo_type action)
1121 {
1122 undostruct *u = openfile->undotop;
1123 size_t datalen, newlen;
1124 char *textposition;
1125 int charlen;
1126
1127 if (u->type != action)
1128 die("Mismatching undo type -- please report a bug\n");
1129
1130 u->newsize = openfile->totsize;
1131
1132 switch (u->type) {
1133 case ADD:
1134 newlen = openfile->current_x - u->head_x;
1135 u->strdata = nrealloc(u->strdata, newlen + 1);
1136 strncpy(u->strdata, openfile->current->data + u->head_x, newlen);
1137 u->strdata[newlen] = '\0';
1138 u->tail_x = openfile->current_x;
1139 break;
1140 case ENTER:
1141 u->strdata = copy_of(openfile->current->data);
1142 u->tail_x = openfile->current_x;
1143 break;
1144 case BACK:
1145 case DEL:
1146 textposition = openfile->current->data + openfile->current_x;
1147 charlen = char_length(textposition);
1148 datalen = strlen(u->strdata);
1149 if (openfile->current_x == u->head_x) {
1150 /* They deleted more: add removed character after earlier stuff. */
1151 u->strdata = nrealloc(u->strdata, datalen + charlen + 1);
1152 strncpy(u->strdata + datalen, textposition, charlen);
1153 u->strdata[datalen + charlen] = '\0';
1154 u->tail_x = openfile->current_x;
1155 } else if (openfile->current_x == u->head_x - charlen) {
1156 /* They backspaced further: add removed character before earlier. */
1157 u->strdata = nrealloc(u->strdata, datalen + charlen + 1);
1158 memmove(u->strdata + charlen, u->strdata, datalen + 1);
1159 strncpy(u->strdata, textposition, charlen);
1160 u->head_x = openfile->current_x;
1161 } else
1162 /* They deleted *elsewhere* on the line: start a new undo item. */
1163 add_undo(u->type, NULL);
1164 break;
1165 case REPLACE:
1166 break;
1167 #ifdef ENABLE_WRAPPING
1168 case SPLIT_BEGIN:
1169 case SPLIT_END:
1170 break;
1171 #endif
1172 case ZAP:
1173 case CUT_TO_EOF:
1174 case CUT:
1175 if (u->type == ZAP)
1176 u->cutbuffer = cutbuffer;
1177 else if (cutbuffer != NULL) {
1178 free_lines(u->cutbuffer);
1179 u->cutbuffer = copy_buffer(cutbuffer);
1180 }
1181 if (!(u->xflags & MARK_WAS_SET)) {
1182 linestruct *bottomline = u->cutbuffer;
1183 size_t count = 0;
1184
1185 /* Find the end of the cut for the undo/redo, using our copy. */
1186 while (bottomline->next != NULL) {
1187 bottomline = bottomline->next;
1188 count++;
1189 }
1190 u->tail_lineno = u->head_lineno + count;
1191 if (ISSET(CUT_FROM_CURSOR) || u->type == CUT_TO_EOF) {
1192 u->tail_x = strlen(bottomline->data);
1193 if (count == 0)
1194 u->tail_x += u->head_x;
1195 } else if (openfile->current == openfile->filebot && ISSET(NO_NEWLINES))
1196 u->tail_x = strlen(bottomline->data);
1197 }
1198 break;
1199 case COUPLE_BEGIN:
1200 break;
1201 case COUPLE_END:
1202 case PASTE:
1203 case INSERT:
1204 u->tail_lineno = openfile->current->lineno;
1205 u->tail_x = openfile->current_x;
1206 break;
1207 default:
1208 die("Bad undo type -- please report a bug\n");
1209 }
1210 }
1211 #endif /* !NANO_TINY */
1212
1213 #ifdef ENABLE_WRAPPING
1214 /* When the current line is overlong, hard-wrap it at the furthest possible
1215 * whitespace character, and (if possible) prepend the remainder of the line
1216 * to the next line. Return TRUE if wrapping occurred, and FALSE otherwise. */
do_wrap(void)1217 bool do_wrap(void)
1218 {
1219 linestruct *line = openfile->current;
1220 /* The line to be wrapped, if needed and possible. */
1221 size_t line_len = strlen(line->data);
1222 /* The length of this line. */
1223 #ifdef ENABLE_JUSTIFY
1224 size_t quot_len = quote_length(line->data);
1225 /* The length of the quoting part of this line. */
1226 size_t lead_len = quot_len + indent_length(line->data + quot_len);
1227 /* The length of the quoting part plus subsequent whitespace. */
1228 #else
1229 size_t lead_len = indent_length(line->data);
1230 #endif
1231 size_t cursor_x = openfile->current_x;
1232 /* The current cursor position, for comparison with the wrap point. */
1233 ssize_t wrap_loc;
1234 /* The position in the line's text where we wrap. */
1235 const char *remainder;
1236 /* The text after the wrap point. */
1237 size_t rest_length;
1238 /* The length of the remainder. */
1239
1240 /* First find the last blank character where we can break the line. */
1241 wrap_loc = break_line(line->data + lead_len,
1242 wrap_at - wideness(line->data, lead_len), FALSE);
1243
1244 /* If no wrapping point was found before end-of-line, we don't wrap. */
1245 if (wrap_loc < 0 || lead_len + wrap_loc == line_len)
1246 return FALSE;
1247
1248 /* Adjust the wrap location to its position in the full line,
1249 * and step forward to the character just after the blank. */
1250 wrap_loc = lead_len + step_right(line->data + lead_len, wrap_loc);
1251
1252 /* When now at end-of-line, no need to wrap. */
1253 if (line->data[wrap_loc] == '\0')
1254 return FALSE;
1255
1256 #ifndef NANO_TINY
1257 add_undo(SPLIT_BEGIN, NULL);
1258 #endif
1259 #ifdef ENABLE_JUSTIFY
1260 bool autowhite = ISSET(AUTOINDENT);
1261
1262 if (quot_len > 0)
1263 UNSET(AUTOINDENT);
1264 #endif
1265
1266 /* The remainder is the text that will be wrapped to the next line. */
1267 remainder = line->data + wrap_loc;
1268 rest_length = line_len - wrap_loc;
1269
1270 /* When prepending and the remainder of this line will not make the next
1271 * line too long, then join the two lines, so that, after the line wrap,
1272 * the remainder will effectively have been prefixed to the next line. */
1273 if (openfile->spillage_line && openfile->spillage_line == line->next &&
1274 rest_length + breadth(line->next->data) <= wrap_at) {
1275 /* Go to the end of this line. */
1276 openfile->current_x = line_len;
1277
1278 /* If the remainder doesn't end in a blank, add a space. */
1279 if (!is_blank_char(remainder + step_left(remainder, rest_length))) {
1280 #ifndef NANO_TINY
1281 add_undo(ADD, NULL);
1282 #endif
1283 line->data = nrealloc(line->data, line_len + 2);
1284 line->data[line_len] = ' ';
1285 line->data[line_len + 1] = '\0';
1286 rest_length++;
1287 openfile->totsize++;
1288 openfile->current_x++;
1289 #ifndef NANO_TINY
1290 update_undo(ADD);
1291 #endif
1292 }
1293
1294 /* Join the next line to this one. */
1295 do_delete();
1296
1297 #ifdef ENABLE_JUSTIFY
1298 /* If the leading part of the current line equals the leading part of
1299 * what was the next line, then strip this second leading part. */
1300 if (strncmp(line->data, line->data + openfile->current_x, lead_len) == 0)
1301 for (size_t i = lead_len; i > 0; i--)
1302 do_delete();
1303 #endif
1304 /* Remove any extra blanks. */
1305 while (is_blank_char(&line->data[openfile->current_x]))
1306 do_delete();
1307 }
1308
1309 /* Go to the wrap location. */
1310 openfile->current_x = wrap_loc;
1311
1312 /* When requested, snip trailing blanks off the wrapped line. */
1313 if (ISSET(TRIM_BLANKS)) {
1314 size_t rear_x = step_left(line->data, wrap_loc);
1315 size_t typed_x = step_left(line->data, cursor_x);
1316
1317 while ((rear_x != typed_x || cursor_x >= wrap_loc) &&
1318 is_blank_char(line->data + rear_x)) {
1319 openfile->current_x = rear_x;
1320 do_delete();
1321 rear_x = step_left(line->data, rear_x);
1322 }
1323 }
1324
1325 /* Now split the line. */
1326 do_enter();
1327
1328 #ifdef ENABLE_JUSTIFY
1329 /* If the original line has quoting, copy it to the spillage line. */
1330 if (quot_len > 0) {
1331 line = line->next;
1332 line_len = strlen(line->data);
1333 line->data = nrealloc(line->data, lead_len + line_len + 1);
1334
1335 memmove(line->data + lead_len, line->data, line_len + 1);
1336 strncpy(line->data, line->prev->data, lead_len);
1337
1338 openfile->current_x += lead_len;
1339 openfile->totsize += lead_len;
1340 #ifndef NANO_TINY
1341 free(openfile->undotop->strdata);
1342 update_undo(ENTER);
1343 #endif
1344 if (autowhite)
1345 SET(AUTOINDENT);
1346 }
1347 #endif
1348
1349 openfile->spillage_line = openfile->current;
1350
1351 if (cursor_x < wrap_loc) {
1352 openfile->current = openfile->current->prev;
1353 openfile->current_x = cursor_x;
1354 } else
1355 openfile->current_x += (cursor_x - wrap_loc);
1356
1357 openfile->placewewant = xplustabs();
1358
1359 #ifndef NANO_TINY
1360 add_undo(SPLIT_END, NULL);
1361 #endif
1362
1363 return TRUE;
1364 }
1365 #endif /* ENABLE_WRAPPING */
1366
1367 #if defined(ENABLE_HELP) || defined(ENABLED_WRAPORJUSTIFY)
1368 /* Find the last blank in the given piece of text such that the display width
1369 * to that point is at most (goal + 1). When there is no such blank, then find
1370 * the first blank. Return the index of the last blank in that group of blanks.
1371 * When snap_at_nl is TRUE, a newline character counts as a blank too. */
break_line(const char * textstart,ssize_t goal,bool snap_at_nl)1372 ssize_t break_line(const char *textstart, ssize_t goal, bool snap_at_nl)
1373 {
1374 const char *lastblank = NULL;
1375 /* The point where the last blank was found, if any. */
1376 const char *pointer = textstart;
1377 /* An iterator through the given line of text. */
1378 size_t column = 0;
1379 /* The column number that corresponds to the position of the pointer. */
1380
1381 /* Skip over leading whitespace, where a line should never be broken. */
1382 while (*pointer != '\0' && is_blank_char(pointer))
1383 pointer += advance_over(pointer, &column);
1384
1385 /* Find the last blank that does not overshoot the target column.
1386 * When treating a help text, do not break in the keystrokes area. */
1387 while (*pointer != '\0' && ((ssize_t)column <= goal)) {
1388 if (is_blank_char(pointer) && (!inhelp || column > 17 || goal < 40))
1389 lastblank = pointer;
1390 #ifdef ENABLE_HELP
1391 else if (snap_at_nl && *pointer == '\n') {
1392 lastblank = pointer;
1393 break;
1394 }
1395 #endif
1396 pointer += advance_over(pointer, &column);
1397 }
1398
1399 /* If the whole line displays shorter than goal, we're done. */
1400 if ((ssize_t)column <= goal)
1401 return (pointer - textstart);
1402
1403 #ifdef ENABLE_HELP
1404 /* When wrapping a help text and no blank was found, force a line break. */
1405 if (snap_at_nl && lastblank == NULL)
1406 return step_left(textstart, pointer - textstart);
1407 #endif
1408
1409 /* If no blank was found within the goal width, seek one after it. */
1410 while (lastblank == NULL) {
1411 if (*pointer == '\0')
1412 return -1;
1413
1414 if (is_blank_char(pointer))
1415 lastblank = pointer;
1416 else
1417 pointer += char_length(pointer);
1418 }
1419
1420 pointer = lastblank + char_length(lastblank);
1421
1422 /* Skip any consecutive blanks after the last blank. */
1423 while (*pointer != '\0' && is_blank_char(pointer)) {
1424 lastblank = pointer;
1425 pointer += char_length(pointer);
1426 }
1427
1428 return (lastblank - textstart);
1429 }
1430 #endif /* ENABLE_HELP || ENABLED_WRAPORJUSTIFY */
1431
1432 #if !defined(NANO_TINY) || defined(ENABLED_WRAPORJUSTIFY)
1433 /* Return the length of the indentation part of the given line. The
1434 * "indentation" of a line is the leading consecutive whitespace. */
indent_length(const char * line)1435 size_t indent_length(const char *line)
1436 {
1437 const char *start = line;
1438
1439 while (*line != '\0' && is_blank_char(line))
1440 line += char_length(line);
1441
1442 return (line - start);
1443 }
1444 #endif
1445
1446 #ifdef ENABLE_JUSTIFY
1447 /* Return the length of the quote part of the given line. The "quote part"
1448 * of a line is the largest initial substring matching the quoting regex. */
quote_length(const char * line)1449 size_t quote_length(const char *line)
1450 {
1451 regmatch_t matches;
1452 int rc = regexec("ereg, line, 1, &matches, 0);
1453
1454 if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
1455 return 0;
1456
1457 return matches.rm_eo;
1458 }
1459
1460 /* The maximum depth of recursion. This must be an even number. */
1461 #define RECURSION_LIMIT 222
1462
1463 /* Return TRUE when the given line is the beginning of a paragraph (BOP). */
begpar(const linestruct * const line,int depth)1464 bool begpar(const linestruct *const line, int depth)
1465 {
1466 size_t quot_len, indent_len, prev_dent_len;
1467
1468 /* If this is the very first line of the buffer, it counts as a BOP
1469 * even when it contains no text. */
1470 if (line == openfile->filetop)
1471 return TRUE;
1472
1473 /* If recursion is going too deep, just say it's not a BOP. */
1474 if (depth > RECURSION_LIMIT)
1475 return FALSE;
1476
1477 quot_len = quote_length(line->data);
1478 indent_len = indent_length(line->data + quot_len);
1479
1480 /* If this line contains no text, it is not a BOP. */
1481 if (line->data[quot_len + indent_len] == '\0')
1482 return FALSE;
1483
1484 /* When requested, treat a line that starts with whitespace as a BOP. */
1485 if (ISSET(BOOKSTYLE) && !ISSET(AUTOINDENT) && is_blank_char(line->data))
1486 return TRUE;
1487
1488 /* If the quote part of the preceding line differs, this is a BOP. */
1489 if (quot_len != quote_length(line->prev->data) ||
1490 strncmp(line->data, line->prev->data, quot_len) != 0)
1491 return TRUE;
1492
1493 prev_dent_len = indent_length(line->prev->data + quot_len);
1494
1495 /* If the preceding line contains no text, this is a BOP. */
1496 if (line->prev->data[quot_len + prev_dent_len] == '\0')
1497 return TRUE;
1498
1499 /* If indentation of this and preceding line are equal, this is not a BOP. */
1500 if (wideness(line->prev->data, quot_len + prev_dent_len) ==
1501 wideness(line->data, quot_len + indent_len))
1502 return FALSE;
1503
1504 /* Otherwise, this is a BOP if the preceding line is not. */
1505 return !begpar(line->prev, depth + 1);
1506 }
1507
1508 /* Return TRUE when the given line is part of a paragraph: when it
1509 * contains something more than quoting and leading whitespace. */
inpar(const linestruct * const line)1510 bool inpar(const linestruct *const line)
1511 {
1512 size_t quot_len = quote_length(line->data);
1513 size_t indent_len = indent_length(line->data + quot_len);
1514
1515 return (line->data[quot_len + indent_len] != '\0');
1516 }
1517
1518 /* Find the first occurring paragraph in the forward direction. Return TRUE
1519 * when a paragraph was found, and FALSE otherwise. Furthermore, return the
1520 * first line and the number of lines of the paragraph. */
find_paragraph(linestruct ** firstline,size_t * const linecount)1521 bool find_paragraph(linestruct **firstline, size_t *const linecount)
1522 {
1523 linestruct *line = *firstline;
1524
1525 /* When not currently in a paragraph, move forward to a line that is. */
1526 while (!inpar(line) && line->next != NULL)
1527 line = line->next;
1528
1529 *firstline = line;
1530
1531 /* Move down to the last line of the paragraph (if any). */
1532 do_para_end(&line);
1533
1534 /* When not in a paragraph now, there aren't any paragraphs left. */
1535 if (!inpar(line))
1536 return FALSE;
1537
1538 /* We found a paragraph; determine its number of lines. */
1539 *linecount = line->lineno - (*firstline)->lineno + 1;
1540
1541 return TRUE;
1542 }
1543
1544 /* Concatenate into a single line all the lines of the paragraph that starts at
1545 * *line and consists of 'count' lines, skipping the quoting and indentation on
1546 * all lines after the first. */
concat_paragraph(linestruct * line,size_t count)1547 void concat_paragraph(linestruct *line, size_t count)
1548 {
1549 while (count > 1) {
1550 linestruct *next_line = line->next;
1551 size_t next_line_len = strlen(next_line->data);
1552 size_t next_quot_len = quote_length(next_line->data);
1553 size_t next_lead_len = next_quot_len +
1554 indent_length(next_line->data + next_quot_len);
1555 size_t line_len = strlen(line->data);
1556
1557 /* We're just about to tack the next line onto this one. If
1558 * this line isn't empty, make sure it ends in a space. */
1559 if (line_len > 0 && line->data[line_len - 1] != ' ') {
1560 line->data = nrealloc(line->data, line_len + 2);
1561 line->data[line_len++] = ' ';
1562 line->data[line_len] = '\0';
1563 }
1564
1565 line->data = nrealloc(line->data,
1566 line_len + next_line_len - next_lead_len + 1);
1567 strcat(line->data, next_line->data + next_lead_len);
1568 #ifndef NANO_TINY
1569 line->has_anchor |= next_line->has_anchor;
1570 #endif
1571 unlink_node(next_line);
1572 count--;
1573 }
1574 }
1575
1576 /* Copy a character from one place to another. */
copy_character(char ** from,char ** to)1577 void copy_character(char **from, char **to)
1578 {
1579 int charlen = char_length(*from);
1580
1581 if (*from == *to) {
1582 *from += charlen;
1583 *to += charlen;
1584 } else
1585 while (--charlen >= 0)
1586 *((*to)++) = *((*from)++);
1587 }
1588
1589 /* In the given line, replace any series of blanks with a single space,
1590 * but keep two spaces (if there are two) after any closing punctuation,
1591 * and remove all blanks from the end of the line. Leave the first skip
1592 * number of characters untreated. */
squeeze(linestruct * line,size_t skip)1593 void squeeze(linestruct *line, size_t skip)
1594 {
1595 char *start = line->data + skip;
1596 char *from = start, *to = start;
1597
1598 /* For each character, 1) when a blank, change it to a space, and pass over
1599 * all blanks after it; 2) if it is punctuation, copy it plus a possible
1600 * tailing bracket, and change at most two subsequent blanks to spaces, and
1601 * pass over all blanks after these; 3) leave anything else unchanged. */
1602 while (*from != '\0') {
1603 if (is_blank_char(from)) {
1604 from += char_length(from);
1605 *(to++) = ' ';
1606
1607 while (*from != '\0' && is_blank_char(from))
1608 from += char_length(from);
1609 } else if (mbstrchr(punct, from) != NULL) {
1610 copy_character(&from, &to);
1611
1612 if (*from != '\0' && mbstrchr(brackets, from) != NULL)
1613 copy_character(&from, &to);
1614
1615 if (*from != '\0' && is_blank_char(from)) {
1616 from += char_length(from);
1617 *(to++) = ' ';
1618 }
1619 if (*from != '\0' && is_blank_char(from)) {
1620 from += char_length(from);
1621 *(to++) = ' ';
1622 }
1623
1624 while (*from != '\0' && is_blank_char(from))
1625 from += char_length(from);
1626 } else
1627 copy_character(&from, &to);
1628 }
1629
1630 /* If there are spaces at the end of the line, remove them. */
1631 while (to > start && *(to - 1) == ' ')
1632 to--;
1633
1634 *to = '\0';
1635 }
1636
1637 /* Rewrap the given line (that starts with the given lead string which is of
1638 * the given length), into lines that fit within the target width (wrap_at). */
rewrap_paragraph(linestruct ** line,char * lead_string,size_t lead_len)1639 void rewrap_paragraph(linestruct **line, char *lead_string, size_t lead_len)
1640 {
1641 ssize_t break_pos;
1642 /* The x-coordinate where the current line is to be broken. */
1643
1644 while (breadth((*line)->data) > wrap_at) {
1645 size_t line_len = strlen((*line)->data);
1646
1647 /* Find a point in the line where it can be broken. */
1648 break_pos = break_line((*line)->data + lead_len,
1649 wrap_at - wideness((*line)->data, lead_len), FALSE);
1650
1651 /* If we can't break the line, or don't need to, we're done. */
1652 if (break_pos < 0 || lead_len + break_pos == line_len)
1653 break;
1654
1655 /* Adjust the breaking position for the leading part and
1656 * move it beyond the found whitespace character. */
1657 break_pos += lead_len + 1;
1658
1659 /* Insert a new line after the current one, and copy the leading part
1660 * plus the text after the breaking point into it. */
1661 splice_node(*line, make_new_node(*line));
1662 (*line)->next->data = nmalloc(lead_len + line_len - break_pos + 1);
1663 strncpy((*line)->next->data, lead_string, lead_len);
1664 strcpy((*line)->next->data + lead_len, (*line)->data + break_pos);
1665
1666 /* When requested, snip the one or two trailing spaces. */
1667 if (ISSET(TRIM_BLANKS)) {
1668 while (break_pos > 0 && (*line)->data[break_pos - 1] == ' ')
1669 break_pos--;
1670 }
1671
1672 /* Now actually break the current line, and go to the next. */
1673 (*line)->data[break_pos] = '\0';
1674 *line = (*line)->next;
1675 }
1676
1677 /* When possible, go to the line after the rewrapped paragraph. */
1678 if ((*line)->next != NULL)
1679 *line = (*line)->next;
1680 }
1681
1682 /* Justify the lines of the given paragraph (that starts at *line, and consists
1683 * of 'count' lines) so they all fit within the target width (wrap_at) and have
1684 * their whitespace normalized. */
justify_paragraph(linestruct ** line,size_t count)1685 void justify_paragraph(linestruct **line, size_t count)
1686 {
1687 linestruct *sampleline;
1688 /* The line from which the indentation is copied. */
1689 size_t quot_len;
1690 /* Length of the quote part. */
1691 size_t lead_len;
1692 /* Length of the quote part plus the indentation part. */
1693 char *lead_string;
1694 /* The quote+indent stuff that is copied from the sample line. */
1695
1696 /* The sample line is either the only line or the second line. */
1697 sampleline = (count == 1 ? *line : (*line)->next);
1698
1699 /* Copy the leading part (quoting + indentation) of the sample line. */
1700 quot_len = quote_length(sampleline->data);
1701 lead_len = quot_len + indent_length(sampleline->data + quot_len);
1702 lead_string = measured_copy(sampleline->data, lead_len);
1703
1704 /* Concatenate all lines of the paragraph into a single line. */
1705 concat_paragraph(*line, count);
1706
1707 /* Change all blank characters to spaces and remove excess spaces. */
1708 squeeze(*line, quot_len + indent_length((*line)->data + quot_len));
1709
1710 /* Rewrap the line into multiple lines, accounting for the leading part. */
1711 rewrap_paragraph(line, lead_string, lead_len);
1712
1713 free(lead_string);
1714 }
1715
1716 #define ONE_PARAGRAPH FALSE
1717 #define WHOLE_BUFFER TRUE
1718
1719 /* Justify the current paragraph, or the entire buffer when whole_buffer is
1720 * TRUE. But if the mark is on, justify only the marked text instead. */
justify_text(bool whole_buffer)1721 void justify_text(bool whole_buffer)
1722 {
1723 size_t linecount;
1724 /* The number of lines in the original paragraph. */
1725 linestruct *startline;
1726 /* The line where the paragraph or region starts. */
1727 linestruct *endline;
1728 /* The line where the paragraph or region ends. */
1729 size_t start_x;
1730 /* The x position where the paragraph or region starts. */
1731 size_t end_x;
1732 /* The x position where the paragraph or region ends. */
1733 linestruct *was_cutbuffer = cutbuffer;
1734 /* The old cutbuffer, so we can justify in the current cutbuffer. */
1735 linestruct *jusline;
1736 /* The line that we're justifying in the current cutbuffer. */
1737 #ifndef NANO_TINY
1738 bool before_eol = FALSE;
1739 /* Whether the end of a marked region is before the end of its line. */
1740 char *primary_lead = NULL;
1741 /* The leading part (quoting + indentation) of the first line
1742 * of the paragraph where the marked region begins. */
1743 size_t primary_len = 0;
1744 /* The length (in bytes) of the above first-line leading part. */
1745 char *secondary_lead = NULL;
1746 /* The leading part for lines after the first one. */
1747 size_t secondary_len = 0;
1748 /* The length of that later lead. */
1749
1750 /* TRANSLATORS: This one goes with Undid/Redid messages. */
1751 add_undo(COUPLE_BEGIN, N_("justification"));
1752
1753 /* If the mark is on, do as Pico: treat all marked text as one paragraph. */
1754 if (openfile->mark) {
1755 size_t quot_len, fore_len, other_quot_len, other_white_len;
1756 linestruct *sampleline;
1757
1758 get_region(&startline, &start_x, &endline, &end_x);
1759
1760 /* When the marked region is empty, do nothing. */
1761 if (startline == endline && start_x == end_x) {
1762 statusline(AHEM, _("Selection is empty"));
1763 discard_until(openfile->undotop->next);
1764 return;
1765 }
1766
1767 quot_len = quote_length(startline->data);
1768 fore_len = quot_len + indent_length(startline->data + quot_len);
1769
1770 /* When the region starts IN the lead, take the whole lead. */
1771 if (start_x <= fore_len)
1772 start_x = 0;
1773
1774 /* Recede over blanks before the region. This effectively snips
1775 * trailing blanks from what will become the preceding paragraph. */
1776 while (start_x > 0 && is_blank_char(&startline->data[start_x - 1]))
1777 start_x = step_left(startline->data, start_x);
1778
1779 quot_len = quote_length(endline->data);
1780 fore_len = quot_len + indent_length(endline->data + quot_len);
1781
1782 /* When the region ends IN the lead, take the whole lead. */
1783 if (0 < end_x && end_x < fore_len)
1784 end_x = fore_len;
1785
1786 /* When not at the left edge, advance over blanks after the region. */
1787 while (end_x > 0 && is_blank_char(&endline->data[end_x]))
1788 end_x = step_right(endline->data, end_x);
1789
1790 sampleline = startline;
1791
1792 /* Find the first line of the paragraph in which the region starts. */
1793 while (sampleline->prev && inpar(sampleline) && !begpar(sampleline, 0))
1794 sampleline = sampleline->prev;
1795
1796 /* Ignore lines that contain no text. */
1797 while (sampleline->next && !inpar(sampleline))
1798 sampleline = sampleline->next;
1799
1800 /* Store the leading part that is to be used for the new paragraph. */
1801 quot_len = quote_length(sampleline->data);
1802 primary_len = quot_len + indent_length(sampleline->data + quot_len);
1803 primary_lead = measured_copy(sampleline->data, primary_len);
1804
1805 if (sampleline->next && startline != endline)
1806 sampleline = sampleline->next;
1807
1808 /* Copy the leading part that is to be used for the new paragraph after
1809 * its first line (if any): the quoting of the first line, plus the
1810 * indentation of the second line. */
1811 other_quot_len = quote_length(sampleline->data);
1812 other_white_len = indent_length(sampleline->data + other_quot_len);
1813
1814 secondary_len = quot_len + other_white_len;
1815 secondary_lead = nmalloc(secondary_len + 1);
1816
1817 strncpy(secondary_lead, startline->data, quot_len);
1818 strncpy(secondary_lead + quot_len, sampleline->data + other_quot_len,
1819 other_white_len);
1820 secondary_lead[secondary_len] = '\0';
1821
1822 /* Include preceding and succeeding leads into the marked region. */
1823 openfile->mark = startline;
1824 openfile->mark_x = start_x;
1825 openfile->current = endline;
1826 openfile->current_x = end_x;
1827
1828 linecount = endline->lineno - startline->lineno + (end_x > 0 ? 1 : 0);
1829
1830 /* Remember whether the end of the region was before the end-of-line. */
1831 before_eol = endline->data[end_x] != '\0';
1832 } else
1833 #endif /* NANO_TINY */
1834 {
1835 /* When justifying the entire buffer, start at the top. Otherwise, when
1836 * in a paragraph but not at its beginning, move back to its first line. */
1837 if (whole_buffer)
1838 openfile->current = openfile->filetop;
1839 else if (inpar(openfile->current) && !begpar(openfile->current, 0))
1840 do_para_begin(&openfile->current);
1841
1842 /* Find the first line of the paragraph(s) to be justified. If the
1843 * search fails, there is nothing to justify, and we will be on the
1844 * last line of the file, so put the cursor at the end of it. */
1845 if (!find_paragraph(&openfile->current, &linecount)) {
1846 openfile->current_x = strlen(openfile->filebot->data);
1847 #ifndef NANO_TINY
1848 discard_until(openfile->undotop->next);
1849 #endif
1850 refresh_needed = TRUE;
1851 return;
1852 }
1853
1854 /* Set the starting point of the paragraph. */
1855 startline = openfile->current;
1856 start_x = 0;
1857
1858 /* Set the end point of the paragraph. */
1859 if (whole_buffer)
1860 endline = openfile->filebot;
1861 else {
1862 endline = startline;
1863 for (size_t count = linecount; count > 1; count--)
1864 endline = endline->next;
1865 }
1866
1867 /* When possible, step one line further; otherwise, to line's end. */
1868 if (endline->next != NULL) {
1869 endline = endline->next;
1870 end_x = 0;
1871 } else
1872 end_x = strlen(endline->data);
1873 }
1874
1875 #ifndef NANO_TINY
1876 add_undo(CUT, NULL);
1877 #endif
1878 /* Do the equivalent of a marked cut into an empty cutbuffer. */
1879 cutbuffer = NULL;
1880 extract_segment(startline, start_x, endline, end_x);
1881 #ifndef NANO_TINY
1882 update_undo(CUT);
1883
1884 if (openfile->mark) {
1885 linestruct *line = cutbuffer;
1886 size_t quot_len = quote_length(line->data);
1887 size_t fore_len = quot_len + indent_length(line->data + quot_len);
1888 size_t text_len = strlen(line->data) - fore_len;
1889
1890 /* If the extracted region begins with any leading part, trim it. */
1891 if (fore_len > 0)
1892 memmove(line->data, line->data + fore_len, text_len + 1);
1893
1894 /* Then copy back in the leading part that it should have. */
1895 if (primary_len > 0) {
1896 line->data = nrealloc(line->data, primary_len + text_len + 1);
1897 memmove(line->data + primary_len, line->data, text_len + 1);
1898 strncpy(line->data, primary_lead, primary_len);
1899 }
1900
1901 /* Now justify the extracted region. */
1902 concat_paragraph(cutbuffer, linecount);
1903 squeeze(cutbuffer, primary_len);
1904 rewrap_paragraph(&line, secondary_lead, secondary_len);
1905
1906 /* If the marked region started in the middle of a line,
1907 * insert a newline before the new paragraph. */
1908 if (start_x > 0) {
1909 cutbuffer->prev = make_new_node(NULL);
1910 cutbuffer->prev->data = copy_of("");
1911 cutbuffer->prev->next = cutbuffer;
1912 cutbuffer = cutbuffer->prev;
1913 }
1914
1915 /* If the marked region ended in the middle of a line,
1916 * insert a newline after the new paragraph. */
1917 if (end_x > 0 && before_eol) {
1918 line->next = make_new_node(line);
1919 line->next->data = copy_of(primary_lead);
1920 }
1921
1922 free(secondary_lead);
1923 free(primary_lead);
1924 } else
1925 #endif
1926 {
1927 /* Prepare to justify the text we just put in the cutbuffer. */
1928 jusline = cutbuffer;
1929
1930 /* Justify the current paragraph. */
1931 justify_paragraph(&jusline, linecount);
1932
1933 /* When justifying the entire buffer, find and justify all paragraphs. */
1934 if (whole_buffer) {
1935 while (find_paragraph(&jusline, &linecount)) {
1936 justify_paragraph(&jusline, linecount);
1937
1938 if (jusline->next == NULL)
1939 break;
1940 }
1941 }
1942 }
1943
1944 #ifndef NANO_TINY
1945 /* Wipe an anchor on the first paragraph if it was only inherited. */
1946 if (whole_buffer && !openfile->mark && !cutbuffer->has_anchor)
1947 openfile->current->has_anchor = FALSE;
1948
1949 add_undo(PASTE, NULL);
1950 #endif
1951 /* Do the equivalent of a paste of the justified text. */
1952 ingraft_buffer(cutbuffer);
1953 #ifndef NANO_TINY
1954 update_undo(PASTE);
1955
1956 /* After justifying a backward-marked text, swap mark and cursor. */
1957 if (openfile->mark && !mark_is_before_cursor()) {
1958 linestruct *bottom = openfile->current;
1959 size_t bottom_x = openfile->current_x;
1960
1961 openfile->current = openfile->mark;
1962 openfile->current_x = openfile->mark_x;
1963 openfile->mark = bottom;
1964 openfile->mark_x = bottom_x;
1965 }
1966
1967 add_undo(COUPLE_END, N_("justification"));
1968
1969 /* Report on the status bar what we justified. */
1970 if (openfile->mark)
1971 statusline(REMARK, _("Justified selection"));
1972 else
1973 #endif
1974 if (whole_buffer)
1975 statusline(REMARK, _("Justified file"));
1976 else
1977 statusbar(_("Justified paragraph"));
1978
1979 /* We're done justifying. Restore the cutbuffer. */
1980 cutbuffer = was_cutbuffer;
1981
1982 /* Set the desired screen column (always zero, except at EOF). */
1983 openfile->placewewant = xplustabs();
1984
1985 set_modified();
1986 refresh_needed = TRUE;
1987 shift_held = TRUE;
1988 }
1989
1990 /* Justify the current paragraph. */
do_justify(void)1991 void do_justify(void)
1992 {
1993 justify_text(ONE_PARAGRAPH);
1994 }
1995
1996 /* Justify the entire file. */
do_full_justify(void)1997 void do_full_justify(void)
1998 {
1999 justify_text(WHOLE_BUFFER);
2000 ran_a_tool = TRUE;
2001 }
2002 #endif /* ENABLE_JUSTIFY */
2003
2004 #if defined(ENABLE_SPELLER) || defined (ENABLE_COLOR)
2005 /* Set up an argument list for executing the given command. */
construct_argument_list(char *** arguments,char * command,char * filename)2006 void construct_argument_list(char ***arguments, char *command, char *filename)
2007 {
2008 char *copy_of_command = copy_of(command);
2009 char *element = strtok(copy_of_command, " ");
2010 int count = 2;
2011
2012 while (element != NULL) {
2013 *arguments = nrealloc(*arguments, ++count * sizeof(char *));
2014 (*arguments)[count - 3] = element;
2015 element = strtok(NULL, " ");
2016 }
2017
2018 (*arguments)[count - 2] = filename;
2019 (*arguments)[count - 1] = NULL;
2020 }
2021
2022 /* Open the specified file, and if that succeeds, remove the text of the marked
2023 * region or of the entire buffer and read the file contents into its place. */
replace_buffer(const char * filename,undo_type action,const char * operation)2024 bool replace_buffer(const char *filename, undo_type action, const char *operation)
2025 {
2026 linestruct *was_cutbuffer = cutbuffer;
2027 int descriptor;
2028 FILE *stream;
2029
2030 descriptor = open_file(filename, FALSE, &stream);
2031
2032 if (descriptor < 0)
2033 return FALSE;
2034
2035 cutbuffer = NULL;
2036
2037 #ifndef NANO_TINY
2038 add_undo(COUPLE_BEGIN, operation);
2039
2040 /* Cut either the marked region or the whole buffer. */
2041 add_undo(action, NULL);
2042 do_snip(openfile->mark != NULL, openfile->mark == NULL, FALSE);
2043 update_undo(action);
2044 #else
2045 do_snip(FALSE, TRUE, FALSE);
2046 #endif
2047
2048 /* Discard what was cut. */
2049 free_lines(cutbuffer);
2050 cutbuffer = was_cutbuffer;
2051
2052 /* Insert the spell-checked file into the cleared area. */
2053 read_file(stream, descriptor, filename, TRUE);
2054
2055 #ifndef NANO_TINY
2056 add_undo(COUPLE_END, operation);
2057 #endif
2058 return TRUE;
2059 }
2060
2061 /* Execute the given program, with the given temp file as last argument. */
treat(char * tempfile_name,char * theprogram,bool spelling)2062 void treat(char *tempfile_name, char *theprogram, bool spelling)
2063 {
2064 ssize_t was_lineno = openfile->current->lineno;
2065 size_t was_pww = openfile->placewewant;
2066 size_t was_x = openfile->current_x;
2067 bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
2068 struct stat fileinfo;
2069 long timestamp_sec = 0;
2070 long timestamp_nsec = 0;
2071 static char **arguments = NULL;
2072 pid_t thepid;
2073 int program_status, errornumber;
2074 bool replaced = FALSE;
2075
2076 /* Stat the temporary file. If that succeeds and its size is zero,
2077 * there is nothing to do; otherwise, store its time of modification. */
2078 if (stat(tempfile_name, &fileinfo) == 0) {
2079 if (fileinfo.st_size == 0) {
2080 #ifndef NANO_TINY
2081 if (spelling && openfile->mark)
2082 statusline(AHEM, _("Selection is empty"));
2083 else
2084 #endif
2085 statusline(AHEM, _("Buffer is empty"));
2086 return;
2087 }
2088
2089 timestamp_sec = (long)fileinfo.st_mtim.tv_sec;
2090 timestamp_nsec = (long)fileinfo.st_mtim.tv_nsec;
2091 }
2092
2093 /* Exit from curses mode to give the program control of the terminal. */
2094 endwin();
2095
2096 construct_argument_list(&arguments, theprogram, tempfile_name);
2097
2098 /* Fork a child process and run the given program in it. */
2099 if ((thepid = fork()) == 0) {
2100 execvp(arguments[0], arguments);
2101
2102 /* Terminate the child if the program is not found. */
2103 exit(9);
2104 } else if (thepid > 0) {
2105 /* Block SIGWINCHes while waiting for the forked program to end,
2106 * so nano doesn't get pushed past the wait(). */
2107 block_sigwinch(TRUE);
2108 wait(&program_status);
2109 block_sigwinch(FALSE);
2110 }
2111
2112 errornumber = errno;
2113
2114 /* Restore the terminal state and reenter curses mode. */
2115 terminal_init();
2116 doupdate();
2117
2118 if (thepid < 0) {
2119 statusline(ALERT, _("Could not fork: %s"), strerror(errornumber));
2120 free(arguments[0]);
2121 return;
2122 } else if (!WIFEXITED(program_status) || WEXITSTATUS(program_status) > 2) {
2123 statusline(ALERT, _("Error invoking '%s'"), arguments[0]);
2124 free(arguments[0]);
2125 return;
2126 } else if (WEXITSTATUS(program_status) != 0)
2127 statusline(ALERT, _("Program '%s' complained"), arguments[0]);
2128
2129 free(arguments[0]);
2130
2131 /* When the temporary file wasn't touched, say so and leave. */
2132 if (timestamp_sec > 0 && stat(tempfile_name, &fileinfo) == 0 &&
2133 (long)fileinfo.st_mtim.tv_sec == timestamp_sec &&
2134 (long)fileinfo.st_mtim.tv_nsec == timestamp_nsec) {
2135 statusline(REMARK, _("Nothing changed"));
2136 return;
2137 }
2138
2139 #ifndef NANO_TINY
2140 /* Replace the marked text (or entire text) with the corrected text. */
2141 if (spelling && openfile->mark) {
2142 ssize_t was_mark_lineno = openfile->mark->lineno;
2143 bool upright = mark_is_before_cursor();
2144
2145 replaced = replace_buffer(tempfile_name, CUT, "spelling correction");
2146
2147 /* Adjust the end point of the marked region for any change in
2148 * length of the region's last line. */
2149 if (upright)
2150 was_x = openfile->current_x;
2151 else
2152 openfile->mark_x = openfile->current_x;
2153
2154 /* Restore the mark. */
2155 openfile->mark = line_from_number(was_mark_lineno);
2156 } else
2157 #endif
2158 {
2159 openfile->current = openfile->filetop;
2160 openfile->current_x = 0;
2161
2162 replaced = replace_buffer(tempfile_name, CUT_TO_EOF,
2163 /* TRANSLATORS: The next two go with Undid/Redid messages. */
2164 (spelling ? N_("spelling correction") : N_("formatting")));
2165 }
2166
2167 /* Go back to the old position. */
2168 goto_line_posx(was_lineno, was_x);
2169 if (was_at_eol || openfile->current_x > strlen(openfile->current->data))
2170 openfile->current_x = strlen(openfile->current->data);
2171
2172 if (replaced) {
2173 #ifndef NANO_TINY
2174 openfile->filetop->has_anchor = FALSE;
2175 update_undo(COUPLE_END);
2176 #endif
2177 }
2178
2179 openfile->placewewant = was_pww;
2180 adjust_viewport(STATIONARY);
2181
2182 if (spelling)
2183 statusline(REMARK, _("Finished checking spelling"));
2184 else
2185 statusline(REMARK, _("Buffer has been processed"));
2186 }
2187 #endif /* ENABLE_SPELLER || ENABLE_COLOR */
2188
2189 #ifdef ENABLE_SPELLER
2190 /* Let the user edit the misspelled word. Return FALSE if the user cancels. */
fix_spello(const char * word)2191 bool fix_spello(const char *word)
2192 {
2193 linestruct *was_edittop = openfile->edittop;
2194 linestruct *was_current = openfile->current;
2195 size_t was_firstcolumn = openfile->firstcolumn;
2196 size_t was_x = openfile->current_x;
2197 bool proceed = FALSE;
2198 int result;
2199 #ifndef NANO_TINY
2200 bool right_side_up = (openfile->mark && mark_is_before_cursor());
2201 linestruct *top, *bot;
2202 size_t top_x, bot_x;
2203
2204 /* If the mark is on, start at the beginning of the marked region. */
2205 if (openfile->mark) {
2206 get_region(&top, &top_x, &bot, &bot_x);
2207 /* If the region is marked normally, swap the end points, so that
2208 * (current, current_x) (where searching starts) is at the top. */
2209 if (right_side_up) {
2210 openfile->current = top;
2211 openfile->current_x = top_x;
2212 openfile->mark = bot;
2213 openfile->mark_x = bot_x;
2214 }
2215 } else
2216 #endif
2217 /* Otherwise, start from the top of the file. */
2218 {
2219 openfile->current = openfile->filetop;
2220 openfile->current_x = 0;
2221 }
2222
2223 /* Find the first whole occurrence of word. */
2224 result = findnextstr(word, TRUE, INREGION, NULL, FALSE, NULL, 0);
2225
2226 /* If the word isn't found, alert the user; if it is, allow correction. */
2227 if (result == 0) {
2228 statusline(ALERT, _("Unfindable word: %s"), word);
2229 lastmessage = VACUUM;
2230 proceed = TRUE;
2231 napms(2800);
2232 } else if (result == 1) {
2233 spotlighted = TRUE;
2234 light_from_col = xplustabs();
2235 light_to_col = light_from_col + breadth(word);
2236 #ifndef NANO_TINY
2237 linestruct *saved_mark = openfile->mark;
2238 openfile->mark = NULL;
2239 #endif
2240 edit_refresh();
2241
2242 put_cursor_at_end_of_answer();
2243
2244 /* Let the user supply a correctly spelled alternative. */
2245 proceed = (do_prompt(MSPELL, word, NULL, edit_refresh,
2246 /* TRANSLATORS: This is a prompt. */
2247 _("Edit a replacement")) != -1);
2248
2249 spotlighted = FALSE;
2250
2251 #ifndef NANO_TINY
2252 openfile->mark = saved_mark;
2253 #endif
2254
2255 /* If a replacement was given, go through all occurrences. */
2256 if (proceed && strcmp(word, answer) != 0) {
2257 do_replace_loop(word, TRUE, was_current, &was_x);
2258
2259 /* TRANSLATORS: Shown after fixing misspellings in one word. */
2260 statusbar(_("Next word..."));
2261 napms(400);
2262 }
2263 }
2264
2265 #ifndef NANO_TINY
2266 if (openfile->mark) {
2267 /* Restore the (compensated) end points of the marked region. */
2268 if (right_side_up) {
2269 openfile->current = openfile->mark;
2270 openfile->current_x = openfile->mark_x;
2271 openfile->mark = top;
2272 openfile->mark_x = top_x;
2273 } else {
2274 openfile->current = top;
2275 openfile->current_x = top_x;
2276 }
2277 } else
2278 #endif
2279 {
2280 /* Restore the (compensated) cursor position. */
2281 openfile->current = was_current;
2282 openfile->current_x = was_x;
2283 }
2284
2285 /* Restore the viewport to where it was. */
2286 openfile->edittop = was_edittop;
2287 openfile->firstcolumn = was_firstcolumn;
2288
2289 return proceed;
2290 }
2291
2292 /* Run a spell-check on the given file, using 'spell' to produce a list of all
2293 * misspelled words, then feeding those through 'sort' and 'uniq' to obtain an
2294 * alphabetical list, which words are then offered one by one to the user for
2295 * correction. */
do_int_speller(const char * tempfile_name)2296 void do_int_speller(const char *tempfile_name)
2297 {
2298 char *misspellings, *pointer, *oneword;
2299 long pipesize;
2300 size_t buffersize, bytesread, totalread;
2301 int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
2302 pid_t pid_spell, pid_sort, pid_uniq;
2303 int spell_status, sort_status, uniq_status;
2304
2305 /* Create all three pipes up front. */
2306 if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1) {
2307 statusline(ALERT, _("Could not create pipe: %s"), strerror(errno));
2308 return;
2309 }
2310
2311 statusbar(_("Invoking spell checker..."));
2312
2313 /* Fork a process to run spell in. */
2314 if ((pid_spell = fork()) == 0) {
2315 /* Child: open the temporary file that holds the text to be checked. */
2316 if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
2317 exit(6);
2318
2319 /* Connect standard input to the temporary file. */
2320 if (dup2(tempfile_fd, STDIN_FILENO) < 0)
2321 exit(7);
2322
2323 /* Connect standard output to the write end of the first pipe. */
2324 if (dup2(spell_fd[1], STDOUT_FILENO) < 0)
2325 exit(8);
2326
2327 close(tempfile_fd);
2328 close(spell_fd[0]);
2329 close(spell_fd[1]);
2330
2331 /* Try to run 'hunspell'; if that fails, fall back to 'spell'. */
2332 execlp("hunspell", "hunspell", "-l", NULL);
2333 execlp("spell", "spell", NULL);
2334
2335 /* Indicate failure when neither speller was found. */
2336 exit(9);
2337 }
2338
2339 /* Parent: close the unused write end of the first pipe. */
2340 close(spell_fd[1]);
2341
2342 /* Fork a process to run sort in. */
2343 if ((pid_sort = fork()) == 0) {
2344 /* Connect standard input to the read end of the first pipe. */
2345 if (dup2(spell_fd[0], STDIN_FILENO) < 0)
2346 exit(7);
2347
2348 /* Connect standard output to the write end of the second pipe. */
2349 if (dup2(sort_fd[1], STDOUT_FILENO) < 0)
2350 exit(8);
2351
2352 close(spell_fd[0]);
2353 close(sort_fd[0]);
2354 close(sort_fd[1]);
2355
2356 /* Now run the sort program. Use -f to mix upper and lower case. */
2357 execlp("sort", "sort", "-f", NULL);
2358
2359 exit(9);
2360 }
2361
2362 close(spell_fd[0]);
2363 close(sort_fd[1]);
2364
2365 /* Fork a process to run uniq in. */
2366 if ((pid_uniq = fork()) == 0) {
2367 if (dup2(sort_fd[0], STDIN_FILENO) < 0)
2368 exit(7);
2369
2370 if (dup2(uniq_fd[1], STDOUT_FILENO) < 0)
2371 exit(8);
2372
2373 close(sort_fd[0]);
2374 close(uniq_fd[0]);
2375 close(uniq_fd[1]);
2376
2377 execlp("uniq", "uniq", NULL);
2378
2379 exit(9);
2380 }
2381
2382 close(sort_fd[0]);
2383 close(uniq_fd[1]);
2384
2385 /* When some child process was not forked successfully... */
2386 if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
2387 statusline(ALERT, _("Could not fork: %s"), strerror(errno));
2388 close(uniq_fd[0]);
2389 return;
2390 }
2391
2392 /* Get the system pipe buffer size. */
2393 pipesize = fpathconf(uniq_fd[0], _PC_PIPE_BUF);
2394
2395 if (pipesize < 1) {
2396 statusline(ALERT, _("Could not get size of pipe buffer"));
2397 close(uniq_fd[0]);
2398 return;
2399 }
2400
2401 /* Leave curses mode so that error messages go to the original screen. */
2402 endwin();
2403
2404 /* Block SIGWINCHes while reading misspelled words from the third pipe. */
2405 block_sigwinch(TRUE);
2406
2407 totalread = 0;
2408 buffersize = pipesize + 1;
2409 misspellings = nmalloc(buffersize);
2410 pointer = misspellings;
2411
2412 while ((bytesread = read(uniq_fd[0], pointer, pipesize)) > 0) {
2413 totalread += bytesread;
2414 buffersize += pipesize;
2415 misspellings = nrealloc(misspellings, buffersize);
2416 pointer = misspellings + totalread;
2417 }
2418
2419 *pointer = '\0';
2420 close(uniq_fd[0]);
2421
2422 block_sigwinch(FALSE);
2423
2424 /* Re-enter curses mode. */
2425 terminal_init();
2426 doupdate();
2427
2428 /* Do any replacements case-sensitively, forward, and without regexes. */
2429 SET(CASE_SENSITIVE);
2430 UNSET(BACKWARDS_SEARCH);
2431 UNSET(USE_REGEXP);
2432
2433 pointer = misspellings;
2434 oneword = misspellings;
2435
2436 /* Process each of the misspelled words. */
2437 while (*pointer != '\0') {
2438 if ((*pointer == '\r') || (*pointer == '\n')) {
2439 *pointer = '\0';
2440 if (oneword != pointer) {
2441 if (!fix_spello(oneword)) {
2442 oneword = pointer;
2443 break;
2444 }
2445 }
2446 oneword = pointer + 1;
2447 }
2448 pointer++;
2449 }
2450
2451 /* Special case: the last word doesn't end with '\r' or '\n'. */
2452 if (oneword != pointer)
2453 fix_spello(oneword);
2454
2455 free(misspellings);
2456 refresh_needed = TRUE;
2457
2458 /* Process the end of the three processes. */
2459 waitpid(pid_spell, &spell_status, 0);
2460 waitpid(pid_sort, &sort_status, 0);
2461 waitpid(pid_uniq, &uniq_status, 0);
2462
2463 if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
2464 statusline(ALERT, _("Error invoking \"uniq\""));
2465 else if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
2466 statusline(ALERT, _("Error invoking \"sort\""));
2467 else if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
2468 statusline(ALERT, _("Error invoking \"spell\""));
2469 else
2470 statusline(REMARK, _("Finished checking spelling"));
2471 }
2472
2473 /* Spell check the current file. If an alternate spell checker is
2474 * specified, use it. Otherwise, use the internal spell checker. */
do_spell(void)2475 void do_spell(void)
2476 {
2477 FILE *stream;
2478 char *temp_name;
2479 unsigned stash[sizeof(flags) / sizeof(flags[0])];
2480 bool okay;
2481
2482 ran_a_tool = TRUE;
2483
2484 if (in_restricted_mode())
2485 return;
2486
2487 temp_name = safe_tempfile(&stream);
2488
2489 if (temp_name == NULL) {
2490 statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
2491 return;
2492 }
2493
2494 /* Save the settings of the global flags. */
2495 memcpy(stash, flags, sizeof(flags));
2496
2497 /* Don't add an extra newline when writing out the (selected) text. */
2498 SET(NO_NEWLINES);
2499
2500 #ifndef NANO_TINY
2501 if (openfile->mark)
2502 okay = write_region_to_file(temp_name, stream, TEMPORARY, OVERWRITE);
2503 else
2504 #endif
2505 okay = write_file(temp_name, stream, TEMPORARY, OVERWRITE, NONOTES);
2506
2507 if (!okay) {
2508 statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
2509 free(temp_name);
2510 return;
2511 }
2512
2513 blank_bottombars();
2514
2515 if (alt_speller && *alt_speller)
2516 treat(temp_name, alt_speller, TRUE);
2517 else
2518 do_int_speller(temp_name);
2519
2520 unlink(temp_name);
2521 free(temp_name);
2522
2523 /* Restore the settings of the global flags. */
2524 memcpy(flags, stash, sizeof(flags));
2525
2526 /* Ensure the help lines will be redrawn and a selection is retained. */
2527 currmenu = MMOST;
2528 shift_held = TRUE;
2529 }
2530 #endif /* ENABLE_SPELLER */
2531
2532 #ifdef ENABLE_COLOR
2533 /* Run a linting program on the current buffer. */
do_linter(void)2534 void do_linter(void)
2535 {
2536 char *lintings, *pointer, *onelint;
2537 long pipesize;
2538 size_t buffersize, bytesread, totalread;
2539 bool parsesuccess = FALSE;
2540 int lint_status, lint_fd[2];
2541 pid_t pid_lint;
2542 bool helpless = ISSET(NO_HELP);
2543 lintstruct *lints = NULL, *tmplint = NULL, *curlint = NULL;
2544 time_t last_wait = 0;
2545
2546 ran_a_tool = TRUE;
2547
2548 if (in_restricted_mode())
2549 return;
2550
2551 if (!openfile->syntax || !openfile->syntax->linter) {
2552 statusline(AHEM, _("No linter is defined for this type of file"));
2553 return;
2554 }
2555
2556 #ifndef NANO_TINY
2557 openfile->mark = NULL;
2558 #endif
2559 edit_refresh();
2560
2561 if (openfile->modified) {
2562 int choice = do_yesno_prompt(FALSE, _("Save modified buffer before linting?"));
2563
2564 if (choice == -1) {
2565 statusbar(_("Cancelled"));
2566 return;
2567 } else if (choice == 1 && (do_writeout(FALSE, FALSE) != 1))
2568 return;
2569 }
2570
2571 /* Create a pipe up front. */
2572 if (pipe(lint_fd) == -1) {
2573 statusline(ALERT, _("Could not create pipe: %s"), strerror(errno));
2574 return;
2575 }
2576
2577 blank_bottombars();
2578 currmenu = MLINTER;
2579 statusbar(_("Invoking linter..."));
2580
2581 /* Fork a process to run the linter in. */
2582 if ((pid_lint = fork()) == 0) {
2583 char **lintargs = NULL;
2584
2585 /* Redirect standard output and standard error into the pipe. */
2586 if (dup2(lint_fd[1], STDOUT_FILENO) < 0)
2587 exit(7);
2588 if (dup2(lint_fd[1], STDERR_FILENO) < 0)
2589 exit(8);
2590
2591 close(lint_fd[0]);
2592 close(lint_fd[1]);
2593
2594 construct_argument_list(&lintargs, openfile->syntax->linter, openfile->filename);
2595
2596 /* Start the linter program; we are using $PATH. */
2597 execvp(lintargs[0], lintargs);
2598
2599 /* This is only reached when the linter is not found. */
2600 exit(9);
2601 }
2602
2603 /* Parent continues here. */
2604 close(lint_fd[1]);
2605
2606 /* If the child process was not forked successfully... */
2607 if (pid_lint < 0) {
2608 statusline(ALERT, _("Could not fork: %s"), strerror(errno));
2609 close(lint_fd[0]);
2610 return;
2611 }
2612
2613 /* Get the system pipe buffer size. */
2614 pipesize = fpathconf(lint_fd[0], _PC_PIPE_BUF);
2615
2616 if (pipesize < 1) {
2617 statusline(ALERT, _("Could not get size of pipe buffer"));
2618 close(lint_fd[0]);
2619 return;
2620 }
2621
2622 /* Block resizing signals while reading from the pipe. */
2623 block_sigwinch(TRUE);
2624
2625 /* Read in the returned syntax errors. */
2626 totalread = 0;
2627 buffersize = pipesize + 1;
2628 lintings = nmalloc(buffersize);
2629 pointer = lintings;
2630
2631 while ((bytesread = read(lint_fd[0], pointer, pipesize)) > 0) {
2632 totalread += bytesread;
2633 buffersize += pipesize;
2634 lintings = nrealloc(lintings, buffersize);
2635 pointer = lintings + totalread;
2636 }
2637
2638 *pointer = '\0';
2639 close(lint_fd[0]);
2640
2641 block_sigwinch(FALSE);
2642
2643 pointer = lintings;
2644 onelint = lintings;
2645
2646 /* Now parse the output of the linter. */
2647 while (*pointer != '\0') {
2648 if ((*pointer == '\r') || (*pointer == '\n')) {
2649 *pointer = '\0';
2650 if (onelint != pointer) {
2651 char *filename = NULL, *linestr = NULL, *maybecol = NULL;
2652 char *message = copy_of(onelint);
2653
2654 /* The recognized format is "filename:line:column: message",
2655 * where ":column" may be absent or be ",column" instead. */
2656 if (strstr(message, ": ") != NULL) {
2657 filename = strtok(onelint, ":");
2658 if ((linestr = strtok(NULL, ":")) != NULL) {
2659 if ((maybecol = strtok(NULL, ":")) != NULL) {
2660 ssize_t tmplineno = 0, tmpcolno = 0;
2661 char *tmplinecol;
2662
2663 tmplineno = strtol(linestr, NULL, 10);
2664 if (tmplineno <= 0) {
2665 pointer++;
2666 free(message);
2667 continue;
2668 }
2669
2670 tmpcolno = strtol(maybecol, NULL, 10);
2671 /* Check if the middle field is in comma format. */
2672 if (tmpcolno <= 0) {
2673 strtok(linestr, ",");
2674 if ((tmplinecol = strtok(NULL, ",")) != NULL)
2675 tmpcolno = strtol(tmplinecol, NULL, 10);
2676 else
2677 tmpcolno = 1;
2678 }
2679
2680 /* Nice. We have a lint message we can use. */
2681 parsesuccess = TRUE;
2682 tmplint = curlint;
2683 curlint = nmalloc(sizeof(lintstruct));
2684 curlint->next = NULL;
2685 curlint->prev = tmplint;
2686 if (curlint->prev != NULL)
2687 curlint->prev->next = curlint;
2688 curlint->msg = copy_of(strstr(message, ": ") + 2);
2689 curlint->lineno = tmplineno;
2690 curlint->colno = tmpcolno;
2691 curlint->filename = copy_of(filename);
2692
2693 if (lints == NULL)
2694 lints = curlint;
2695 }
2696 }
2697 }
2698 free(message);
2699 }
2700 onelint = pointer + 1;
2701 }
2702 pointer++;
2703 }
2704
2705 free(lintings);
2706
2707 /* Process the end of the linting process. */
2708 waitpid(pid_lint, &lint_status, 0);
2709
2710 if (!WIFEXITED(lint_status) || WEXITSTATUS(lint_status) > 2) {
2711 statusline(ALERT, _("Error invoking '%s'"), openfile->syntax->linter);
2712 return;
2713 }
2714
2715 if (!parsesuccess) {
2716 statusline(REMARK, _("Got 0 parsable lines from command: %s"),
2717 openfile->syntax->linter);
2718 return;
2719 }
2720
2721 if (helpless && LINES > 4) {
2722 UNSET(NO_HELP);
2723 window_init();
2724 }
2725
2726 /* Show that we are in the linter now. */
2727 titlebar(NULL);
2728 bottombars(MLINTER);
2729
2730 tmplint = NULL;
2731 curlint = lints;
2732
2733 while (TRUE) {
2734 int kbinput;
2735 functionptrtype func;
2736 struct stat lintfileinfo;
2737
2738 if (stat(curlint->filename, &lintfileinfo) != -1 &&
2739 (openfile->statinfo == NULL ||
2740 openfile->statinfo->st_ino != lintfileinfo.st_ino)) {
2741 #ifdef ENABLE_MULTIBUFFER
2742 const openfilestruct *started_at = openfile;
2743
2744 openfile = openfile->next;
2745 while (openfile != started_at && (openfile->statinfo == NULL ||
2746 openfile->statinfo->st_ino != lintfileinfo.st_ino))
2747 openfile = openfile->next;
2748
2749 if (openfile->statinfo == NULL ||
2750 openfile->statinfo->st_ino != lintfileinfo.st_ino) {
2751 char *msg = nmalloc(1024 + strlen(curlint->filename));
2752 int choice;
2753
2754 sprintf(msg, _("This message is for unopened file %s,"
2755 " open it in a new buffer?"), curlint->filename);
2756 choice = do_yesno_prompt(FALSE, msg);
2757 currmenu = MLINTER;
2758 free(msg);
2759
2760 if (choice == -1) {
2761 statusbar(_("Cancelled"));
2762 break;
2763 } else if (choice == 1) {
2764 open_buffer(curlint->filename, TRUE);
2765 } else {
2766 #endif
2767 char *dontwantfile = copy_of(curlint->filename);
2768 lintstruct *restlint = NULL;
2769
2770 while (curlint != NULL) {
2771 if (strcmp(curlint->filename, dontwantfile) == 0) {
2772 if (curlint == lints)
2773 lints = curlint->next;
2774 else
2775 curlint->prev->next = curlint->next;
2776 if (curlint->next != NULL)
2777 curlint->next->prev = curlint->prev;
2778 tmplint = curlint;
2779 curlint = curlint->next;
2780 free(tmplint->msg);
2781 free(tmplint->filename);
2782 free(tmplint);
2783 } else {
2784 if (restlint == NULL)
2785 restlint = curlint;
2786 curlint = curlint->next;
2787 }
2788 }
2789
2790 free(dontwantfile);
2791
2792 if (restlint == NULL) {
2793 statusline(REMARK, _("No messages for this file"));
2794 break;
2795 } else {
2796 curlint = restlint;
2797 continue;
2798 }
2799 #ifdef ENABLE_MULTIBUFFER
2800 }
2801 }
2802 #endif
2803 }
2804
2805 if (tmplint != curlint) {
2806 /* Put the cursor at the reported position, but don't go beyond EOL
2807 * when the second number is a column number instead of an index. */
2808 goto_line_posx(curlint->lineno, curlint->colno - 1);
2809 openfile->current_x = actual_x(openfile->current->data, openfile->placewewant);
2810 titlebar(NULL);
2811 adjust_viewport(CENTERING);
2812 #ifdef ENABLE_LINENUMBERS
2813 confirm_margin();
2814 #endif
2815 edit_refresh();
2816 statusline(NOTICE, curlint->msg);
2817 bottombars(MLINTER);
2818 }
2819
2820 /* Place the cursor to indicate the affected line. */
2821 place_the_cursor();
2822 wnoutrefresh(edit);
2823
2824 kbinput = get_kbinput(bottomwin, VISIBLE);
2825
2826 #ifndef NANO_TINY
2827 if (kbinput == KEY_WINCH)
2828 continue;
2829 #endif
2830 func = func_from_key(&kbinput);
2831 tmplint = curlint;
2832
2833 if (func == do_cancel || func == do_enter) {
2834 wipe_statusbar();
2835 break;
2836 } else if (func == do_help) {
2837 tmplint = NULL;
2838 do_help();
2839 } else if (func == do_page_up || func == to_prev_block) {
2840 if (curlint->prev != NULL)
2841 curlint = curlint->prev;
2842 else if (last_wait != time(NULL)) {
2843 statusbar(_("At first message"));
2844 beep();
2845 napms(600);
2846 last_wait = time(NULL);
2847 statusline(NOTICE, curlint->msg);
2848 }
2849 } else if (func == do_page_down || func == to_next_block) {
2850 if (curlint->next != NULL)
2851 curlint = curlint->next;
2852 else if (last_wait != time(NULL)) {
2853 statusbar(_("At last message"));
2854 beep();
2855 napms(600);
2856 last_wait = time(NULL);
2857 statusline(NOTICE, curlint->msg);
2858 }
2859 } else
2860 beep();
2861 }
2862
2863 for (curlint = lints; curlint != NULL;) {
2864 tmplint = curlint;
2865 curlint = curlint->next;
2866 free(tmplint->msg);
2867 free(tmplint->filename);
2868 free(tmplint);
2869 }
2870
2871 if (helpless) {
2872 SET(NO_HELP);
2873 window_init();
2874 refresh_needed = TRUE;
2875 }
2876
2877 lastmessage = VACUUM;
2878 currmenu = MMOST;
2879 titlebar(NULL);
2880 }
2881
2882 /* Run a manipulation program on the contents of the buffer. */
do_formatter(void)2883 void do_formatter(void)
2884 {
2885 FILE *stream;
2886 char *temp_name;
2887 bool okay = FALSE;
2888
2889 ran_a_tool = TRUE;
2890
2891 if (in_restricted_mode())
2892 return;
2893
2894 if (!openfile->syntax || !openfile->syntax->formatter) {
2895 statusline(AHEM, _("No formatter is defined for this type of file"));
2896 return;
2897 }
2898
2899 #ifndef NANO_TINY
2900 openfile->mark = NULL;
2901 #endif
2902
2903 temp_name = safe_tempfile(&stream);
2904
2905 if (temp_name != NULL)
2906 okay = write_file(temp_name, stream, TEMPORARY, OVERWRITE, NONOTES);
2907
2908 if (!okay) {
2909 statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
2910 free(temp_name);
2911 return;
2912 }
2913
2914 treat(temp_name, openfile->syntax->formatter, FALSE);
2915
2916 unlink(temp_name);
2917 free(temp_name);
2918 }
2919 #endif /* ENABLE_COLOR */
2920
2921 #ifndef NANO_TINY
2922 /* Our own version of "wc". Note that its character counts are in
2923 * multibyte characters instead of single-byte characters. */
do_wordlinechar_count(void)2924 void do_wordlinechar_count(void)
2925 {
2926 linestruct *was_current = openfile->current;
2927 size_t was_x = openfile->current_x;
2928 linestruct *topline, *botline;
2929 size_t top_x, bot_x;
2930 size_t words = 0, chars = 0;
2931 ssize_t lines = 0;
2932
2933 /* Set the start and end point of the area to measure: either the marked
2934 * region or the whole buffer. Then compute the number of characters. */
2935 if (openfile->mark) {
2936 get_region(&topline, &top_x, &botline, &bot_x);
2937
2938 if (topline != botline)
2939 chars = number_of_characters_in(topline->next, botline) + 1;
2940
2941 chars += mbstrlen(topline->data + top_x) - mbstrlen(botline->data + bot_x);
2942 } else {
2943 topline = openfile->filetop;
2944 top_x = 0;
2945 botline = openfile->filebot;
2946 bot_x = strlen(botline->data);
2947
2948 chars = openfile->totsize;
2949 }
2950
2951 /* Compute the number of lines. */
2952 lines = botline->lineno - topline->lineno;
2953 lines += (bot_x == 0 || (topline == botline && top_x == bot_x)) ? 0 : 1;
2954
2955 openfile->current = topline;
2956 openfile->current_x = top_x;
2957
2958 /* Keep stepping to the next word (considering punctuation as part of a
2959 * word, as "wc -w" does), until we reach the end of the relevant area,
2960 * incrementing the word count for each successful step. */
2961 while (openfile->current->lineno < botline->lineno ||
2962 (openfile->current == botline && openfile->current_x < bot_x)) {
2963 if (do_next_word(FALSE, TRUE))
2964 words++;
2965 }
2966
2967 /* Restore where we were. */
2968 openfile->current = was_current;
2969 openfile->current_x = was_x;
2970
2971 /* Report on the status bar the number of lines, words, and characters. */
2972 statusline(INFO, _("%s%zd %s, %zu %s, %zu %s"),
2973 openfile->mark ? _("In Selection: ") : "",
2974 lines, P_("line", "lines", lines),
2975 words, P_("word", "words", words),
2976 chars, P_("character", "characters", chars));
2977 }
2978 #endif /* !NANO_TINY */
2979
2980 /* Get verbatim input. */
do_verbatim_input(void)2981 void do_verbatim_input(void)
2982 {
2983 size_t count = 1;
2984 char *bytes;
2985
2986 /* TRANSLATORS: Shown when the next keystroke will be inserted verbatim. */
2987 statusline(INFO, _("Verbatim Input"));
2988 place_the_cursor();
2989
2990 /* Read in the first one or two bytes of the next keystroke. */
2991 bytes = get_verbatim_kbinput(edit, &count);
2992
2993 /* When something valid was obtained, unsuppress cursor-position display,
2994 * insert the bytes into the edit buffer, and blank the status bar. */
2995 if (count > 0) {
2996 if (ISSET(CONSTANT_SHOW) || ISSET(MINIBAR))
2997 lastmessage = VACUUM;
2998
2999 if (count < 999)
3000 inject(bytes, count);
3001
3002 wipe_statusbar();
3003 } else
3004 /* TRANSLATORS: An invalid verbatim Unicode code was typed. */
3005 statusline(AHEM, _("Invalid code"));
3006
3007 free(bytes);
3008 }
3009
3010 #ifdef ENABLE_WORDCOMPLETION
3011 /* Return a copy of the found completion candidate. */
copy_completion(char * text)3012 char *copy_completion(char *text)
3013 {
3014 char *word;
3015 size_t length = 0, index = 0;
3016
3017 /* Find the end of the candidate word to get its length. */
3018 while (is_word_char(&text[length], FALSE))
3019 length = step_right(text, length);
3020
3021 /* Now copy this candidate to a new string. */
3022 word = nmalloc(length + 1);
3023 while (index < length)
3024 word[index++] = *(text++);
3025 word[index] = '\0';
3026
3027 return word;
3028 }
3029
3030 /* Look at the fragment the user has typed, then search the current buffer for
3031 * the first word that starts with this fragment, and tentatively complete the
3032 * fragment. If the user types 'Complete' again, search and paste the next
3033 * possible completion. */
complete_a_word(void)3034 void complete_a_word(void)
3035 {
3036 char *shard, *completion = NULL;
3037 size_t start_of_shard, shard_length = 0;
3038 size_t i = 0, j = 0;
3039 completionstruct *some_word;
3040 #ifdef ENABLE_WRAPPING
3041 bool was_set_wrapping = ISSET(BREAK_LONG_LINES);
3042 #endif
3043
3044 /* If this is a fresh completion attempt... */
3045 if (pletion_line == NULL) {
3046 /* Clear the list of words of a previous completion run. */
3047 while (list_of_completions != NULL) {
3048 completionstruct *dropit = list_of_completions;
3049 list_of_completions = list_of_completions->next;
3050 free(dropit->word);
3051 free(dropit);
3052 }
3053
3054 /* Prevent a completion from being merged with typed text. */
3055 openfile->last_action = OTHER;
3056
3057 /* Initialize the starting point for searching. */
3058 pletion_line = openfile->filetop;
3059 pletion_x = 0;
3060
3061 /* Wipe the "No further matches" message. */
3062 wipe_statusbar();
3063 } else {
3064 /* Remove the attempted completion from the buffer. */
3065 do_undo();
3066 }
3067
3068 /* Find the start of the fragment that the user typed. */
3069 start_of_shard = openfile->current_x;
3070 while (start_of_shard > 0) {
3071 size_t oneleft = step_left(openfile->current->data, start_of_shard);
3072
3073 if (!is_word_char(&openfile->current->data[oneleft], FALSE))
3074 break;
3075 start_of_shard = oneleft;
3076 }
3077
3078 /* If there is no word fragment before the cursor, do nothing. */
3079 if (start_of_shard == openfile->current_x) {
3080 /* TRANSLATORS: Shown when no text is directly left of the cursor. */
3081 statusline(AHEM, _("No word fragment"));
3082 pletion_line = NULL;
3083 return;
3084 }
3085
3086 shard = nmalloc(openfile->current_x - start_of_shard + 1);
3087
3088 /* Copy the fragment that has to be searched for. */
3089 while (start_of_shard < openfile->current_x)
3090 shard[shard_length++] = openfile->current->data[start_of_shard++];
3091 shard[shard_length] = '\0';
3092
3093 /* Run through all of the lines in the buffer, looking for shard. */
3094 while (pletion_line != NULL) {
3095 ssize_t threshold = strlen(pletion_line->data) - shard_length - 1;
3096 /* The point where we can stop searching for shard. */
3097
3098 /* Traverse the whole line, looking for shard. */
3099 for (i = pletion_x; (ssize_t)i < threshold; i++) {
3100 /* If the first byte doesn't match, run on. */
3101 if (pletion_line->data[i] != shard[0])
3102 continue;
3103
3104 /* Compare the rest of the bytes in shard. */
3105 for (j = 1; j < shard_length; j++)
3106 if (pletion_line->data[i + j] != shard[j])
3107 break;
3108
3109 /* If not all of the bytes matched, continue searching. */
3110 if (j < shard_length)
3111 continue;
3112
3113 /* If the found match is not /longer/ than shard, skip it. */
3114 if (!is_word_char(&pletion_line->data[i + j], FALSE))
3115 continue;
3116
3117 /* If the match is not a separate word, skip it. */
3118 if (i > 0 && is_word_char(&pletion_line->data[
3119 step_left(pletion_line->data, i)], FALSE))
3120 continue;
3121
3122 /* If this match is the shard itself, ignore it. */
3123 if (pletion_line == openfile->current &&
3124 i == openfile->current_x - shard_length)
3125 continue;
3126
3127 completion = copy_completion(pletion_line->data + i);
3128
3129 /* Look among earlier attempted completions for a duplicate. */
3130 some_word = list_of_completions;
3131 while (some_word && strcmp(some_word->word, completion) != 0)
3132 some_word = some_word->next;
3133
3134 /* If we've already tried this word, skip it. */
3135 if (some_word != NULL) {
3136 free(completion);
3137 continue;
3138 }
3139
3140 /* Add the found word to the list of completions. */
3141 some_word = nmalloc(sizeof(completionstruct));
3142 some_word->word = completion;
3143 some_word->next = list_of_completions;
3144 list_of_completions = some_word;
3145
3146 #ifdef ENABLE_WRAPPING
3147 /* Temporarily disable wrapping so only one undo item is added. */
3148 UNSET(BREAK_LONG_LINES);
3149 #endif
3150 /* Inject the completion into the buffer. */
3151 inject(&completion[shard_length], strlen(completion) - shard_length);
3152
3153 #ifdef ENABLE_WRAPPING
3154 /* If needed, reenable wrapping and wrap the current line. */
3155 if (was_set_wrapping) {
3156 SET(BREAK_LONG_LINES);
3157 do_wrap();
3158 }
3159 #endif
3160 /* Set the position for a possible next search attempt. */
3161 pletion_x = ++i;
3162
3163 free(shard);
3164 return;
3165 }
3166
3167 pletion_line = pletion_line->next;
3168 pletion_x = 0;
3169 }
3170
3171 /* The search has reached the end of the file. */
3172 if (list_of_completions != NULL) {
3173 edit_refresh();
3174 statusline(AHEM, _("No further matches"));
3175 } else
3176 /* TRANSLATORS: Shown when there are zero possible completions. */
3177 statusline(AHEM, _("No matches"));
3178
3179 free(shard);
3180 }
3181 #endif /* ENABLE_WORDCOMPLETION */
3182