2005-07-24 19:57:51 +00:00
|
|
|
/**************************************************************************
|
2016-08-29 15:10:49 +00:00
|
|
|
* text.c -- This file is part of GNU nano. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
2021-01-11 13:21:03 +00:00
|
|
|
* Copyright (C) 1999-2011, 2013-2021 Free Software Foundation, Inc. *
|
2017-11-12 09:42:29 +00:00
|
|
|
* Copyright (C) 2014-2015 Mark Majeres *
|
2016-08-29 13:14:18 +00:00
|
|
|
* Copyright (C) 2016 Mike Scalora *
|
2016-12-07 19:37:36 +00:00
|
|
|
* Copyright (C) 2016 Sumedh Pendurkar *
|
2018-06-01 07:55:23 +00:00
|
|
|
* Copyright (C) 2018 Marco Diego Aurélio Mesquita *
|
2021-09-24 09:01:41 +00:00
|
|
|
* Copyright (C) 2015-2021 Benno Schulenberg *
|
2016-08-29 13:14:18 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano is free software: you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published *
|
|
|
|
* by the Free Software Foundation, either version 3 of the License, *
|
|
|
|
* or (at your option) any later version. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty *
|
|
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
|
|
* See the GNU General Public License for more details. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
2016-08-29 15:10:49 +00:00
|
|
|
* along with this program. If not, see http://www.gnu.org/licenses/. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
|
|
|
**************************************************************************/
|
|
|
|
|
2020-06-20 10:09:31 +00:00
|
|
|
#include "prototypes.h"
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <errno.h>
|
2005-07-24 19:57:51 +00:00
|
|
|
#include <fcntl.h>
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <string.h>
|
2020-09-13 17:40:39 +00:00
|
|
|
#include <time.h>
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <unistd.h>
|
2005-07-24 19:57:51 +00:00
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2019-12-06 11:19:32 +00:00
|
|
|
#if defined(__APPLE__) && !defined(st_mtim)
|
|
|
|
#define st_mtim st_mtimespec
|
|
|
|
#endif
|
|
|
|
|
2016-12-07 12:10:40 +00:00
|
|
|
#ifdef ENABLE_WORDCOMPLETION
|
2016-12-07 04:13:47 +00:00
|
|
|
static int pletion_x = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The x position in pletion_line of the last found completion. */
|
2021-09-11 15:57:02 +00:00
|
|
|
static completionstruct *list_of_completions;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A linked list of the completions that have been attempted. */
|
2016-12-07 04:13:47 +00:00
|
|
|
#endif
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Toggle the mark. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_mark(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->mark) {
|
|
|
|
openfile->mark = openfile->current;
|
|
|
|
openfile->mark_x = openfile->current_x;
|
2020-12-19 15:40:21 +00:00
|
|
|
openfile->softmark = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Mark Set"));
|
|
|
|
} else {
|
|
|
|
openfile->mark = NULL;
|
|
|
|
statusbar(_("Mark Unset"));
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
2021-06-16 09:19:23 +00:00
|
|
|
#endif
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2021-05-28 14:42:56 +00:00
|
|
|
/* Insert a tab. Or, if --tabstospaces is in effect, insert the number
|
|
|
|
* of spaces that a tab would normally take up at this position. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_tab(void)
|
|
|
|
{
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2020-01-16 18:01:45 +00:00
|
|
|
if (openfile->syntax && openfile->syntax->tab)
|
2020-02-12 10:12:34 +00:00
|
|
|
inject(openfile->syntax->tab, strlen(openfile->syntax->tab));
|
2018-10-29 19:40:55 +00:00
|
|
|
else
|
|
|
|
#endif
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-01-16 18:01:45 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
2020-08-30 02:57:45 +00:00
|
|
|
char *spaces = nmalloc(tabsize + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t length = tabsize - (xplustabs() % tabsize);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2019-09-18 13:20:08 +00:00
|
|
|
memset(spaces, ' ', length);
|
2017-12-29 18:27:33 +00:00
|
|
|
spaces[length] = '\0';
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2020-02-12 10:12:34 +00:00
|
|
|
inject(spaces, length);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(spaces);
|
|
|
|
} else
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2020-02-12 10:12:34 +00:00
|
|
|
inject((char *)"\t", 1);
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2006-04-28 13:19:56 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-09-10 11:25:20 +00:00
|
|
|
/* Add an indent to the given line. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void indent_a_line(linestruct *line, char *indentation)
|
2017-07-13 22:22:10 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t length = strlen(line->data);
|
|
|
|
size_t indent_len = strlen(indentation);
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2019-01-01 16:19:46 +00:00
|
|
|
/* If the requested indentation is empty, don't change the line. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (indent_len == 0)
|
|
|
|
return;
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Add the fabricated indentation to the beginning of the line. */
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, length + indent_len + 1);
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + indent_len, line->data, length + 1);
|
2019-12-06 11:34:57 +00:00
|
|
|
memcpy(line->data, indentation, indent_len);
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize += indent_len;
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Compensate for the change in the current line. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
2017-12-29 20:35:14 +00:00
|
|
|
openfile->mark_x += indent_len;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += indent_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2017-07-13 22:22:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Indent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* This inserts either a tab character or a tab's worth of spaces,
|
|
|
|
* depending on whether --tabstospaces is in effect. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_indent(void)
|
2006-04-28 13:19:56 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *indentation;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Use either all the marked lines or just the current line. */
|
2020-03-29 18:17:32 +00:00
|
|
|
get_range(&top, &bot);
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any leading empty lines. */
|
|
|
|
while (top != bot->next && top->data[0] == '\0')
|
|
|
|
top = top->next;
|
2017-08-16 15:16:18 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If all lines are empty, there is nothing to do. */
|
|
|
|
if (top == bot->next)
|
|
|
|
return;
|
2017-12-24 09:56:16 +00:00
|
|
|
|
2020-08-30 02:57:45 +00:00
|
|
|
indentation = nmalloc(tabsize + 1);
|
2017-08-16 15:16:18 +00:00
|
|
|
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax && openfile->syntax->tab)
|
|
|
|
indentation = mallocstrcpy(indentation, openfile->syntax->tab);
|
|
|
|
else
|
|
|
|
#endif
|
2021-05-28 14:42:56 +00:00
|
|
|
/* Set the indentation to either a bunch of spaces or a single tab. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
2019-09-18 13:20:08 +00:00
|
|
|
memset(indentation, ' ', tabsize);
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation[tabsize] = '\0';
|
|
|
|
} else {
|
|
|
|
indentation[0] = '\t';
|
|
|
|
indentation[1] = '\0';
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(INDENT, NULL);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, adding an indent to the non-empty ones,
|
|
|
|
* and recording whatever was added in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
char *real_indent = (line->data[0] == '\0') ? "" : indentation;
|
2017-07-07 16:06:55 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indent_a_line(line, real_indent);
|
|
|
|
update_multiline_undo(line->lineno, real_indent);
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(indentation);
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
2019-04-13 09:43:02 +00:00
|
|
|
ensure_firstcolumn_is_aligned();
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
2006-04-28 13:19:56 +00:00
|
|
|
}
|
|
|
|
|
2017-12-07 20:09:11 +00:00
|
|
|
/* Return the number of bytes of whitespace at the start of the given text,
|
|
|
|
* but at most a tab's worth. */
|
2017-07-07 18:53:00 +00:00
|
|
|
size_t length_of_white(const char *text)
|
|
|
|
{
|
2019-09-22 15:38:22 +00:00
|
|
|
size_t white_count = 0;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax && openfile->syntax->tab) {
|
|
|
|
size_t thelength = strlen(openfile->syntax->tab);
|
|
|
|
|
|
|
|
while (text[white_count] == openfile->syntax->tab[white_count])
|
|
|
|
if (++white_count == thelength)
|
|
|
|
return thelength;
|
|
|
|
|
|
|
|
white_count = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
if (*text == '\t')
|
2021-10-02 04:55:17 +00:00
|
|
|
return white_count + 1;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*text != ' ')
|
2019-09-22 15:38:22 +00:00
|
|
|
return white_count;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2019-09-22 15:38:22 +00:00
|
|
|
if (++white_count == tabsize)
|
2017-12-29 18:27:33 +00:00
|
|
|
return tabsize;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
text++;
|
|
|
|
}
|
2017-07-07 18:53:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-24 10:25:10 +00:00
|
|
|
/* Adjust the positions of mark and cursor when they are on the given line. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void compensate_leftward(linestruct *line, size_t leftshift)
|
2017-12-24 10:25:10 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->mark) {
|
|
|
|
if (openfile->mark_x < leftshift)
|
|
|
|
openfile->mark_x = 0;
|
|
|
|
else
|
|
|
|
openfile->mark_x -= leftshift;
|
|
|
|
}
|
2017-12-24 10:25:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->current) {
|
|
|
|
if (openfile->current_x < leftshift)
|
|
|
|
openfile->current_x = 0;
|
|
|
|
else
|
|
|
|
openfile->current_x -= leftshift;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2017-12-24 10:25:10 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 11:25:20 +00:00
|
|
|
/* Remove an indent from the given line. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void unindent_a_line(linestruct *line, size_t indent_len)
|
2017-07-13 22:38:26 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t length = strlen(line->data);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the indent is empty, don't change the line. */
|
|
|
|
if (indent_len == 0)
|
|
|
|
return;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Remove the first tab's worth of whitespace from this line. */
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data, line->data + indent_len, length - indent_len + 1);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= indent_len;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when they are affected. */
|
|
|
|
compensate_leftward(line, indent_len);
|
2017-07-13 22:38:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Unindent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* The removed indent can be a mixture of spaces plus at most one tab. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_unindent(void)
|
2017-07-10 20:48:50 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Use either all the marked lines or just the current line. */
|
2020-03-29 18:17:32 +00:00
|
|
|
get_range(&top, &bot);
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any leading lines that cannot be unindented. */
|
|
|
|
while (top != bot->next && length_of_white(top->data) == 0)
|
|
|
|
top = top->next;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If none of the lines can be unindented, there is nothing to do. */
|
|
|
|
if (top == bot->next)
|
|
|
|
return;
|
2017-12-07 20:09:11 +00:00
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(UNINDENT, NULL);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, removing their leading indent where
|
|
|
|
* possible, and saving the removed whitespace in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
size_t indent_len = length_of_white(line->data);
|
2020-02-20 15:52:48 +00:00
|
|
|
char *indentation = measured_copy(line->data, indent_len);
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
unindent_a_line(line, indent_len);
|
|
|
|
update_multiline_undo(line->lineno, indentation);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(indentation);
|
|
|
|
}
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
2019-04-12 12:00:14 +00:00
|
|
|
ensure_firstcolumn_is_aligned();
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
2017-07-10 20:48:50 +00:00
|
|
|
}
|
2017-07-13 23:15:58 +00:00
|
|
|
|
|
|
|
/* Perform an undo or redo for an indent or unindent action. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void handle_indent_action(undostruct *u, bool undoing, bool add_indent)
|
2017-07-13 23:15:58 +00:00
|
|
|
{
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group = u->grouping;
|
2019-05-24 16:56:34 +00:00
|
|
|
linestruct *line = line_from_number(group->top_line);
|
2018-04-01 09:36:27 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the indenter adjust it. */
|
|
|
|
if (!undoing)
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
/* For each line in the group, add or remove the individual indent. */
|
2020-02-28 11:34:28 +00:00
|
|
|
while (line != NULL && line->lineno <= group->bottom_line) {
|
2018-04-01 09:36:27 +00:00
|
|
|
char *blanks = group->indentations[line->lineno - group->top_line];
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
if (undoing ^ add_indent)
|
|
|
|
indent_a_line(line, blanks);
|
|
|
|
else
|
|
|
|
unindent_a_line(line, strlen(blanks));
|
2017-09-12 18:46:14 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When undoing, reposition the cursor to the recorded location. */
|
|
|
|
if (undoing)
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2017-07-13 23:15:58 +00:00
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif /* !NANO_TINY */
|
|
|
|
|
2016-09-08 19:00:51 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2016-05-25 20:13:50 +00:00
|
|
|
/* Test whether the given line can be uncommented, or add or remove a comment,
|
|
|
|
* depending on action. Return TRUE if the line is uncommentable, or when
|
|
|
|
* anything was added or removed; FALSE otherwise. */
|
2019-03-21 16:08:52 +00:00
|
|
|
bool comment_line(undo_type action, linestruct *line, const char *comment_seq)
|
2016-05-25 20:13:50 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t comment_seq_len = strlen(comment_seq);
|
|
|
|
const char *post_seq = strchr(comment_seq, '|');
|
|
|
|
/* The postfix, if this is a bracketing type comment sequence. */
|
|
|
|
size_t pre_len = post_seq ? post_seq++ - comment_seq : comment_seq_len;
|
|
|
|
/* Length of prefix. */
|
|
|
|
size_t post_len = post_seq ? comment_seq_len - pre_len - 1 : 0;
|
|
|
|
/* Length of postfix. */
|
|
|
|
size_t line_len = strlen(line->data);
|
|
|
|
|
2019-04-07 06:47:29 +00:00
|
|
|
if (!ISSET(NO_NEWLINES) && line == openfile->filebot)
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == COMMENT) {
|
|
|
|
/* Make room for the comment sequence(s), move the text right and
|
|
|
|
* copy them in. */
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, line_len + pre_len + post_len + 1);
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + pre_len, line->data, line_len + 1);
|
|
|
|
memmove(line->data, comment_seq, pre_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (post_len > 0)
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + pre_len + line_len, post_seq, post_len + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
openfile->totsize += pre_len + post_len;
|
|
|
|
|
|
|
|
/* If needed, adjust the position of the mark and of the cursor. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
|
|
|
openfile->mark_x += pre_len;
|
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += pre_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the line is commented, report it as uncommentable, or uncomment it. */
|
|
|
|
if (strncmp(line->data, comment_seq, pre_len) == 0 && (post_len == 0 ||
|
|
|
|
strcmp(line->data + line_len - post_len, post_seq) == 0)) {
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == PREFLIGHT)
|
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Erase the comment prefix by moving the non-comment part. */
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data, line->data + pre_len, line_len - pre_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Truncate the postfix if there was one. */
|
|
|
|
line->data[line_len - pre_len - post_len] = '\0';
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= pre_len + post_len;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when needed. */
|
|
|
|
compensate_leftward(line, pre_len);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-04-01 08:53:15 +00:00
|
|
|
/* Comment or uncomment the current line or the marked lines. */
|
|
|
|
void do_comment(void)
|
|
|
|
{
|
|
|
|
const char *comment_seq = GENERAL_COMMENT_CHARACTER;
|
|
|
|
undo_type action = UNCOMMENT;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2018-04-01 08:53:15 +00:00
|
|
|
bool empty, all_empty = TRUE;
|
|
|
|
|
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax)
|
|
|
|
comment_seq = openfile->syntax->comment;
|
|
|
|
|
|
|
|
if (*comment_seq == '\0') {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Commenting is not supported for this file type"));
|
2018-04-01 08:53:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Determine which lines to work on. */
|
2020-03-29 18:17:32 +00:00
|
|
|
get_range(&top, &bot);
|
2018-04-01 08:53:15 +00:00
|
|
|
|
|
|
|
/* If only the magic line is selected, don't do anything. */
|
2019-04-07 06:47:29 +00:00
|
|
|
if (top == bot && bot == openfile->filebot && !ISSET(NO_NEWLINES)) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Cannot comment past end of file"));
|
2018-04-01 08:53:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Figure out whether to comment or uncomment the selected line or lines. */
|
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
empty = white_string(line->data);
|
|
|
|
|
|
|
|
/* If this line is not blank and not commented, we comment all. */
|
|
|
|
if (!empty && !comment_line(PREFLIGHT, line, comment_seq)) {
|
|
|
|
action = COMMENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
all_empty = all_empty && empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If all selected lines are blank, we comment them. */
|
|
|
|
action = all_empty ? COMMENT : action;
|
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(action, NULL);
|
2018-04-01 08:53:15 +00:00
|
|
|
|
|
|
|
/* Store the comment sequence used for the operation, because it could
|
|
|
|
* change when the file name changes; we need to know what it was. */
|
2019-10-13 10:24:27 +00:00
|
|
|
openfile->current_undo->strdata = copy_of(comment_seq);
|
2018-04-01 08:53:15 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Comment/uncomment each of the selected lines when possible, and
|
|
|
|
* store undo data when a line changed. */
|
2021-05-28 14:42:56 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next)
|
|
|
|
if (comment_line(action, line, comment_seq))
|
2018-04-01 08:53:15 +00:00
|
|
|
update_multiline_undo(line->lineno, "");
|
|
|
|
|
|
|
|
set_modified();
|
2019-04-13 09:43:02 +00:00
|
|
|
ensure_firstcolumn_is_aligned();
|
2018-04-01 08:53:15 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
|
|
|
}
|
|
|
|
|
2016-05-25 20:13:50 +00:00
|
|
|
/* Perform an undo or redo for a comment or uncomment action. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void handle_comment_action(undostruct *u, bool undoing, bool add_comment)
|
2016-05-25 20:13:50 +00:00
|
|
|
{
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group = u->grouping;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the commenter adjust it. */
|
|
|
|
if (!undoing)
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (group) {
|
2020-02-27 12:28:01 +00:00
|
|
|
linestruct *line = line_from_number(group->top_line);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2020-02-27 12:28:01 +00:00
|
|
|
while (line != NULL && line->lineno <= group->bottom_line) {
|
2017-12-29 18:27:33 +00:00
|
|
|
comment_line(undoing ^ add_comment ?
|
2020-02-27 12:28:01 +00:00
|
|
|
COMMENT : UNCOMMENT, line, u->strdata);
|
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-09-12 18:46:14 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
group = group->next;
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When undoing, reposition the cursor to the recorded location. */
|
|
|
|
if (undoing)
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
#endif /* ENABLE_COMMENT */
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
2020-05-29 16:45:14 +00:00
|
|
|
#define redo_paste undo_cut
|
|
|
|
#define undo_paste redo_cut
|
2014-05-15 20:00:46 +00:00
|
|
|
|
2019-10-02 15:46:20 +00:00
|
|
|
/* Undo a cut, or redo a paste. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void undo_cut(undostruct *u)
|
2008-09-16 21:35:19 +00:00
|
|
|
{
|
2020-03-05 13:52:02 +00:00
|
|
|
goto_line_posx(u->head_lineno, (u->xflags & WAS_WHOLE_LINE) ? 0 : u->head_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
|
2021-09-15 10:56:27 +00:00
|
|
|
/* Clear an inherited anchor but not a user-placed one. */
|
|
|
|
if (!(u->xflags & HAD_ANCHOR_AT_START))
|
|
|
|
openfile->current->has_anchor = FALSE;
|
|
|
|
|
2020-03-01 11:10:21 +00:00
|
|
|
if (u->cutbuffer)
|
|
|
|
copy_from_buffer(u->cutbuffer);
|
2014-05-25 19:41:49 +00:00
|
|
|
|
2020-02-14 12:43:00 +00:00
|
|
|
/* If originally the last line was cut too, remove an extra magic line. */
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
|
2020-02-14 12:43:00 +00:00
|
|
|
openfile->filebot != openfile->current &&
|
|
|
|
openfile->filebot->prev->data[0] == '\0')
|
2018-12-27 22:09:19 +00:00
|
|
|
remove_magicline();
|
|
|
|
|
2020-03-29 12:10:50 +00:00
|
|
|
if (u->xflags & CURSOR_WAS_AT_HEAD)
|
2020-03-05 13:52:02 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-10-02 15:46:20 +00:00
|
|
|
/* Redo a cut, or undo a paste. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void redo_cut(undostruct *u)
|
2014-04-08 18:38:45 +00:00
|
|
|
{
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *oldcutbuffer = cutbuffer;
|
2017-07-01 11:25:11 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
cutbuffer = NULL;
|
2014-07-02 20:52:27 +00:00
|
|
|
|
2020-03-05 13:52:02 +00:00
|
|
|
openfile->mark = line_from_number(u->head_lineno);
|
|
|
|
openfile->mark_x = (u->xflags & WAS_WHOLE_LINE) ? 0 : u->head_x;
|
|
|
|
|
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2014-06-22 11:03:49 +00:00
|
|
|
|
2020-03-30 14:44:55 +00:00
|
|
|
do_snip(TRUE, FALSE, u->type == ZAP);
|
2014-06-22 11:03:49 +00:00
|
|
|
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(cutbuffer);
|
2017-12-29 18:27:33 +00:00
|
|
|
cutbuffer = oldcutbuffer;
|
2008-09-16 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Undo the last thing(s) we did. */
|
2008-07-10 20:13:04 +00:00
|
|
|
void do_undo(void)
|
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->current_undo;
|
2020-02-27 12:38:41 +00:00
|
|
|
linestruct *line = NULL, *intruder;
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *oldcutbuffer;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *data, *undidmsg = NULL;
|
2020-04-03 09:07:36 +00:00
|
|
|
size_t original_x, regain_from_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-04-04 10:53:03 +00:00
|
|
|
if (u == NULL) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Nothing to undo"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2019-05-24 17:02:14 +00:00
|
|
|
if (u->type <= REPLACE)
|
2020-02-27 12:28:01 +00:00
|
|
|
line = line_from_number(u->tail_lineno);
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
2018-05-27 15:53:36 +00:00
|
|
|
/* TRANSLATORS: The next thirteen strings describe actions
|
2018-05-27 16:35:52 +00:00
|
|
|
* that are undone or redone. They are all nouns, not verbs. */
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("addition");
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
|
2017-12-29 18:27:33 +00:00
|
|
|
remove_magicline();
|
2020-02-27 13:14:18 +00:00
|
|
|
memmove(line->data + u->head_x, line->data + u->head_x + strlen(u->strdata),
|
|
|
|
strlen(line->data + u->head_x) - strlen(u->strdata) + 1);
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case ENTER:
|
|
|
|
undidmsg = _("line break");
|
2020-04-03 09:07:36 +00:00
|
|
|
/* An <Enter> at the end of leading whitespace while autoindenting has
|
|
|
|
* deleted the whitespace, and stored an x position of zero. In that
|
|
|
|
* case, adjust the positions to return to and to scoop data from. */
|
|
|
|
original_x = (u->head_x == 0) ? u->tail_x : u->head_x;
|
|
|
|
regain_from_x = (u->head_x == 0) ? 0 : u->tail_x;
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, strlen(line->data) +
|
2020-04-03 09:07:36 +00:00
|
|
|
strlen(&u->strdata[regain_from_x]) + 1);
|
|
|
|
strcat(line->data, &u->strdata[regain_from_x]);
|
2020-04-06 15:42:29 +00:00
|
|
|
line->has_anchor |= line->next->has_anchor;
|
2020-02-27 12:28:01 +00:00
|
|
|
unlink_node(line->next);
|
|
|
|
renumber_from(line);
|
2020-04-03 09:07:36 +00:00
|
|
|
goto_line_posx(u->head_lineno, original_x);
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
|
|
|
case DEL:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("deletion");
|
2020-08-30 02:57:45 +00:00
|
|
|
data = nmalloc(strlen(line->data) + strlen(u->strdata) + 1);
|
2020-02-27 12:28:01 +00:00
|
|
|
strncpy(data, line->data, u->head_x);
|
2020-02-25 15:47:50 +00:00
|
|
|
strcpy(&data[u->head_x], u->strdata);
|
2020-02-27 12:28:01 +00:00
|
|
|
strcpy(&data[u->head_x + strlen(u->strdata)], &line->data[u->head_x]);
|
|
|
|
free(line->data);
|
|
|
|
line->data = data;
|
2020-02-25 16:09:51 +00:00
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case JOIN:
|
|
|
|
undidmsg = _("line join");
|
|
|
|
/* When the join was done by a Backspace at the tail of the file,
|
|
|
|
* and the nonewlines flag isn't set, do not re-add a newline that
|
|
|
|
* wasn't actually deleted; just position the cursor. */
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & WAS_BACKSPACE_AT_EOF) && !ISSET(NO_NEWLINES)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(openfile->filebot->lineno, 0);
|
|
|
|
break;
|
|
|
|
}
|
2020-02-28 16:02:34 +00:00
|
|
|
line->data[u->tail_x] = '\0';
|
2020-02-27 12:38:41 +00:00
|
|
|
intruder = make_new_node(line);
|
|
|
|
intruder->data = copy_of(u->strdata);
|
|
|
|
splice_node(line, intruder);
|
|
|
|
renumber_from(intruder);
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case REPLACE:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("replacement");
|
2021-01-01 18:39:07 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
|
|
|
|
remove_magicline();
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
data = u->strdata;
|
2020-02-27 12:28:01 +00:00
|
|
|
u->strdata = line->data;
|
|
|
|
line->data = data;
|
2020-02-27 14:09:36 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
2020-02-27 14:14:06 +00:00
|
|
|
case SPLIT_BEGIN:
|
|
|
|
undidmsg = _("addition");
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case SPLIT_END:
|
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
while (openfile->current_undo->type != SPLIT_BEGIN)
|
|
|
|
do_undo();
|
|
|
|
u = openfile->current_undo;
|
|
|
|
break;
|
|
|
|
#endif
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
|
|
|
undidmsg = _("erasure");
|
|
|
|
undo_cut(u);
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT_TO_EOF:
|
|
|
|
case CUT:
|
2019-02-25 09:04:10 +00:00
|
|
|
/* TRANSLATORS: Remember: these are nouns, NOT verbs. */
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("cut");
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_cut(u);
|
|
|
|
break;
|
|
|
|
case PASTE:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("paste");
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_paste(u);
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
|
2020-02-14 12:43:00 +00:00
|
|
|
openfile->filebot != openfile->current)
|
|
|
|
remove_magicline();
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case INSERT:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("insertion");
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
oldcutbuffer = cutbuffer;
|
|
|
|
cutbuffer = NULL;
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2020-02-25 16:09:51 +00:00
|
|
|
openfile->mark = line_from_number(u->tail_lineno);
|
|
|
|
openfile->mark_x = u->tail_x;
|
2020-03-29 11:14:17 +00:00
|
|
|
cut_marked_region();
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
u->cutbuffer = cutbuffer;
|
|
|
|
cutbuffer = oldcutbuffer;
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES) &&
|
2020-02-14 12:43:00 +00:00
|
|
|
openfile->filebot != openfile->current)
|
|
|
|
remove_magicline();
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2018-08-04 07:51:32 +00:00
|
|
|
undidmsg = u->strdata;
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2020-02-25 16:09:51 +00:00
|
|
|
openfile->current_y = u->tail_lineno;
|
2019-10-11 13:29:15 +00:00
|
|
|
adjust_viewport(STATIONARY);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
|
|
|
case COUPLE_END:
|
2020-03-05 11:29:22 +00:00
|
|
|
/* Remember the row of the cursor for a possible redo. */
|
|
|
|
openfile->current_undo->head_lineno = openfile->current_y;
|
2018-05-17 10:10:06 +00:00
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
do_undo();
|
|
|
|
do_undo();
|
|
|
|
do_undo();
|
|
|
|
return;
|
2017-12-29 18:27:33 +00:00
|
|
|
case INDENT:
|
|
|
|
handle_indent_action(u, TRUE, TRUE);
|
|
|
|
undidmsg = _("indent");
|
|
|
|
break;
|
|
|
|
case UNINDENT:
|
|
|
|
handle_indent_action(u, TRUE, FALSE);
|
|
|
|
undidmsg = _("unindent");
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_COMMENT
|
|
|
|
case COMMENT:
|
|
|
|
handle_comment_action(u, TRUE, TRUE);
|
|
|
|
undidmsg = _("comment");
|
|
|
|
break;
|
|
|
|
case UNCOMMENT:
|
|
|
|
handle_comment_action(u, TRUE, FALSE);
|
|
|
|
undidmsg = _("uncomment");
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (undidmsg && !pletion_line)
|
2019-02-24 15:05:22 +00:00
|
|
|
statusline(HUSH, _("Undid %s"), undidmsg);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2014-06-09 10:01:54 +00:00
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
openfile->last_action = OTHER;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
|
|
|
|
openfile->totsize = u->wassize;
|
|
|
|
|
2021-09-27 14:37:03 +00:00
|
|
|
/* When at the point where the buffer was last saved, unset "Modified". */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current_undo == openfile->last_saved) {
|
|
|
|
openfile->modified = FALSE;
|
|
|
|
titlebar(NULL);
|
|
|
|
} else
|
|
|
|
set_modified();
|
2008-07-10 20:13:04 +00:00
|
|
|
}
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Redo the last thing(s) we undid. */
|
2008-07-10 20:13:04 +00:00
|
|
|
void do_redo(void)
|
|
|
|
{
|
2020-02-27 12:38:41 +00:00
|
|
|
linestruct *line = NULL, *intruder;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *data, *redidmsg = NULL;
|
2020-02-28 12:21:57 +00:00
|
|
|
bool suppress_modification = FALSE;
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->undotop;
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u == NULL || u == openfile->current_undo) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Nothing to redo"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-10-29 17:02:13 +00:00
|
|
|
|
2019-04-04 10:47:20 +00:00
|
|
|
/* Find the item before the current one in the undo stack. */
|
|
|
|
while (u->next != openfile->current_undo)
|
2017-12-29 18:27:33 +00:00
|
|
|
u = u->next;
|
2015-10-28 20:49:16 +00:00
|
|
|
|
2019-05-24 17:02:14 +00:00
|
|
|
if (u->type <= REPLACE)
|
2020-02-27 12:28:01 +00:00
|
|
|
line = line_from_number(u->tail_lineno);
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("addition");
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
|
2017-12-29 18:27:33 +00:00
|
|
|
new_magicline();
|
2020-08-30 02:57:45 +00:00
|
|
|
data = nmalloc(strlen(line->data) + strlen(u->strdata) + 1);
|
2020-02-27 12:28:01 +00:00
|
|
|
strncpy(data, line->data, u->head_x);
|
2020-02-25 15:47:50 +00:00
|
|
|
strcpy(&data[u->head_x], u->strdata);
|
2020-02-27 12:28:01 +00:00
|
|
|
strcpy(&data[u->head_x + strlen(u->strdata)], &line->data[u->head_x]);
|
|
|
|
free(line->data);
|
|
|
|
line->data = data;
|
2020-02-25 16:09:51 +00:00
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case ENTER:
|
|
|
|
redidmsg = _("line break");
|
2020-02-27 12:28:01 +00:00
|
|
|
line->data[u->head_x] = '\0';
|
2020-02-27 12:38:41 +00:00
|
|
|
intruder = make_new_node(line);
|
|
|
|
intruder->data = copy_of(u->strdata);
|
|
|
|
splice_node(line, intruder);
|
|
|
|
renumber_from(intruder);
|
2020-02-25 16:09:51 +00:00
|
|
|
goto_line_posx(u->head_lineno + 1, u->tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case BACK:
|
|
|
|
case DEL:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("deletion");
|
2020-02-27 13:14:18 +00:00
|
|
|
memmove(line->data + u->head_x, line->data + u->head_x + strlen(u->strdata),
|
|
|
|
strlen(line->data + u->head_x) - strlen(u->strdata) + 1);
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case JOIN:
|
|
|
|
redidmsg = _("line join");
|
|
|
|
/* When the join was done by a Backspace at the tail of the file,
|
|
|
|
* and the nonewlines flag isn't set, do not join anything, as
|
|
|
|
* nothing was actually deleted; just position the cursor. */
|
2020-03-02 16:13:10 +00:00
|
|
|
if ((u->xflags & WAS_BACKSPACE_AT_EOF) && !ISSET(NO_NEWLINES)) {
|
2020-02-25 16:09:51 +00:00
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, strlen(line->data) + strlen(u->strdata) + 1);
|
2020-02-27 12:28:01 +00:00
|
|
|
strcat(line->data, u->strdata);
|
|
|
|
unlink_node(line->next);
|
|
|
|
renumber_from(line);
|
2020-02-25 16:09:51 +00:00
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case REPLACE:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("replacement");
|
2021-01-01 18:39:07 +00:00
|
|
|
if ((u->xflags & INCLUDED_LAST_LINE) && !ISSET(NO_NEWLINES))
|
|
|
|
new_magicline();
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
data = u->strdata;
|
2020-02-27 12:28:01 +00:00
|
|
|
u->strdata = line->data;
|
|
|
|
line->data = data;
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
openfile->current_undo = u;
|
|
|
|
while (openfile->current_undo->type != SPLIT_END)
|
|
|
|
do_redo();
|
|
|
|
u = openfile->current_undo;
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2020-02-27 14:14:06 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case SPLIT_END:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("addition");
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
|
|
|
#endif
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
|
|
|
redidmsg = _("erasure");
|
|
|
|
redo_cut(u);
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT_TO_EOF:
|
|
|
|
case CUT:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("cut");
|
2017-12-29 18:27:33 +00:00
|
|
|
redo_cut(u);
|
|
|
|
break;
|
|
|
|
case PASTE:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("paste");
|
2017-12-29 18:27:33 +00:00
|
|
|
redo_paste(u);
|
|
|
|
break;
|
|
|
|
case INSERT:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("insertion");
|
2020-02-25 15:47:50 +00:00
|
|
|
goto_line_posx(u->head_lineno, u->head_x);
|
2020-02-28 11:52:06 +00:00
|
|
|
if (u->cutbuffer)
|
|
|
|
copy_from_buffer(u->cutbuffer);
|
2020-02-28 12:21:57 +00:00
|
|
|
else
|
|
|
|
suppress_modification = TRUE;
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(u->cutbuffer);
|
2017-12-29 18:27:33 +00:00
|
|
|
u->cutbuffer = NULL;
|
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
|
|
|
openfile->current_undo = u;
|
|
|
|
do_redo();
|
|
|
|
do_redo();
|
|
|
|
do_redo();
|
|
|
|
return;
|
|
|
|
case COUPLE_END:
|
2018-08-04 07:51:32 +00:00
|
|
|
redidmsg = u->strdata;
|
2020-03-05 11:22:20 +00:00
|
|
|
goto_line_posx(u->tail_lineno, u->tail_x);
|
2020-03-05 11:29:22 +00:00
|
|
|
openfile->current_y = u->head_lineno;
|
2019-10-11 13:29:15 +00:00
|
|
|
adjust_viewport(STATIONARY);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case INDENT:
|
|
|
|
handle_indent_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("indent");
|
|
|
|
break;
|
|
|
|
case UNINDENT:
|
|
|
|
handle_indent_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("unindent");
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_COMMENT
|
|
|
|
case COMMENT:
|
|
|
|
handle_comment_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("comment");
|
|
|
|
break;
|
|
|
|
case UNCOMMENT:
|
|
|
|
handle_comment_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("uncomment");
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (redidmsg)
|
2019-02-24 15:05:22 +00:00
|
|
|
statusline(HUSH, _("Redid %s"), redidmsg);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2014-06-09 10:01:54 +00:00
|
|
|
openfile->current_undo = u;
|
|
|
|
openfile->last_action = OTHER;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
|
|
|
|
openfile->totsize = u->newsize;
|
|
|
|
|
2021-09-27 14:37:03 +00:00
|
|
|
/* When at the point where the buffer was last saved, unset "Modified". */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current_undo == openfile->last_saved) {
|
|
|
|
openfile->modified = FALSE;
|
|
|
|
titlebar(NULL);
|
2020-02-28 12:21:57 +00:00
|
|
|
} else if (!suppress_modification)
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
2008-07-10 20:13:04 +00:00
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
#endif /* !NANO_TINY */
|
|
|
|
|
2016-12-13 18:27:33 +00:00
|
|
|
/* Break the current line at the cursor position. */
|
2016-12-15 12:04:52 +00:00
|
|
|
void do_enter(void)
|
2005-07-25 02:41:59 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *newnode = make_new_node(openfile->current);
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t extra = 0;
|
2017-04-19 12:29:30 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *sampleline = openfile->current;
|
2017-12-29 18:27:33 +00:00
|
|
|
bool allblanks = FALSE;
|
|
|
|
|
2020-01-16 18:01:45 +00:00
|
|
|
if (ISSET(AUTOINDENT)) {
|
2018-05-28 07:57:24 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When doing automatic long-line wrapping and the next line is
|
|
|
|
* in this same paragraph, use its indentation as the model. */
|
2019-01-29 19:03:59 +00:00
|
|
|
if (ISSET(BREAK_LONG_LINES) && sampleline->next != NULL &&
|
2018-05-30 18:45:06 +00:00
|
|
|
inpar(sampleline->next) && !begpar(sampleline->next, 0))
|
2018-05-28 08:39:47 +00:00
|
|
|
sampleline = sampleline->next;
|
2018-05-28 07:57:24 +00:00
|
|
|
#endif
|
2018-05-22 19:10:10 +00:00
|
|
|
extra = indent_length(sampleline->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When breaking in the indentation, limit the automatic one. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (extra > openfile->current_x)
|
|
|
|
extra = openfile->current_x;
|
|
|
|
else if (extra == openfile->current_x)
|
|
|
|
allblanks = TRUE;
|
|
|
|
}
|
2018-05-28 07:57:24 +00:00
|
|
|
#endif /* NANO_TINY */
|
2020-08-30 02:57:45 +00:00
|
|
|
newnode->data = nmalloc(strlen(openfile->current->data +
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x) + extra + 1);
|
|
|
|
strcpy(&newnode->data[extra], openfile->current->data +
|
|
|
|
openfile->current_x);
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-01-16 18:01:45 +00:00
|
|
|
if (ISSET(AUTOINDENT)) {
|
2018-05-22 19:10:10 +00:00
|
|
|
/* Copy the whitespace from the sample line to the new one. */
|
|
|
|
strncpy(newnode->data, sampleline->data, extra);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there were only blanks before the cursor, trim them. */
|
|
|
|
if (allblanks)
|
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2019-04-04 12:44:44 +00:00
|
|
|
/* Make the current line end at the cursor position. */
|
|
|
|
openfile->current->data[openfile->current_x] = '\0';
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(ENTER, NULL);
|
2017-04-06 18:34:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the mark if it was on the current line after the cursor. */
|
|
|
|
if (openfile->mark == openfile->current &&
|
|
|
|
openfile->mark_x > openfile->current_x) {
|
|
|
|
openfile->mark = newnode;
|
|
|
|
openfile->mark_x += extra - openfile->current_x;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Insert the newly created line after the current one and renumber. */
|
2017-12-29 18:27:33 +00:00
|
|
|
splice_node(openfile->current, newnode);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(newnode);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Put the cursor on the new line, after any automatic whitespace. */
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current = newnode;
|
|
|
|
openfile->current_x = extra;
|
|
|
|
openfile->placewewant = xplustabs();
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize++;
|
|
|
|
set_modified();
|
2005-09-13 04:45:46 +00:00
|
|
|
|
2015-11-11 19:15:36 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-07-22 07:08:35 +00:00
|
|
|
if (ISSET(AUTOINDENT) && !allblanks)
|
|
|
|
openfile->totsize += extra;
|
2017-12-29 18:27:33 +00:00
|
|
|
update_undo(ENTER);
|
2015-11-11 19:15:36 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2018-02-16 10:40:03 +00:00
|
|
|
focusing = FALSE;
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-03-08 11:48:41 +00:00
|
|
|
/* Discard undo items that are newer than the given one, or all if NULL. */
|
2020-03-08 11:54:47 +00:00
|
|
|
void discard_until(const undostruct *thisitem)
|
2015-12-03 08:50:34 +00:00
|
|
|
{
|
2020-03-08 11:54:47 +00:00
|
|
|
undostruct *dropit = openfile->undotop;
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
while (dropit != NULL && dropit != thisitem) {
|
2020-03-08 11:54:47 +00:00
|
|
|
openfile->undotop = dropit->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
free(dropit->strdata);
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(dropit->cutbuffer);
|
2017-12-29 18:27:33 +00:00
|
|
|
group = dropit->grouping;
|
|
|
|
while (group != NULL) {
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *next = group->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
free_chararray(group->indentations,
|
2021-03-05 17:43:43 +00:00
|
|
|
group->bottom_line - group->top_line + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
free(group);
|
|
|
|
group = next;
|
|
|
|
}
|
2018-03-11 19:06:24 +00:00
|
|
|
free(dropit);
|
2020-03-08 11:54:47 +00:00
|
|
|
dropit = openfile->undotop;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust the pointer to the top of the undo stack. */
|
2020-03-08 11:54:47 +00:00
|
|
|
openfile->current_undo = (undostruct *)thisitem;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Prevent a chain of editing actions from continuing. */
|
2020-03-08 11:54:47 +00:00
|
|
|
openfile->last_action = OTHER;
|
2015-12-03 08:50:34 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 17:31:23 +00:00
|
|
|
/* Add a new undo item of the given type to the top of the current pile. */
|
2019-10-09 17:12:07 +00:00
|
|
|
void add_undo(undo_type action, const char *message)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2019-10-02 15:39:45 +00:00
|
|
|
undostruct *u = nmalloc(sizeof(undostruct));
|
2020-03-05 14:19:12 +00:00
|
|
|
linestruct *thisline = openfile->current;
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2019-10-02 15:39:45 +00:00
|
|
|
/* Initialize the newly allocated undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
u->type = action;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->strdata = NULL;
|
|
|
|
u->cutbuffer = NULL;
|
2020-03-05 14:19:12 +00:00
|
|
|
u->head_lineno = thisline->lineno;
|
2020-02-25 15:47:50 +00:00
|
|
|
u->head_x = openfile->current_x;
|
2020-03-05 14:19:12 +00:00
|
|
|
u->tail_lineno = thisline->lineno;
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_x = openfile->current_x;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->wassize = openfile->totsize;
|
2018-08-09 03:38:16 +00:00
|
|
|
u->newsize = openfile->totsize;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->grouping = NULL;
|
2019-10-02 15:39:45 +00:00
|
|
|
u->xflags = 0;
|
|
|
|
|
|
|
|
/* Blow away any undone items. */
|
2020-03-08 11:54:47 +00:00
|
|
|
discard_until(openfile->current_undo);
|
2018-06-01 14:45:37 +00:00
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2018-06-01 14:45:37 +00:00
|
|
|
/* If some action caused automatic long-line wrapping, insert the
|
|
|
|
* SPLIT_BEGIN item underneath that action's undo item. Otherwise,
|
|
|
|
* just add the new item to the top of the undo stack. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->type == SPLIT_BEGIN) {
|
2020-02-27 14:14:06 +00:00
|
|
|
action = openfile->undotop->type;
|
2020-02-17 10:32:03 +00:00
|
|
|
u->wassize = openfile->undotop->wassize;
|
2017-12-29 18:27:33 +00:00
|
|
|
u->next = openfile->undotop->next;
|
|
|
|
openfile->undotop->next = u;
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
u->next = openfile->undotop;
|
|
|
|
openfile->undotop = u;
|
|
|
|
openfile->current_undo = u;
|
|
|
|
}
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Record the info needed to be able to undo each possible action. */
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
|
|
|
/* If a new magic line will be added, an undo should remove it. */
|
2020-03-05 14:19:12 +00:00
|
|
|
if (thisline == openfile->filebot)
|
2020-03-02 16:13:10 +00:00
|
|
|
u->xflags |= INCLUDED_LAST_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case ENTER:
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
|
|
|
/* If the next line is the magic line, don't ever undo this
|
|
|
|
* backspace, as it won't actually have deleted anything. */
|
2020-03-05 14:19:12 +00:00
|
|
|
if (thisline->next == openfile->filebot && thisline->data[0] != '\0')
|
2020-03-02 16:13:10 +00:00
|
|
|
u->xflags |= WAS_BACKSPACE_AT_EOF;
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
2017-12-29 18:27:33 +00:00
|
|
|
case DEL:
|
2020-02-28 18:05:09 +00:00
|
|
|
/* When not at the end of a line, store the deleted character;
|
|
|
|
* otherwise, morph the undo item into a line join. */
|
2020-03-05 14:19:12 +00:00
|
|
|
if (thisline->data[openfile->current_x] != '\0') {
|
|
|
|
int charlen = char_length(thisline->data + u->head_x);
|
2020-02-28 18:24:31 +00:00
|
|
|
|
2020-03-05 14:19:12 +00:00
|
|
|
u->strdata = measured_copy(thisline->data + u->head_x, charlen);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->type == BACK)
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_x += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-02-28 18:05:09 +00:00
|
|
|
action = JOIN;
|
2020-03-05 14:19:12 +00:00
|
|
|
if (thisline->next != NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->type == BACK) {
|
2020-03-05 14:19:12 +00:00
|
|
|
u->head_lineno = thisline->next->lineno;
|
2020-02-25 15:47:50 +00:00
|
|
|
u->head_x = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-03-05 14:19:12 +00:00
|
|
|
u->strdata = copy_of(thisline->next->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-02-28 17:59:04 +00:00
|
|
|
u->type = JOIN;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case REPLACE:
|
2020-03-05 14:19:12 +00:00
|
|
|
u->strdata = copy_of(thisline->data);
|
2021-01-01 19:09:42 +00:00
|
|
|
if (thisline == openfile->filebot && answer[0] != '\0')
|
2021-01-01 18:39:07 +00:00
|
|
|
u->xflags |= INCLUDED_LAST_LINE;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
case SPLIT_BEGIN:
|
|
|
|
case SPLIT_END:
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
case CUT_TO_EOF:
|
2020-03-29 12:10:50 +00:00
|
|
|
u->xflags |= (INCLUDED_LAST_LINE | CURSOR_WAS_AT_HEAD);
|
2021-09-15 10:56:27 +00:00
|
|
|
if (openfile->current->has_anchor)
|
|
|
|
u->xflags |= HAD_ANCHOR_AT_START;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT:
|
|
|
|
if (openfile->mark) {
|
2020-03-05 15:09:56 +00:00
|
|
|
if (mark_is_before_cursor()){
|
2020-02-28 14:21:36 +00:00
|
|
|
u->head_lineno = openfile->mark->lineno;
|
|
|
|
u->head_x = openfile->mark_x;
|
2020-03-29 12:10:50 +00:00
|
|
|
u->xflags |= MARK_WAS_SET;
|
2020-03-05 13:52:02 +00:00
|
|
|
} else {
|
|
|
|
u->tail_lineno = openfile->mark->lineno;
|
|
|
|
u->tail_x = openfile->mark_x;
|
2020-03-29 12:10:50 +00:00
|
|
|
u->xflags |= (MARK_WAS_SET | CURSOR_WAS_AT_HEAD);
|
2020-02-28 14:21:36 +00:00
|
|
|
}
|
2020-03-05 14:19:12 +00:00
|
|
|
if (u->tail_lineno == openfile->filebot->lineno)
|
2020-03-02 16:13:10 +00:00
|
|
|
u->xflags |= INCLUDED_LAST_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else if (!ISSET(CUT_FROM_CURSOR)) {
|
|
|
|
/* The entire line is being cut regardless of the cursor position. */
|
2020-03-29 12:21:44 +00:00
|
|
|
u->xflags |= (WAS_WHOLE_LINE | CURSOR_WAS_AT_HEAD);
|
2020-03-05 13:52:02 +00:00
|
|
|
u->tail_x = 0;
|
2020-03-29 12:21:44 +00:00
|
|
|
} else
|
|
|
|
u->xflags |= CURSOR_WAS_AT_HEAD;
|
2021-09-15 10:56:27 +00:00
|
|
|
if ((openfile->mark && mark_is_before_cursor() && openfile->mark->has_anchor) ||
|
|
|
|
((!openfile->mark || !mark_is_before_cursor()) && openfile->current->has_anchor))
|
|
|
|
u->xflags |= HAD_ANCHOR_AT_START;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case PASTE:
|
2019-03-21 16:18:50 +00:00
|
|
|
u->cutbuffer = copy_buffer(cutbuffer);
|
2021-01-01 19:24:55 +00:00
|
|
|
/* Fall-through. */
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case INSERT:
|
2020-03-05 14:19:12 +00:00
|
|
|
if (thisline == openfile->filebot)
|
2020-03-02 16:13:10 +00:00
|
|
|
u->xflags |= INCLUDED_LAST_LINE;
|
2019-10-13 15:31:53 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_lineno = openfile->current_y;
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_END:
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(_(message));
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case INDENT:
|
|
|
|
case UNINDENT:
|
2016-05-25 20:13:50 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2017-12-29 18:27:33 +00:00
|
|
|
case COMMENT:
|
|
|
|
case UNCOMMENT:
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2020-02-28 18:15:23 +00:00
|
|
|
default:
|
|
|
|
die("Bad undo type -- please report a bug\n");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
openfile->last_action = action;
|
2008-07-10 20:13:04 +00:00
|
|
|
}
|
|
|
|
|
2017-07-09 01:54:59 +00:00
|
|
|
/* Update a multiline undo item. This should be called once for each line
|
2018-04-01 09:24:44 +00:00
|
|
|
* affected by a multiple-line-altering feature. The indentation that is
|
|
|
|
* added or removed is saved separately for each line in the undo item. */
|
2017-07-09 01:54:59 +00:00
|
|
|
void update_multiline_undo(ssize_t lineno, char *indentation)
|
2016-05-25 20:13:50 +00:00
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->current_undo;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If there already is a group and the current line is contiguous with it,
|
|
|
|
* extend the group; otherwise, create a new group. */
|
|
|
|
if (u->grouping && u->grouping->bottom_line + 1 == lineno) {
|
2021-05-28 14:42:56 +00:00
|
|
|
size_t number_of_lines = lineno - u->grouping->top_line + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-05-28 14:42:56 +00:00
|
|
|
u->grouping->bottom_line = lineno;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-08-30 02:57:45 +00:00
|
|
|
u->grouping->indentations = nrealloc(u->grouping->indentations,
|
2017-12-29 18:27:33 +00:00
|
|
|
number_of_lines * sizeof(char *));
|
2019-10-13 10:24:27 +00:00
|
|
|
u->grouping->indentations[number_of_lines - 1] = copy_of(indentation);
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *born = nmalloc(sizeof(groupstruct));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
born->top_line = lineno;
|
|
|
|
born->bottom_line = lineno;
|
|
|
|
|
2021-05-28 14:42:56 +00:00
|
|
|
born->indentations = nmalloc(sizeof(char *));
|
|
|
|
born->indentations[0] = copy_of(indentation);
|
|
|
|
|
|
|
|
born->next = u->grouping;
|
|
|
|
u->grouping = born;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Store the file size after the change, to be used when redoing. */
|
|
|
|
u->newsize = openfile->totsize;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 18:25:21 +00:00
|
|
|
/* Update an undo item with (among other things) the file size and
|
|
|
|
* cursor position after the given action. */
|
2008-08-03 04:48:05 +00:00
|
|
|
void update_undo(undo_type action)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->undotop;
|
2020-02-13 17:47:29 +00:00
|
|
|
size_t datalen, newlen;
|
|
|
|
char *textposition;
|
2019-06-09 15:07:02 +00:00
|
|
|
int charlen;
|
2008-08-01 03:50:20 +00:00
|
|
|
|
2019-03-17 18:46:40 +00:00
|
|
|
if (u->type != action)
|
2020-02-28 18:15:23 +00:00
|
|
|
die("Mismatching undo type -- please report a bug\n");
|
2019-03-17 18:46:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
u->newsize = openfile->totsize;
|
2015-11-30 16:21:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
2018-03-05 09:18:11 +00:00
|
|
|
case ADD:
|
2020-02-25 15:47:50 +00:00
|
|
|
newlen = openfile->current_x - u->head_x;
|
2020-08-30 02:57:45 +00:00
|
|
|
u->strdata = nrealloc(u->strdata, newlen + 1);
|
2020-02-25 15:47:50 +00:00
|
|
|
strncpy(u->strdata, openfile->current->data + u->head_x, newlen);
|
2020-02-13 17:47:29 +00:00
|
|
|
u->strdata[newlen] = '\0';
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_x = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case ENTER:
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(openfile->current->data);
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_x = openfile->current_x;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
2018-03-05 09:18:11 +00:00
|
|
|
case DEL:
|
2020-02-13 16:25:27 +00:00
|
|
|
textposition = openfile->current->data + openfile->current_x;
|
|
|
|
charlen = char_length(textposition);
|
|
|
|
datalen = strlen(u->strdata);
|
2020-02-25 15:47:50 +00:00
|
|
|
if (openfile->current_x == u->head_x) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* They deleted more: add removed character after earlier stuff. */
|
2020-08-30 02:57:45 +00:00
|
|
|
u->strdata = nrealloc(u->strdata, datalen + charlen + 1);
|
2020-02-13 16:25:27 +00:00
|
|
|
strncpy(u->strdata + datalen, textposition, charlen);
|
|
|
|
u->strdata[datalen + charlen] = '\0';
|
2020-02-25 16:09:51 +00:00
|
|
|
u->tail_x = openfile->current_x;
|
2020-02-25 15:47:50 +00:00
|
|
|
} else if (openfile->current_x == u->head_x - charlen) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* They backspaced further: add removed character before earlier. */
|
2020-08-30 02:57:45 +00:00
|
|
|
u->strdata = nrealloc(u->strdata, datalen + charlen + 1);
|
2020-02-13 16:25:27 +00:00
|
|
|
memmove(u->strdata + charlen, u->strdata, datalen + 1);
|
|
|
|
strncpy(u->strdata, textposition, charlen);
|
2020-02-25 15:47:50 +00:00
|
|
|
u->head_x = openfile->current_x;
|
2020-02-28 17:59:04 +00:00
|
|
|
} else
|
2017-12-29 18:27:33 +00:00
|
|
|
/* They deleted *elsewhere* on the line: start a new undo item. */
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(u->type, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case REPLACE:
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
case SPLIT_END:
|
|
|
|
break;
|
|
|
|
#endif
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT_TO_EOF:
|
|
|
|
case CUT:
|
2018-10-24 02:25:22 +00:00
|
|
|
if (u->type == ZAP)
|
|
|
|
u->cutbuffer = cutbuffer;
|
2020-03-01 11:10:21 +00:00
|
|
|
else if (cutbuffer != NULL) {
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(u->cutbuffer);
|
|
|
|
u->cutbuffer = copy_buffer(cutbuffer);
|
2018-10-24 02:25:22 +00:00
|
|
|
}
|
2020-02-28 14:21:36 +00:00
|
|
|
if (!(u->xflags & MARK_WAS_SET)) {
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *bottomline = u->cutbuffer;
|
2019-05-01 10:24:26 +00:00
|
|
|
size_t count = 0;
|
2019-04-28 17:21:40 +00:00
|
|
|
|
|
|
|
/* Find the end of the cut for the undo/redo, using our copy. */
|
2019-05-01 10:24:26 +00:00
|
|
|
while (bottomline->next != NULL) {
|
2019-04-28 17:21:40 +00:00
|
|
|
bottomline = bottomline->next;
|
2019-05-01 10:24:26 +00:00
|
|
|
count++;
|
|
|
|
}
|
2020-03-05 13:52:02 +00:00
|
|
|
u->tail_lineno = u->head_lineno + count;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(CUT_FROM_CURSOR) || u->type == CUT_TO_EOF) {
|
2020-03-05 13:52:02 +00:00
|
|
|
u->tail_x = strlen(bottomline->data);
|
|
|
|
if (count == 0)
|
|
|
|
u->tail_x += u->head_x;
|
2020-02-28 17:59:04 +00:00
|
|
|
} else if (openfile->current == openfile->filebot && ISSET(NO_NEWLINES))
|
2020-03-05 13:52:02 +00:00
|
|
|
u->tail_x = strlen(bottomline->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2018-08-16 13:59:30 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_END:
|
2021-01-01 19:24:55 +00:00
|
|
|
case PASTE:
|
|
|
|
case INSERT:
|
2020-03-05 11:22:20 +00:00
|
|
|
u->tail_lineno = openfile->current->lineno;
|
|
|
|
u->tail_x = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
default:
|
2020-02-28 18:15:23 +00:00
|
|
|
die("Bad undo type -- please report a bug\n");
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2014-06-18 21:23:50 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2019-04-20 12:00:07 +00:00
|
|
|
/* When the current line is overlong, hard-wrap it at the furthest possible
|
2021-10-21 09:56:22 +00:00
|
|
|
* whitespace character, and prepend the excess part to an "overflow" line
|
|
|
|
* (when it already exists, otherwise create one). */
|
|
|
|
void do_wrap(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2019-04-19 07:44:57 +00:00
|
|
|
linestruct *line = openfile->current;
|
2019-04-20 12:00:07 +00:00
|
|
|
/* The line to be wrapped, if needed and possible. */
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t line_len = strlen(line->data);
|
2019-04-20 12:00:07 +00:00
|
|
|
/* The length of this line. */
|
2019-12-22 10:28:56 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-12-19 09:52:06 +00:00
|
|
|
size_t quot_len = quote_length(line->data);
|
|
|
|
/* The length of the quoting part of this line. */
|
|
|
|
size_t lead_len = quot_len + indent_length(line->data + quot_len);
|
|
|
|
/* The length of the quoting part plus subsequent whitespace. */
|
2019-12-22 10:28:56 +00:00
|
|
|
#else
|
|
|
|
size_t lead_len = indent_length(line->data);
|
|
|
|
#endif
|
2019-04-19 08:10:14 +00:00
|
|
|
size_t cursor_x = openfile->current_x;
|
|
|
|
/* The current cursor position, for comparison with the wrap point. */
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t wrap_loc;
|
2019-04-20 12:00:07 +00:00
|
|
|
/* The position in the line's text where we wrap. */
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *remainder;
|
|
|
|
/* The text after the wrap point. */
|
|
|
|
size_t rest_length;
|
|
|
|
/* The length of the remainder. */
|
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* First find the last blank character where we can break the line. */
|
2019-12-19 09:52:06 +00:00
|
|
|
wrap_loc = break_line(line->data + lead_len,
|
|
|
|
wrap_at - wideness(line->data, lead_len), FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* If no wrapping point was found before end-of-line, we don't wrap. */
|
2019-12-19 10:06:02 +00:00
|
|
|
if (wrap_loc < 0 || lead_len + wrap_loc == line_len)
|
2021-10-21 09:56:22 +00:00
|
|
|
return;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-12-19 10:06:02 +00:00
|
|
|
/* Adjust the wrap location to its position in the full line,
|
|
|
|
* and step forward to the character just after the blank. */
|
|
|
|
wrap_loc = lead_len + step_right(line->data + lead_len, wrap_loc);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* When now at end-of-line, no need to wrap. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line->data[wrap_loc] == '\0')
|
2021-10-21 09:56:22 +00:00
|
|
|
return;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(SPLIT_BEGIN, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
2019-04-26 08:37:04 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
|
|
|
bool autowhite = ISSET(AUTOINDENT);
|
|
|
|
|
2019-12-19 09:52:06 +00:00
|
|
|
if (quot_len > 0)
|
2019-04-26 08:37:04 +00:00
|
|
|
UNSET(AUTOINDENT);
|
|
|
|
#endif
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The remainder is the text that will be wrapped to the next line. */
|
|
|
|
remainder = line->data + wrap_loc;
|
|
|
|
rest_length = line_len - wrap_loc;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-21 09:58:08 +00:00
|
|
|
/* When prepending and the remainder of this line will not make the next
|
|
|
|
* line too long, then join the two lines, so that, after the line wrap,
|
|
|
|
* the remainder will effectively have been prefixed to the next line. */
|
2019-04-21 15:31:29 +00:00
|
|
|
if (openfile->spillage_line && openfile->spillage_line == line->next &&
|
2019-04-24 06:49:18 +00:00
|
|
|
rest_length + breadth(line->next->data) <= wrap_at) {
|
2019-04-21 09:58:08 +00:00
|
|
|
/* Go to the end of this line. */
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = line_len;
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the remainder doesn't end in a blank, add a space. */
|
2020-03-12 11:29:44 +00:00
|
|
|
if (!is_blank_char(remainder + step_left(remainder, rest_length))) {
|
2014-06-20 16:13:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(ADD, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
#endif
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, line_len + 2);
|
2017-12-29 18:27:33 +00:00
|
|
|
line->data[line_len] = ' ';
|
|
|
|
line->data[line_len + 1] = '\0';
|
|
|
|
rest_length++;
|
|
|
|
openfile->totsize++;
|
|
|
|
openfile->current_x++;
|
2014-06-20 16:13:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
update_undo(ADD);
|
2014-06-20 16:13:54 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-26 08:37:04 +00:00
|
|
|
/* Join the next line to this one. */
|
|
|
|
do_delete();
|
|
|
|
|
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-12-19 10:06:02 +00:00
|
|
|
/* If the leading part of the current line equals the leading part of
|
|
|
|
* what was the next line, then strip this second leading part. */
|
2019-04-26 08:37:04 +00:00
|
|
|
if (strncmp(line->data, line->data + openfile->current_x, lead_len) == 0)
|
|
|
|
for (size_t i = lead_len; i > 0; i--)
|
|
|
|
do_delete();
|
|
|
|
#endif
|
|
|
|
/* Remove any extra blanks. */
|
2020-03-12 11:29:44 +00:00
|
|
|
while (is_blank_char(&line->data[openfile->current_x]))
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Go to the wrap location. */
|
|
|
|
openfile->current_x = wrap_loc;
|
2017-12-02 09:06:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When requested, snip trailing blanks off the wrapped line. */
|
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
2020-03-08 13:08:49 +00:00
|
|
|
size_t rear_x = step_left(line->data, wrap_loc);
|
2019-06-09 17:37:56 +00:00
|
|
|
size_t typed_x = step_left(line->data, cursor_x);
|
2017-12-02 09:06:12 +00:00
|
|
|
|
2020-03-08 13:08:49 +00:00
|
|
|
while ((rear_x != typed_x || cursor_x >= wrap_loc) &&
|
2020-03-12 11:29:44 +00:00
|
|
|
is_blank_char(line->data + rear_x)) {
|
2020-03-08 13:08:49 +00:00
|
|
|
openfile->current_x = rear_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2020-03-08 13:08:49 +00:00
|
|
|
rear_x = step_left(line->data, rear_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-12-02 09:06:12 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Now split the line. */
|
|
|
|
do_enter();
|
2005-11-25 13:48:09 +00:00
|
|
|
|
2019-04-26 08:37:04 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
|
|
|
/* If the original line has quoting, copy it to the spillage line. */
|
2019-12-19 09:52:06 +00:00
|
|
|
if (quot_len > 0) {
|
2019-04-26 08:37:04 +00:00
|
|
|
line = line->next;
|
|
|
|
line_len = strlen(line->data);
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, lead_len + line_len + 1);
|
2019-04-26 08:37:04 +00:00
|
|
|
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + lead_len, line->data, line_len + 1);
|
2019-04-26 08:37:04 +00:00
|
|
|
strncpy(line->data, line->prev->data, lead_len);
|
|
|
|
|
|
|
|
openfile->current_x += lead_len;
|
2021-05-14 08:12:06 +00:00
|
|
|
openfile->totsize += lead_len;
|
2019-04-26 08:37:04 +00:00
|
|
|
#ifndef NANO_TINY
|
2021-05-14 07:53:28 +00:00
|
|
|
free(openfile->undotop->strdata);
|
2019-04-26 08:37:04 +00:00
|
|
|
update_undo(ENTER);
|
|
|
|
#endif
|
|
|
|
if (autowhite)
|
|
|
|
SET(AUTOINDENT);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-04-21 15:31:29 +00:00
|
|
|
openfile->spillage_line = openfile->current;
|
|
|
|
|
2019-04-19 08:10:14 +00:00
|
|
|
if (cursor_x < wrap_loc) {
|
2019-04-19 08:06:57 +00:00
|
|
|
openfile->current = openfile->current->prev;
|
2019-04-19 08:10:14 +00:00
|
|
|
openfile->current_x = cursor_x;
|
2019-04-21 15:31:29 +00:00
|
|
|
} else
|
2019-04-19 08:10:14 +00:00
|
|
|
openfile->current_x += (cursor_x - wrap_loc);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->placewewant = xplustabs();
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(SPLIT_END, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
2021-10-21 09:56:22 +00:00
|
|
|
refresh_needed = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-29 20:00:09 +00:00
|
|
|
#endif /* ENABLE_WRAPPING */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:39:30 +00:00
|
|
|
#if defined(ENABLE_HELP) || defined(ENABLED_WRAPORJUSTIFY)
|
2020-03-12 16:18:10 +00:00
|
|
|
/* Find the last blank in the given piece of text such that the display width
|
|
|
|
* to that point is at most (goal + 1). When there is no such blank, then find
|
|
|
|
* the first blank. Return the index of the last blank in that group of blanks.
|
|
|
|
* When snap_at_nl is TRUE, a newline character counts as a blank too. */
|
2020-03-12 15:45:52 +00:00
|
|
|
ssize_t break_line(const char *textstart, ssize_t goal, bool snap_at_nl)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2020-03-12 15:45:52 +00:00
|
|
|
const char *lastblank = NULL;
|
|
|
|
/* The point where the last blank was found, if any. */
|
|
|
|
const char *pointer = textstart;
|
|
|
|
/* An iterator through the given line of text. */
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t column = 0;
|
2020-03-12 15:45:52 +00:00
|
|
|
/* The column number that corresponds to the position of the pointer. */
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-03-11 12:46:46 +00:00
|
|
|
/* Skip over leading whitespace, where a line should never be broken. */
|
2020-03-12 15:45:52 +00:00
|
|
|
while (*pointer != '\0' && is_blank_char(pointer))
|
|
|
|
pointer += advance_over(pointer, &column);
|
2020-03-11 12:46:46 +00:00
|
|
|
|
2020-03-15 12:44:06 +00:00
|
|
|
/* Find the last blank that does not overshoot the target column.
|
|
|
|
* When treating a help text, do not break in the keystrokes area. */
|
2020-03-12 15:45:52 +00:00
|
|
|
while (*pointer != '\0' && ((ssize_t)column <= goal)) {
|
2020-03-15 13:04:57 +00:00
|
|
|
if (is_blank_char(pointer) && (!inhelp || column > 17 || goal < 40))
|
2020-03-12 15:45:52 +00:00
|
|
|
lastblank = pointer;
|
2019-12-19 15:16:09 +00:00
|
|
|
#ifdef ENABLE_HELP
|
2020-03-12 15:45:52 +00:00
|
|
|
else if (snap_at_nl && *pointer == '\n') {
|
|
|
|
lastblank = pointer;
|
2019-12-19 15:16:09 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-12-19 15:16:09 +00:00
|
|
|
#endif
|
2020-03-12 15:45:52 +00:00
|
|
|
pointer += advance_over(pointer, &column);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the whole line displays shorter than goal, we're done. */
|
2018-05-27 08:24:02 +00:00
|
|
|
if ((ssize_t)column <= goal)
|
2020-03-12 15:45:52 +00:00
|
|
|
return (pointer - textstart);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-04-25 15:51:45 +00:00
|
|
|
#ifdef ENABLE_HELP
|
2020-03-12 16:18:10 +00:00
|
|
|
/* When wrapping a help text and no blank was found, force a line break. */
|
2020-03-12 15:45:52 +00:00
|
|
|
if (snap_at_nl && lastblank == NULL)
|
|
|
|
return step_left(textstart, pointer - textstart);
|
2017-12-29 18:27:33 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* If no blank was found within the goal width, seek one after it. */
|
2020-03-12 15:57:24 +00:00
|
|
|
while (lastblank == NULL) {
|
|
|
|
if (*pointer == '\0')
|
|
|
|
return -1;
|
2020-03-12 15:45:52 +00:00
|
|
|
|
2020-03-12 15:57:24 +00:00
|
|
|
if (is_blank_char(pointer))
|
|
|
|
lastblank = pointer;
|
|
|
|
else
|
2020-03-12 15:45:52 +00:00
|
|
|
pointer += char_length(pointer);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-03-12 15:45:52 +00:00
|
|
|
pointer = lastblank + char_length(lastblank);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any consecutive blanks after the last blank. */
|
2020-03-12 15:45:52 +00:00
|
|
|
while (*pointer != '\0' && is_blank_char(pointer)) {
|
|
|
|
lastblank = pointer;
|
|
|
|
pointer += char_length(pointer);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2020-03-12 15:45:52 +00:00
|
|
|
return (lastblank - textstart);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2017-10-31 16:39:30 +00:00
|
|
|
#endif /* ENABLE_HELP || ENABLED_WRAPORJUSTIFY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-12-22 11:56:18 +00:00
|
|
|
#if !defined(NANO_TINY) || defined(ENABLED_WRAPORJUSTIFY)
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Return the length of the indentation part of the given line. The
|
|
|
|
* "indentation" of a line is the leading consecutive whitespace. */
|
2005-07-25 02:33:45 +00:00
|
|
|
size_t indent_length(const char *line)
|
|
|
|
{
|
2021-03-29 18:01:46 +00:00
|
|
|
const char *start = line;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2021-03-29 18:01:46 +00:00
|
|
|
while (*line != '\0' && is_blank_char(line))
|
|
|
|
line += char_length(line);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2021-03-29 18:01:46 +00:00
|
|
|
return (line - start);
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2019-12-22 11:56:18 +00:00
|
|
|
#endif
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:34:07 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Return the length of the quote part of the given line. The "quote part"
|
|
|
|
* of a line is the largest initial substring matching the quoting regex. */
|
2005-07-25 02:33:45 +00:00
|
|
|
size_t quote_length(const char *line)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
regmatch_t matches;
|
|
|
|
int rc = regexec("ereg, line, 1, &matches, 0);
|
|
|
|
|
|
|
|
if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1)
|
|
|
|
return 0;
|
2019-03-19 18:56:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return matches.rm_eo;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-26 10:27:48 +00:00
|
|
|
/* The maximum depth of recursion. This must be an even number. */
|
2020-05-29 16:45:14 +00:00
|
|
|
#define RECURSION_LIMIT 222
|
2018-05-26 10:27:48 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* Return TRUE when the given line is the beginning of a paragraph (BOP). */
|
2019-03-21 16:08:52 +00:00
|
|
|
bool begpar(const linestruct *const line, int depth)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2020-03-09 11:26:04 +00:00
|
|
|
size_t quot_len, indent_len, prev_dent_len;
|
2005-11-09 18:26:44 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If this is the very first line of the buffer, it counts as a BOP
|
|
|
|
* even when it contains no text. */
|
2019-03-21 16:23:49 +00:00
|
|
|
if (line == openfile->filetop)
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-26 10:27:48 +00:00
|
|
|
/* If recursion is going too deep, just say it's not a BOP. */
|
|
|
|
if (depth > RECURSION_LIMIT)
|
|
|
|
return FALSE;
|
|
|
|
|
2020-03-09 11:26:04 +00:00
|
|
|
quot_len = quote_length(line->data);
|
|
|
|
indent_len = indent_length(line->data + quot_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If this line contains no text, it is not a BOP. */
|
2020-03-09 11:26:04 +00:00
|
|
|
if (line->data[quot_len + indent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-12-18 16:19:04 +00:00
|
|
|
/* When requested, treat a line that starts with whitespace as a BOP. */
|
|
|
|
if (ISSET(BOOKSTYLE) && !ISSET(AUTOINDENT) && is_blank_char(line->data))
|
|
|
|
return TRUE;
|
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the quote part of the preceding line differs, this is a BOP. */
|
2020-03-09 11:26:04 +00:00
|
|
|
if (quot_len != quote_length(line->prev->data) ||
|
|
|
|
strncmp(line->data, line->prev->data, quot_len) != 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-03-09 11:26:04 +00:00
|
|
|
prev_dent_len = indent_length(line->prev->data + quot_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the preceding line contains no text, this is a BOP. */
|
2020-03-09 11:26:04 +00:00
|
|
|
if (line->prev->data[quot_len + prev_dent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-12-17 15:15:51 +00:00
|
|
|
/* If indentation of this and preceding line are equal, this is not a BOP. */
|
2020-03-09 11:26:04 +00:00
|
|
|
if (wideness(line->prev->data, quot_len + prev_dent_len) ==
|
|
|
|
wideness(line->data, quot_len + indent_len))
|
2018-05-21 08:19:40 +00:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* Otherwise, this is a BOP if the preceding line is not. */
|
2018-05-26 10:27:48 +00:00
|
|
|
return !begpar(line->prev, depth + 1);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-19 12:49:35 +00:00
|
|
|
/* Return TRUE when the given line is part of a paragraph: when it
|
|
|
|
* contains something more than quoting and leading whitespace. */
|
2019-03-21 16:08:52 +00:00
|
|
|
bool inpar(const linestruct *const line)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2020-03-09 11:26:04 +00:00
|
|
|
size_t quot_len = quote_length(line->data);
|
|
|
|
size_t indent_len = indent_length(line->data + quot_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-03-09 11:26:04 +00:00
|
|
|
return (line->data[quot_len + indent_len] != '\0');
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Find the first occurring paragraph in the forward direction. Return TRUE
|
|
|
|
* when a paragraph was found, and FALSE otherwise. Furthermore, return the
|
2020-03-09 09:08:06 +00:00
|
|
|
* first line and the number of lines of the paragraph. */
|
|
|
|
bool find_paragraph(linestruct **firstline, size_t *const linecount)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *line = *firstline;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-25 12:04:35 +00:00
|
|
|
/* When not currently in a paragraph, move forward to a line that is. */
|
|
|
|
while (!inpar(line) && line->next != NULL)
|
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-11-22 19:54:22 +00:00
|
|
|
*firstline = line;
|
2018-09-11 06:55:00 +00:00
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Move down to the last line of the paragraph (if any). */
|
2018-11-25 18:24:08 +00:00
|
|
|
do_para_end(&line);
|
2018-11-25 12:04:35 +00:00
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* When not in a paragraph now, there aren't any paragraphs left. */
|
|
|
|
if (!inpar(line))
|
2018-09-11 06:55:00 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* We found a paragraph; determine its number of lines. */
|
2020-03-09 09:08:06 +00:00
|
|
|
*linecount = line->lineno - (*firstline)->lineno + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Concatenate into a single line all the lines of the paragraph that starts at
|
2020-03-09 09:08:06 +00:00
|
|
|
* *line and consists of 'count' lines, skipping the quoting and indentation on
|
2019-06-08 11:58:28 +00:00
|
|
|
* all lines after the first. */
|
2020-03-09 13:25:41 +00:00
|
|
|
void concat_paragraph(linestruct *line, size_t count)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2020-03-09 09:08:06 +00:00
|
|
|
while (count > 1) {
|
2020-03-09 13:25:41 +00:00
|
|
|
linestruct *next_line = line->next;
|
2018-08-25 22:53:17 +00:00
|
|
|
size_t next_line_len = strlen(next_line->data);
|
2020-03-09 11:26:04 +00:00
|
|
|
size_t next_quot_len = quote_length(next_line->data);
|
|
|
|
size_t next_lead_len = next_quot_len +
|
|
|
|
indent_length(next_line->data + next_quot_len);
|
2020-03-09 13:25:41 +00:00
|
|
|
size_t line_len = strlen(line->data);
|
2018-08-25 22:53:17 +00:00
|
|
|
|
|
|
|
/* We're just about to tack the next line onto this one. If
|
|
|
|
* this line isn't empty, make sure it ends in a space. */
|
2020-03-09 13:25:41 +00:00
|
|
|
if (line_len > 0 && line->data[line_len - 1] != ' ') {
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, line_len + 2);
|
2020-03-09 13:25:41 +00:00
|
|
|
line->data[line_len++] = ' ';
|
|
|
|
line->data[line_len] = '\0';
|
2018-08-25 22:53:17 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data,
|
2018-12-26 18:55:39 +00:00
|
|
|
line_len + next_line_len - next_lead_len + 1);
|
2020-03-09 13:25:41 +00:00
|
|
|
strcat(line->data, next_line->data + next_lead_len);
|
2020-04-30 14:02:27 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
line->has_anchor |= next_line->has_anchor;
|
|
|
|
#endif
|
2018-08-25 22:53:17 +00:00
|
|
|
unlink_node(next_line);
|
2020-03-09 09:08:06 +00:00
|
|
|
count--;
|
2018-08-25 22:53:17 +00:00
|
|
|
}
|
2018-12-26 19:52:06 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-03-19 14:08:07 +00:00
|
|
|
/* Copy a character from one place to another. */
|
|
|
|
void copy_character(char **from, char **to)
|
|
|
|
{
|
|
|
|
int charlen = char_length(*from);
|
|
|
|
|
|
|
|
if (*from == *to) {
|
|
|
|
*from += charlen;
|
|
|
|
*to += charlen;
|
|
|
|
} else
|
|
|
|
while (--charlen >= 0)
|
|
|
|
*((*to)++) = *((*from)++);
|
|
|
|
}
|
|
|
|
|
2020-03-09 08:44:43 +00:00
|
|
|
/* In the given line, replace any series of blanks with a single space,
|
|
|
|
* but keep two spaces (if there are two) after any closing punctuation,
|
|
|
|
* and remove all blanks from the end of the line. Leave the first skip
|
|
|
|
* number of characters untreated. */
|
|
|
|
void squeeze(linestruct *line, size_t skip)
|
|
|
|
{
|
|
|
|
char *start = line->data + skip;
|
|
|
|
char *from = start, *to = start;
|
|
|
|
|
|
|
|
/* For each character, 1) when a blank, change it to a space, and pass over
|
|
|
|
* all blanks after it; 2) if it is punctuation, copy it plus a possible
|
|
|
|
* tailing bracket, and change at most two subsequent blanks to spaces, and
|
|
|
|
* pass over all blanks after these; 3) leave anything else unchanged. */
|
|
|
|
while (*from != '\0') {
|
2020-03-12 11:29:44 +00:00
|
|
|
if (is_blank_char(from)) {
|
2020-03-09 08:44:43 +00:00
|
|
|
from += char_length(from);
|
|
|
|
*(to++) = ' ';
|
|
|
|
|
2020-03-12 11:29:44 +00:00
|
|
|
while (*from != '\0' && is_blank_char(from))
|
2020-03-09 08:44:43 +00:00
|
|
|
from += char_length(from);
|
|
|
|
} else if (mbstrchr(punct, from) != NULL) {
|
|
|
|
copy_character(&from, &to);
|
|
|
|
|
|
|
|
if (*from != '\0' && mbstrchr(brackets, from) != NULL)
|
|
|
|
copy_character(&from, &to);
|
|
|
|
|
2020-03-12 11:29:44 +00:00
|
|
|
if (*from != '\0' && is_blank_char(from)) {
|
2020-03-09 08:44:43 +00:00
|
|
|
from += char_length(from);
|
|
|
|
*(to++) = ' ';
|
|
|
|
}
|
2020-03-12 11:29:44 +00:00
|
|
|
if (*from != '\0' && is_blank_char(from)) {
|
2020-03-09 08:44:43 +00:00
|
|
|
from += char_length(from);
|
|
|
|
*(to++) = ' ';
|
|
|
|
}
|
|
|
|
|
2020-03-12 11:29:44 +00:00
|
|
|
while (*from != '\0' && is_blank_char(from))
|
2020-03-09 08:44:43 +00:00
|
|
|
from += char_length(from);
|
|
|
|
} else
|
|
|
|
copy_character(&from, &to);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If there are spaces at the end of the line, remove them. */
|
|
|
|
while (to > start && *(to - 1) == ' ')
|
|
|
|
to--;
|
|
|
|
|
|
|
|
*to = '\0';
|
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Rewrap the given line (that starts with the given lead string which is of
|
|
|
|
* the given length), into lines that fit within the target width (wrap_at). */
|
2019-03-21 16:08:52 +00:00
|
|
|
void rewrap_paragraph(linestruct **line, char *lead_string, size_t lead_len)
|
2018-12-26 19:52:06 +00:00
|
|
|
{
|
|
|
|
ssize_t break_pos;
|
|
|
|
/* The x-coordinate where the current line is to be broken. */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-24 06:49:18 +00:00
|
|
|
while (breadth((*line)->data) > wrap_at) {
|
2018-12-26 13:56:29 +00:00
|
|
|
size_t line_len = strlen((*line)->data);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Find a point in the line where it can be broken. */
|
2018-12-26 13:56:29 +00:00
|
|
|
break_pos = break_line((*line)->data + lead_len,
|
2019-04-24 07:08:23 +00:00
|
|
|
wrap_at - wideness((*line)->data, lead_len), FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* If we can't break the line, or don't need to, we're done. */
|
2019-12-19 10:06:02 +00:00
|
|
|
if (break_pos < 0 || lead_len + break_pos == line_len)
|
2018-08-25 22:53:17 +00:00
|
|
|
break;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* Adjust the breaking position for the leading part and
|
|
|
|
* move it beyond the found whitespace character. */
|
|
|
|
break_pos += lead_len + 1;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Insert a new line after the current one, and copy the leading part
|
|
|
|
* plus the text after the breaking point into it. */
|
2018-12-26 13:56:29 +00:00
|
|
|
splice_node(*line, make_new_node(*line));
|
2020-08-30 02:57:45 +00:00
|
|
|
(*line)->next->data = nmalloc(lead_len + line_len - break_pos + 1);
|
2018-12-26 13:56:29 +00:00
|
|
|
strncpy((*line)->next->data, lead_string, lead_len);
|
|
|
|
strcpy((*line)->next->data + lead_len, (*line)->data + break_pos);
|
2018-05-27 15:11:18 +00:00
|
|
|
|
2019-12-19 11:07:36 +00:00
|
|
|
/* When requested, snip the one or two trailing spaces. */
|
2018-08-25 22:53:17 +00:00
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
2019-12-19 11:07:36 +00:00
|
|
|
while (break_pos > 0 && (*line)->data[break_pos - 1] == ' ')
|
2018-08-25 22:53:17 +00:00
|
|
|
break_pos--;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-12-30 17:00:28 +00:00
|
|
|
/* Now actually break the current line, and go to the next. */
|
2019-04-04 12:44:44 +00:00
|
|
|
(*line)->data[break_pos] = '\0';
|
2018-12-26 13:56:29 +00:00
|
|
|
*line = (*line)->next;
|
2018-08-25 22:53:17 +00:00
|
|
|
}
|
2017-11-22 18:26:47 +00:00
|
|
|
|
2018-12-26 14:03:37 +00:00
|
|
|
/* When possible, go to the line after the rewrapped paragraph. */
|
2018-12-26 13:37:36 +00:00
|
|
|
if ((*line)->next != NULL)
|
|
|
|
*line = (*line)->next;
|
2018-08-25 22:44:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Justify the lines of the given paragraph (that starts at *line, and consists
|
2020-03-09 09:08:06 +00:00
|
|
|
* of 'count' lines) so they all fit within the target width (wrap_at) and have
|
2019-03-19 18:56:58 +00:00
|
|
|
* their whitespace normalized. */
|
2020-03-09 09:08:06 +00:00
|
|
|
void justify_paragraph(linestruct **line, size_t count)
|
2018-12-26 19:52:06 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *sampleline;
|
2019-03-19 18:56:58 +00:00
|
|
|
/* The line from which the indentation is copied. */
|
2020-03-09 11:26:04 +00:00
|
|
|
size_t quot_len;
|
2018-12-26 19:52:06 +00:00
|
|
|
/* Length of the quote part. */
|
|
|
|
size_t lead_len;
|
|
|
|
/* Length of the quote part plus the indentation part. */
|
|
|
|
char *lead_string;
|
|
|
|
/* The quote+indent stuff that is copied from the sample line. */
|
|
|
|
|
|
|
|
/* The sample line is either the only line or the second line. */
|
2020-03-09 09:08:06 +00:00
|
|
|
sampleline = (count == 1 ? *line : (*line)->next);
|
2018-12-26 19:52:06 +00:00
|
|
|
|
|
|
|
/* Copy the leading part (quoting + indentation) of the sample line. */
|
2020-03-09 11:26:04 +00:00
|
|
|
quot_len = quote_length(sampleline->data);
|
|
|
|
lead_len = quot_len + indent_length(sampleline->data + quot_len);
|
2020-02-20 15:52:48 +00:00
|
|
|
lead_string = measured_copy(sampleline->data, lead_len);
|
2018-12-26 19:52:06 +00:00
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Concatenate all lines of the paragraph into a single line. */
|
2020-03-09 13:25:41 +00:00
|
|
|
concat_paragraph(*line, count);
|
2018-12-26 19:52:06 +00:00
|
|
|
|
|
|
|
/* Change all blank characters to spaces and remove excess spaces. */
|
2020-03-09 11:26:04 +00:00
|
|
|
squeeze(*line, quot_len + indent_length((*line)->data + quot_len));
|
2018-12-26 19:52:06 +00:00
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Rewrap the line into multiple lines, accounting for the leading part. */
|
2018-12-26 19:52:06 +00:00
|
|
|
rewrap_paragraph(line, lead_string, lead_len);
|
|
|
|
|
|
|
|
free(lead_string);
|
|
|
|
}
|
|
|
|
|
2021-06-13 10:06:24 +00:00
|
|
|
#define ONE_PARAGRAPH FALSE
|
|
|
|
#define WHOLE_BUFFER TRUE
|
|
|
|
|
2021-06-13 14:57:08 +00:00
|
|
|
/* Justify the current paragraph, or the entire buffer when whole_buffer is
|
2019-03-19 18:56:58 +00:00
|
|
|
* TRUE. But if the mark is on, justify only the marked text instead. */
|
2021-06-13 14:57:08 +00:00
|
|
|
void justify_text(bool whole_buffer)
|
2018-08-25 22:44:20 +00:00
|
|
|
{
|
2020-03-09 09:08:06 +00:00
|
|
|
size_t linecount;
|
2019-03-19 18:56:58 +00:00
|
|
|
/* The number of lines in the original paragraph. */
|
2020-03-08 12:57:04 +00:00
|
|
|
linestruct *startline;
|
|
|
|
/* The line where the paragraph or region starts. */
|
|
|
|
linestruct *endline;
|
|
|
|
/* The line where the paragraph or region ends. */
|
2020-03-08 13:04:29 +00:00
|
|
|
size_t start_x;
|
|
|
|
/* The x position where the paragraph or region starts. */
|
|
|
|
size_t end_x;
|
|
|
|
/* The x position where the paragraph or region ends. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *was_cutbuffer = cutbuffer;
|
2018-09-01 00:45:27 +00:00
|
|
|
/* The old cutbuffer, so we can justify in the current cutbuffer. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *jusline;
|
2018-09-01 00:45:27 +00:00
|
|
|
/* The line that we're justifying in the current cutbuffer. */
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-03-08 13:08:49 +00:00
|
|
|
bool before_eol = FALSE;
|
|
|
|
/* Whether the end of a marked region is before the end of its line. */
|
2020-03-11 11:23:07 +00:00
|
|
|
char *primary_lead = NULL;
|
|
|
|
/* The leading part (quoting + indentation) of the first line
|
|
|
|
* of the paragraph where the marked region begins. */
|
|
|
|
size_t primary_len = 0;
|
|
|
|
/* The length (in bytes) of the above first-line leading part. */
|
2020-03-11 11:53:39 +00:00
|
|
|
char *secondary_lead = NULL;
|
|
|
|
/* The leading part for lines after the first one. */
|
|
|
|
size_t secondary_len = 0;
|
|
|
|
/* The length of that later lead. */
|
2018-09-07 17:01:43 +00:00
|
|
|
|
2020-03-11 13:18:06 +00:00
|
|
|
/* TRANSLATORS: This one goes with Undid/Redid messages. */
|
2020-03-08 12:37:53 +00:00
|
|
|
add_undo(COUPLE_BEGIN, N_("justification"));
|
|
|
|
|
2018-09-10 22:34:33 +00:00
|
|
|
/* If the mark is on, do as Pico: treat all marked text as one paragraph. */
|
|
|
|
if (openfile->mark) {
|
2020-03-10 09:44:08 +00:00
|
|
|
size_t quot_len, fore_len, other_quot_len, other_white_len;
|
2020-03-09 13:11:47 +00:00
|
|
|
linestruct *sampleline;
|
2019-01-08 23:48:40 +00:00
|
|
|
|
2020-03-29 18:17:32 +00:00
|
|
|
get_region(&startline, &start_x, &endline, &end_x);
|
2018-09-10 22:34:33 +00:00
|
|
|
|
2020-03-05 15:57:16 +00:00
|
|
|
/* When the marked region is empty, do nothing. */
|
2020-03-08 13:04:29 +00:00
|
|
|
if (startline == endline && start_x == end_x) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Selection is empty"));
|
2020-03-08 12:37:53 +00:00
|
|
|
discard_until(openfile->undotop->next);
|
2020-03-05 15:57:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-09 11:26:04 +00:00
|
|
|
quot_len = quote_length(startline->data);
|
2020-03-10 09:44:08 +00:00
|
|
|
fore_len = quot_len + indent_length(startline->data + quot_len);
|
2019-01-08 23:48:40 +00:00
|
|
|
|
2020-03-09 10:33:16 +00:00
|
|
|
/* When the region starts IN the lead, take the whole lead. */
|
2020-03-10 09:44:08 +00:00
|
|
|
if (start_x <= fore_len)
|
2020-03-09 10:33:16 +00:00
|
|
|
start_x = 0;
|
|
|
|
|
2020-03-10 09:44:08 +00:00
|
|
|
/* Recede over blanks before the region. This effectively snips
|
|
|
|
* trailing blanks from what will become the preceding paragraph. */
|
2020-03-12 11:29:44 +00:00
|
|
|
while (start_x > 0 && is_blank_char(&startline->data[start_x - 1]))
|
2020-03-22 17:45:46 +00:00
|
|
|
start_x = step_left(startline->data, start_x);
|
2020-03-10 09:44:08 +00:00
|
|
|
|
|
|
|
quot_len = quote_length(endline->data);
|
|
|
|
fore_len = quot_len + indent_length(endline->data + quot_len);
|
|
|
|
|
|
|
|
/* When the region ends IN the lead, take the whole lead. */
|
|
|
|
if (0 < end_x && end_x < fore_len)
|
|
|
|
end_x = fore_len;
|
|
|
|
|
2020-03-23 10:00:17 +00:00
|
|
|
/* When not at the left edge, advance over blanks after the region. */
|
|
|
|
while (end_x > 0 && is_blank_char(&endline->data[end_x]))
|
2020-03-22 17:45:46 +00:00
|
|
|
end_x = step_right(endline->data, end_x);
|
2020-03-22 15:56:50 +00:00
|
|
|
|
2020-03-10 09:19:24 +00:00
|
|
|
sampleline = startline;
|
|
|
|
|
|
|
|
/* Find the first line of the paragraph in which the region starts. */
|
|
|
|
while (sampleline->prev && inpar(sampleline) && !begpar(sampleline, 0))
|
|
|
|
sampleline = sampleline->prev;
|
|
|
|
|
2020-03-10 09:56:05 +00:00
|
|
|
/* Ignore lines that contain no text. */
|
|
|
|
while (sampleline->next && !inpar(sampleline))
|
|
|
|
sampleline = sampleline->next;
|
|
|
|
|
2020-03-10 09:19:24 +00:00
|
|
|
/* Store the leading part that is to be used for the new paragraph. */
|
|
|
|
quot_len = quote_length(sampleline->data);
|
2020-03-11 11:23:07 +00:00
|
|
|
primary_len = quot_len + indent_length(sampleline->data + quot_len);
|
|
|
|
primary_lead = measured_copy(sampleline->data, primary_len);
|
2020-03-10 09:19:24 +00:00
|
|
|
|
|
|
|
if (sampleline->next && startline != endline)
|
|
|
|
sampleline = sampleline->next;
|
2020-03-09 13:11:47 +00:00
|
|
|
|
2019-01-08 23:48:40 +00:00
|
|
|
/* Copy the leading part that is to be used for the new paragraph after
|
|
|
|
* its first line (if any): the quoting of the first line, plus the
|
|
|
|
* indentation of the second line. */
|
2020-03-09 13:11:47 +00:00
|
|
|
other_quot_len = quote_length(sampleline->data);
|
|
|
|
other_white_len = indent_length(sampleline->data + other_quot_len);
|
|
|
|
|
2020-03-11 11:53:39 +00:00
|
|
|
secondary_len = quot_len + other_white_len;
|
2020-08-30 02:57:45 +00:00
|
|
|
secondary_lead = nmalloc(secondary_len + 1);
|
2020-03-09 13:11:47 +00:00
|
|
|
|
2020-03-11 11:53:39 +00:00
|
|
|
strncpy(secondary_lead, startline->data, quot_len);
|
|
|
|
strncpy(secondary_lead + quot_len, sampleline->data + other_quot_len,
|
2020-03-09 13:11:47 +00:00
|
|
|
other_white_len);
|
2020-03-11 11:53:39 +00:00
|
|
|
secondary_lead[secondary_len] = '\0';
|
2020-03-09 08:55:12 +00:00
|
|
|
|
2020-03-10 09:44:08 +00:00
|
|
|
/* Include preceding and succeeding leads into the marked region. */
|
2020-03-09 08:55:12 +00:00
|
|
|
openfile->mark = startline;
|
|
|
|
openfile->mark_x = start_x;
|
|
|
|
openfile->current = endline;
|
|
|
|
openfile->current_x = end_x;
|
|
|
|
|
2020-03-09 09:08:06 +00:00
|
|
|
linecount = endline->lineno - startline->lineno + (end_x > 0 ? 1 : 0);
|
2020-03-09 08:55:12 +00:00
|
|
|
|
|
|
|
/* Remember whether the end of the region was before the end-of-line. */
|
|
|
|
before_eol = endline->data[end_x] != '\0';
|
2018-09-10 22:34:33 +00:00
|
|
|
} else
|
2020-03-08 12:37:53 +00:00
|
|
|
#endif /* NANO_TINY */
|
2018-09-10 22:34:33 +00:00
|
|
|
{
|
2019-04-11 13:12:04 +00:00
|
|
|
/* When justifying the entire buffer, start at the top. Otherwise, when
|
|
|
|
* in a paragraph but not at its beginning, move back to its first line. */
|
2021-06-13 14:57:08 +00:00
|
|
|
if (whole_buffer)
|
2019-04-11 13:12:04 +00:00
|
|
|
openfile->current = openfile->filetop;
|
|
|
|
else if (inpar(openfile->current) && !begpar(openfile->current, 0))
|
|
|
|
do_para_begin(&openfile->current);
|
|
|
|
|
|
|
|
/* Find the first line of the paragraph(s) to be justified. If the
|
|
|
|
* search fails, there is nothing to justify, and we will be on the
|
|
|
|
* last line of the file, so put the cursor at the end of it. */
|
2020-03-09 09:08:06 +00:00
|
|
|
if (!find_paragraph(&openfile->current, &linecount)) {
|
2019-04-11 13:12:04 +00:00
|
|
|
openfile->current_x = strlen(openfile->filebot->data);
|
2020-03-22 12:41:28 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-03-08 12:37:53 +00:00
|
|
|
discard_until(openfile->undotop->next);
|
2020-03-22 12:41:28 +00:00
|
|
|
#endif
|
|
|
|
refresh_needed = TRUE;
|
2019-04-11 13:12:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-08 15:55:02 +00:00
|
|
|
/* Set the starting point of the paragraph. */
|
2020-03-08 12:57:04 +00:00
|
|
|
startline = openfile->current;
|
2020-03-08 13:04:29 +00:00
|
|
|
start_x = 0;
|
2018-09-01 00:45:27 +00:00
|
|
|
|
2020-03-08 15:55:02 +00:00
|
|
|
/* Set the end point of the paragraph. */
|
2021-06-13 14:57:08 +00:00
|
|
|
if (whole_buffer)
|
2020-03-08 15:55:02 +00:00
|
|
|
endline = openfile->filebot;
|
|
|
|
else {
|
|
|
|
endline = startline;
|
2020-03-09 09:08:06 +00:00
|
|
|
for (size_t count = linecount; count > 1; count--)
|
2020-03-08 15:55:02 +00:00
|
|
|
endline = endline->next;
|
|
|
|
}
|
2018-11-26 09:24:01 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
/* When possible, step one line further; otherwise, to line's end. */
|
2020-03-08 12:57:04 +00:00
|
|
|
if (endline->next != NULL) {
|
|
|
|
endline = endline->next;
|
2020-03-08 13:04:29 +00:00
|
|
|
end_x = 0;
|
2019-01-02 17:56:22 +00:00
|
|
|
} else
|
2020-03-08 13:04:29 +00:00
|
|
|
end_x = strlen(endline->data);
|
2018-09-10 22:34:33 +00:00
|
|
|
}
|
2018-08-27 02:46:44 +00:00
|
|
|
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(CUT, NULL);
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
2019-04-11 13:12:04 +00:00
|
|
|
/* Do the equivalent of a marked cut into an empty cutbuffer. */
|
|
|
|
cutbuffer = NULL;
|
2020-03-08 13:04:29 +00:00
|
|
|
extract_segment(startline, start_x, endline, end_x);
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(CUT);
|
2018-08-27 02:46:44 +00:00
|
|
|
|
2018-09-10 22:34:33 +00:00
|
|
|
if (openfile->mark) {
|
2020-03-09 13:28:57 +00:00
|
|
|
linestruct *line = cutbuffer;
|
2020-03-10 09:44:08 +00:00
|
|
|
size_t quot_len = quote_length(line->data);
|
|
|
|
size_t fore_len = quot_len + indent_length(line->data + quot_len);
|
2020-03-10 09:49:07 +00:00
|
|
|
size_t text_len = strlen(line->data) - fore_len;
|
2020-03-09 10:43:50 +00:00
|
|
|
|
2020-03-10 09:19:24 +00:00
|
|
|
/* If the extracted region begins with any leading part, trim it. */
|
2020-03-10 09:44:08 +00:00
|
|
|
if (fore_len > 0)
|
2020-03-10 09:49:07 +00:00
|
|
|
memmove(line->data, line->data + fore_len, text_len + 1);
|
2020-03-10 09:19:24 +00:00
|
|
|
|
|
|
|
/* Then copy back in the leading part that it should have. */
|
2020-03-11 11:23:07 +00:00
|
|
|
if (primary_len > 0) {
|
2020-08-30 02:57:45 +00:00
|
|
|
line->data = nrealloc(line->data, primary_len + text_len + 1);
|
2020-03-11 11:23:07 +00:00
|
|
|
memmove(line->data + primary_len, line->data, text_len + 1);
|
|
|
|
strncpy(line->data, primary_lead, primary_len);
|
2019-01-08 21:58:08 +00:00
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Now justify the extracted region. */
|
2020-03-09 13:25:41 +00:00
|
|
|
concat_paragraph(cutbuffer, linecount);
|
2020-03-11 11:23:07 +00:00
|
|
|
squeeze(cutbuffer, primary_len);
|
2020-03-11 11:53:39 +00:00
|
|
|
rewrap_paragraph(&line, secondary_lead, secondary_len);
|
2019-01-08 23:48:40 +00:00
|
|
|
|
2020-03-09 10:33:16 +00:00
|
|
|
/* If the marked region started in the middle of a line,
|
|
|
|
* insert a newline before the new paragraph. */
|
2020-03-08 13:04:29 +00:00
|
|
|
if (start_x > 0) {
|
2020-03-09 10:33:16 +00:00
|
|
|
cutbuffer->prev = make_new_node(NULL);
|
|
|
|
cutbuffer->prev->data = copy_of("");
|
|
|
|
cutbuffer->prev->next = cutbuffer;
|
|
|
|
cutbuffer = cutbuffer->prev;
|
2019-01-08 21:58:08 +00:00
|
|
|
}
|
|
|
|
|
2020-03-09 11:07:06 +00:00
|
|
|
/* If the marked region ended in the middle of a line,
|
|
|
|
* insert a newline after the new paragraph. */
|
2020-03-08 13:08:49 +00:00
|
|
|
if (end_x > 0 && before_eol) {
|
2019-04-28 17:21:40 +00:00
|
|
|
line->next = make_new_node(line);
|
2020-03-11 11:23:07 +00:00
|
|
|
line->next->data = copy_of(primary_lead);
|
2019-01-08 21:58:08 +00:00
|
|
|
}
|
|
|
|
|
2020-03-11 11:53:39 +00:00
|
|
|
free(secondary_lead);
|
2020-03-11 11:23:07 +00:00
|
|
|
free(primary_lead);
|
2018-09-10 22:34:33 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
2019-01-02 17:56:22 +00:00
|
|
|
/* Prepare to justify the text we just put in the cutbuffer. */
|
|
|
|
jusline = cutbuffer;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
/* Justify the current paragraph. */
|
2020-03-09 09:08:06 +00:00
|
|
|
justify_paragraph(&jusline, linecount);
|
2018-08-28 15:56:52 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
/* When justifying the entire buffer, find and justify all paragraphs. */
|
2021-06-13 14:57:08 +00:00
|
|
|
if (whole_buffer) {
|
2020-03-09 09:08:06 +00:00
|
|
|
while (find_paragraph(&jusline, &linecount)) {
|
|
|
|
justify_paragraph(&jusline, linecount);
|
2018-11-26 09:24:01 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
if (jusline->next == NULL)
|
|
|
|
break;
|
|
|
|
}
|
2018-11-26 09:24:01 +00:00
|
|
|
}
|
2018-09-01 00:45:27 +00:00
|
|
|
}
|
2018-08-31 17:42:41 +00:00
|
|
|
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
2021-09-17 08:06:21 +00:00
|
|
|
/* Wipe an anchor on the first paragraph if it was only inherited. */
|
2021-06-13 14:57:08 +00:00
|
|
|
if (whole_buffer && !openfile->mark && !cutbuffer->has_anchor)
|
2020-04-29 17:34:48 +00:00
|
|
|
openfile->current->has_anchor = FALSE;
|
2021-09-17 08:06:21 +00:00
|
|
|
|
|
|
|
add_undo(PASTE, NULL);
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
2018-11-26 08:25:32 +00:00
|
|
|
/* Do the equivalent of a paste of the justified text. */
|
2018-09-01 00:45:27 +00:00
|
|
|
ingraft_buffer(cutbuffer);
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(PASTE);
|
|
|
|
|
2020-03-05 17:57:03 +00:00
|
|
|
/* After justifying a backward-marked text, swap mark and cursor. */
|
2020-08-22 17:23:55 +00:00
|
|
|
if (openfile->mark && !mark_is_before_cursor()) {
|
2020-03-06 11:43:06 +00:00
|
|
|
linestruct *bottom = openfile->current;
|
|
|
|
size_t bottom_x = openfile->current_x;
|
|
|
|
|
|
|
|
openfile->current = openfile->mark;
|
|
|
|
openfile->current_x = openfile->mark_x;
|
|
|
|
openfile->mark = bottom;
|
|
|
|
openfile->mark_x = bottom_x;
|
2018-09-10 22:34:33 +00:00
|
|
|
}
|
2020-03-06 11:30:24 +00:00
|
|
|
|
2020-03-11 13:18:06 +00:00
|
|
|
add_undo(COUPLE_END, N_("justification"));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2021-09-17 08:06:21 +00:00
|
|
|
/* Report on the status bar what we justified. */
|
2018-09-10 22:34:33 +00:00
|
|
|
if (openfile->mark)
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Justified selection"));
|
2018-09-10 22:34:33 +00:00
|
|
|
else
|
|
|
|
#endif
|
2021-06-13 14:57:08 +00:00
|
|
|
if (whole_buffer)
|
2021-02-20 11:05:07 +00:00
|
|
|
statusline(REMARK, _("Justified file"));
|
2018-09-10 20:12:07 +00:00
|
|
|
else
|
|
|
|
statusbar(_("Justified paragraph"));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2021-09-17 08:06:21 +00:00
|
|
|
/* We're done justifying. Restore the cutbuffer. */
|
|
|
|
cutbuffer = was_cutbuffer;
|
|
|
|
|
2018-09-10 20:17:07 +00:00
|
|
|
/* Set the desired screen column (always zero, except at EOF). */
|
|
|
|
openfile->placewewant = xplustabs();
|
2018-12-10 16:36:03 +00:00
|
|
|
|
|
|
|
set_modified();
|
|
|
|
refresh_needed = TRUE;
|
2018-09-10 22:34:33 +00:00
|
|
|
shift_held = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Justify the current paragraph. */
|
2021-06-13 10:14:35 +00:00
|
|
|
void do_justify(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2021-06-13 10:14:35 +00:00
|
|
|
justify_text(ONE_PARAGRAPH);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Justify the entire file. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_full_justify(void)
|
|
|
|
{
|
2021-06-13 10:14:35 +00:00
|
|
|
justify_text(WHOLE_BUFFER);
|
2020-05-22 09:46:30 +00:00
|
|
|
ran_a_tool = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 16:34:07 +00:00
|
|
|
#endif /* ENABLE_JUSTIFY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
#if defined(ENABLE_SPELLER) || defined (ENABLE_COLOR)
|
|
|
|
/* Set up an argument list for executing the given command. */
|
|
|
|
void construct_argument_list(char ***arguments, char *command, char *filename)
|
|
|
|
{
|
2019-10-13 10:24:27 +00:00
|
|
|
char *copy_of_command = copy_of(command);
|
2018-04-22 10:00:26 +00:00
|
|
|
char *element = strtok(copy_of_command, " ");
|
2018-04-22 10:05:19 +00:00
|
|
|
int count = 2;
|
2018-04-22 08:29:06 +00:00
|
|
|
|
2018-04-22 10:00:26 +00:00
|
|
|
while (element != NULL) {
|
2020-08-30 02:57:45 +00:00
|
|
|
*arguments = nrealloc(*arguments, ++count * sizeof(char *));
|
2018-04-22 10:05:19 +00:00
|
|
|
(*arguments)[count - 3] = element;
|
2018-04-22 10:00:26 +00:00
|
|
|
element = strtok(NULL, " ");
|
2018-04-22 08:29:06 +00:00
|
|
|
}
|
|
|
|
|
2018-04-22 10:05:19 +00:00
|
|
|
(*arguments)[count - 2] = filename;
|
|
|
|
(*arguments)[count - 1] = NULL;
|
2018-04-22 08:29:06 +00:00
|
|
|
}
|
2020-06-22 06:39:59 +00:00
|
|
|
|
|
|
|
/* Open the specified file, and if that succeeds, remove the text of the marked
|
|
|
|
* region or of the entire buffer and read the file contents into its place. */
|
|
|
|
bool replace_buffer(const char *filename, undo_type action, const char *operation)
|
|
|
|
{
|
|
|
|
linestruct *was_cutbuffer = cutbuffer;
|
|
|
|
int descriptor;
|
|
|
|
FILE *stream;
|
|
|
|
|
|
|
|
descriptor = open_file(filename, FALSE, &stream);
|
|
|
|
|
|
|
|
if (descriptor < 0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
cutbuffer = NULL;
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
add_undo(COUPLE_BEGIN, operation);
|
|
|
|
|
|
|
|
/* Cut either the marked region or the whole buffer. */
|
|
|
|
add_undo(action, NULL);
|
|
|
|
do_snip(openfile->mark != NULL, openfile->mark == NULL, FALSE);
|
|
|
|
update_undo(action);
|
2020-06-22 12:00:23 +00:00
|
|
|
#else
|
|
|
|
do_snip(FALSE, TRUE, FALSE);
|
2018-04-22 08:29:06 +00:00
|
|
|
#endif
|
|
|
|
|
2020-06-22 06:39:59 +00:00
|
|
|
/* Discard what was cut. */
|
|
|
|
free_lines(cutbuffer);
|
|
|
|
cutbuffer = was_cutbuffer;
|
|
|
|
|
|
|
|
/* Insert the spell-checked file into the cleared area. */
|
|
|
|
read_file(stream, descriptor, filename, TRUE);
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
add_undo(COUPLE_END, operation);
|
|
|
|
#endif
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Execute the given program, with the given temp file as last argument. */
|
2020-08-01 09:56:52 +00:00
|
|
|
void treat(char *tempfile_name, char *theprogram, bool spelling)
|
2020-06-22 06:39:59 +00:00
|
|
|
{
|
2021-09-20 08:08:56 +00:00
|
|
|
ssize_t was_lineno = openfile->current->lineno;
|
|
|
|
size_t was_pww = openfile->placewewant;
|
|
|
|
size_t was_x = openfile->current_x;
|
2020-06-22 06:39:59 +00:00
|
|
|
bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
|
|
|
|
struct stat fileinfo;
|
2020-07-05 15:17:09 +00:00
|
|
|
long timestamp_sec = 0;
|
|
|
|
long timestamp_nsec = 0;
|
2020-06-22 06:39:59 +00:00
|
|
|
static char **arguments = NULL;
|
|
|
|
pid_t thepid;
|
2020-07-31 17:45:03 +00:00
|
|
|
int program_status, errornumber;
|
2020-06-22 06:39:59 +00:00
|
|
|
bool replaced = FALSE;
|
|
|
|
|
2020-07-05 15:17:09 +00:00
|
|
|
/* Stat the temporary file. If that succeeds and its size is zero,
|
|
|
|
* there is nothing to do; otherwise, store its time of modification. */
|
|
|
|
if (stat(tempfile_name, &fileinfo) == 0) {
|
2020-08-01 18:38:29 +00:00
|
|
|
if (fileinfo.st_size == 0) {
|
2020-08-11 18:10:40 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-08-01 18:38:29 +00:00
|
|
|
if (spelling && openfile->mark)
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Selection is empty"));
|
2020-08-01 18:38:29 +00:00
|
|
|
else
|
2020-08-11 18:10:40 +00:00
|
|
|
#endif
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Buffer is empty"));
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2020-08-01 18:38:29 +00:00
|
|
|
}
|
2020-06-22 06:39:59 +00:00
|
|
|
|
2020-07-05 15:17:09 +00:00
|
|
|
timestamp_sec = (long)fileinfo.st_mtim.tv_sec;
|
|
|
|
timestamp_nsec = (long)fileinfo.st_mtim.tv_nsec;
|
|
|
|
}
|
2020-06-22 06:39:59 +00:00
|
|
|
|
|
|
|
/* Exit from curses mode to give the program control of the terminal. */
|
|
|
|
endwin();
|
|
|
|
|
|
|
|
construct_argument_list(&arguments, theprogram, tempfile_name);
|
|
|
|
|
|
|
|
/* Fork a child process and run the given program in it. */
|
|
|
|
if ((thepid = fork()) == 0) {
|
|
|
|
execvp(arguments[0], arguments);
|
|
|
|
|
|
|
|
/* Terminate the child if the program is not found. */
|
|
|
|
exit(9);
|
2020-07-31 17:45:03 +00:00
|
|
|
} else if (thepid > 0) {
|
2020-07-31 18:34:50 +00:00
|
|
|
/* Block SIGWINCHes while waiting for the forked program to end,
|
|
|
|
* so nano doesn't get pushed past the wait(). */
|
|
|
|
block_sigwinch(TRUE);
|
|
|
|
wait(&program_status);
|
|
|
|
block_sigwinch(FALSE);
|
2020-08-04 15:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
errornumber = errno;
|
2020-06-22 06:39:59 +00:00
|
|
|
|
|
|
|
/* Restore the terminal state and reenter curses mode. */
|
|
|
|
terminal_init();
|
|
|
|
doupdate();
|
|
|
|
|
2020-07-31 17:45:03 +00:00
|
|
|
if (thepid < 0) {
|
|
|
|
statusline(ALERT, _("Could not fork: %s"), strerror(errornumber));
|
2021-01-11 18:36:34 +00:00
|
|
|
free(arguments[0]);
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2020-07-31 18:34:50 +00:00
|
|
|
} else if (!WIFEXITED(program_status) || WEXITSTATUS(program_status) > 2) {
|
2020-06-22 06:39:59 +00:00
|
|
|
statusline(ALERT, _("Error invoking '%s'"), arguments[0]);
|
2021-01-11 18:36:34 +00:00
|
|
|
free(arguments[0]);
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2020-06-22 06:39:59 +00:00
|
|
|
} else if (WEXITSTATUS(program_status) != 0)
|
|
|
|
statusline(ALERT, _("Program '%s' complained"), arguments[0]);
|
|
|
|
|
2021-01-11 18:36:34 +00:00
|
|
|
free(arguments[0]);
|
|
|
|
|
2020-06-22 06:39:59 +00:00
|
|
|
/* When the temporary file wasn't touched, say so and leave. */
|
2020-07-05 15:17:09 +00:00
|
|
|
if (timestamp_sec > 0 && stat(tempfile_name, &fileinfo) == 0 &&
|
|
|
|
(long)fileinfo.st_mtim.tv_sec == timestamp_sec &&
|
|
|
|
(long)fileinfo.st_mtim.tv_nsec == timestamp_nsec) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Nothing changed"));
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2020-06-22 06:39:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
/* Replace the marked text (or entire text) with the corrected text. */
|
|
|
|
if (spelling && openfile->mark) {
|
|
|
|
ssize_t was_mark_lineno = openfile->mark->lineno;
|
|
|
|
bool upright = mark_is_before_cursor();
|
|
|
|
|
|
|
|
replaced = replace_buffer(tempfile_name, CUT, "spelling correction");
|
|
|
|
|
|
|
|
/* Adjust the end point of the marked region for any change in
|
|
|
|
* length of the region's last line. */
|
|
|
|
if (upright)
|
2021-09-20 08:08:56 +00:00
|
|
|
was_x = openfile->current_x;
|
2020-06-22 06:39:59 +00:00
|
|
|
else
|
|
|
|
openfile->mark_x = openfile->current_x;
|
|
|
|
|
|
|
|
/* Restore the mark. */
|
|
|
|
openfile->mark = line_from_number(was_mark_lineno);
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
openfile->current = openfile->filetop;
|
|
|
|
openfile->current_x = 0;
|
|
|
|
|
|
|
|
replaced = replace_buffer(tempfile_name, CUT_TO_EOF,
|
|
|
|
/* TRANSLATORS: The next two go with Undid/Redid messages. */
|
|
|
|
(spelling ? N_("spelling correction") : N_("formatting")));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Go back to the old position. */
|
2021-09-20 08:08:56 +00:00
|
|
|
goto_line_posx(was_lineno, was_x);
|
2020-06-22 06:39:59 +00:00
|
|
|
if (was_at_eol || openfile->current_x > strlen(openfile->current->data))
|
|
|
|
openfile->current_x = strlen(openfile->current->data);
|
|
|
|
|
|
|
|
if (replaced) {
|
2020-07-16 10:41:01 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-06-22 06:39:59 +00:00
|
|
|
openfile->filetop->has_anchor = FALSE;
|
|
|
|
update_undo(COUPLE_END);
|
|
|
|
#endif
|
2020-07-16 10:41:01 +00:00
|
|
|
}
|
2020-06-22 06:39:59 +00:00
|
|
|
|
2021-09-20 08:08:56 +00:00
|
|
|
openfile->placewewant = was_pww;
|
2020-06-22 06:39:59 +00:00
|
|
|
adjust_viewport(STATIONARY);
|
|
|
|
|
|
|
|
if (spelling)
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Finished checking spelling"));
|
2020-06-22 06:39:59 +00:00
|
|
|
else
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Buffer has been processed"));
|
2020-06-22 06:39:59 +00:00
|
|
|
}
|
|
|
|
#endif /* ENABLE_SPELLER || ENABLE_COLOR */
|
|
|
|
|
2017-10-31 18:32:42 +00:00
|
|
|
#ifdef ENABLE_SPELLER
|
2018-04-25 09:49:25 +00:00
|
|
|
/* Let the user edit the misspelled word. Return FALSE if the user cancels. */
|
|
|
|
bool fix_spello(const char *word)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2020-08-25 10:10:51 +00:00
|
|
|
linestruct *was_edittop = openfile->edittop;
|
|
|
|
linestruct *was_current = openfile->current;
|
|
|
|
size_t was_firstcolumn = openfile->firstcolumn;
|
|
|
|
size_t was_x = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
bool proceed = FALSE;
|
2020-08-25 10:10:51 +00:00
|
|
|
int result;
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-08-25 10:10:51 +00:00
|
|
|
bool right_side_up = (openfile->mark && mark_is_before_cursor());
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t top_x, bot_x;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the mark is on, start at the beginning of the marked region. */
|
|
|
|
if (openfile->mark) {
|
2020-03-29 18:17:32 +00:00
|
|
|
get_region(&top, &top_x, &bot, &bot_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the region is marked normally, swap the end points, so that
|
|
|
|
* (current, current_x) (where searching starts) is at the top. */
|
|
|
|
if (right_side_up) {
|
|
|
|
openfile->current = top;
|
|
|
|
openfile->current_x = top_x;
|
|
|
|
openfile->mark = bot;
|
|
|
|
openfile->mark_x = bot_x;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
/* Otherwise, start from the top of the file. */
|
|
|
|
{
|
2019-03-21 16:23:49 +00:00
|
|
|
openfile->current = openfile->filetop;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the first whole occurrence of word. */
|
|
|
|
result = findnextstr(word, TRUE, INREGION, NULL, FALSE, NULL, 0);
|
|
|
|
|
|
|
|
/* If the word isn't found, alert the user; if it is, allow correction. */
|
|
|
|
if (result == 0) {
|
|
|
|
statusline(ALERT, _("Unfindable word: %s"), word);
|
2020-07-04 07:34:54 +00:00
|
|
|
lastmessage = VACUUM;
|
2017-12-29 18:27:33 +00:00
|
|
|
proceed = TRUE;
|
|
|
|
napms(2800);
|
|
|
|
} else if (result == 1) {
|
2018-09-29 16:14:43 +00:00
|
|
|
spotlighted = TRUE;
|
|
|
|
light_from_col = xplustabs();
|
2019-04-24 06:49:18 +00:00
|
|
|
light_to_col = light_from_col + breadth(word);
|
2017-05-12 16:07:32 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *saved_mark = openfile->mark;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
2017-05-12 16:07:32 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
2018-09-25 00:11:10 +00:00
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
put_cursor_at_end_of_answer();
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Let the user supply a correctly spelled alternative. */
|
2020-06-20 15:15:40 +00:00
|
|
|
proceed = (do_prompt(MSPELL, word, NULL, edit_refresh,
|
|
|
|
/* TRANSLATORS: This is a prompt. */
|
|
|
|
_("Edit a replacement")) != -1);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-29 16:14:43 +00:00
|
|
|
spotlighted = FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-25 00:11:10 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
openfile->mark = saved_mark;
|
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If a replacement was given, go through all occurrences. */
|
|
|
|
if (proceed && strcmp(word, answer) != 0) {
|
2020-08-25 10:10:51 +00:00
|
|
|
do_replace_loop(word, TRUE, was_current, &was_x);
|
2016-05-04 17:31:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* TRANSLATORS: Shown after fixing misspellings in one word. */
|
|
|
|
statusbar(_("Next word..."));
|
|
|
|
napms(400);
|
|
|
|
}
|
2016-03-31 11:27:16 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark) {
|
|
|
|
/* Restore the (compensated) end points of the marked region. */
|
|
|
|
if (right_side_up) {
|
|
|
|
openfile->current = openfile->mark;
|
|
|
|
openfile->current_x = openfile->mark_x;
|
|
|
|
openfile->mark = top;
|
|
|
|
openfile->mark_x = top_x;
|
|
|
|
} else {
|
|
|
|
openfile->current = top;
|
|
|
|
openfile->current_x = top_x;
|
|
|
|
}
|
|
|
|
} else
|
2016-05-04 15:49:37 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
|
|
|
/* Restore the (compensated) cursor position. */
|
2020-08-25 10:10:51 +00:00
|
|
|
openfile->current = was_current;
|
|
|
|
openfile->current_x = was_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the viewport to where it was. */
|
2020-08-25 10:10:51 +00:00
|
|
|
openfile->edittop = was_edittop;
|
|
|
|
openfile->firstcolumn = was_firstcolumn;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return proceed;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Run a spell-check on the given file, using 'spell' to produce a list of all
|
|
|
|
* misspelled words, then feeding those through 'sort' and 'uniq' to obtain an
|
|
|
|
* alphabetical list, which words are then offered one by one to the user for
|
2020-08-01 09:56:52 +00:00
|
|
|
* correction. */
|
|
|
|
void do_int_speller(const char *tempfile_name)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2019-03-30 18:08:17 +00:00
|
|
|
char *misspellings, *pointer, *oneword;
|
2019-04-11 07:14:30 +00:00
|
|
|
long pipesize;
|
2019-04-11 06:51:15 +00:00
|
|
|
size_t buffersize, bytesread, totalread;
|
2017-12-29 18:27:33 +00:00
|
|
|
int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
|
|
|
|
pid_t pid_spell, pid_sort, pid_uniq;
|
|
|
|
int spell_status, sort_status, uniq_status;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Create all three pipes up front. */
|
2020-08-01 09:49:28 +00:00
|
|
|
if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1) {
|
2020-08-06 06:40:17 +00:00
|
|
|
statusline(ALERT, _("Could not create pipe: %s"), strerror(errno));
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2020-08-01 09:49:28 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-08-03 09:13:35 +00:00
|
|
|
statusbar(_("Invoking spell checker..."));
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run spell in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_spell = fork()) == 0) {
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Child: open the temporary file that holds the text to be checked. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(6);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard input to the temporary file. */
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(tempfile_fd, STDIN_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(7);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard output to the write end of the first pipe. */
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(spell_fd[1], STDOUT_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(tempfile_fd);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(spell_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-11-25 15:57:04 +00:00
|
|
|
/* Try to run 'hunspell'; if that fails, fall back to 'spell'. */
|
2019-11-24 09:11:32 +00:00
|
|
|
execlp("hunspell", "hunspell", "-l", NULL);
|
2019-11-25 15:57:04 +00:00
|
|
|
execlp("spell", "spell", NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-11-24 09:11:32 +00:00
|
|
|
/* Indicate failure when neither speller was found. */
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Parent: close the unused write end of the first pipe. */
|
2005-07-25 02:33:45 +00:00
|
|
|
close(spell_fd[1]);
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run sort in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_sort = fork()) == 0) {
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard input to the read end of the first pipe. */
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(spell_fd[0], STDIN_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(7);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard output to the write end of the second pipe. */
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(sort_fd[1], STDOUT_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(spell_fd[0]);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(sort_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[1]);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Now run the sort program. Use -f to mix upper and lower case. */
|
2017-12-29 18:27:33 +00:00
|
|
|
execlp("sort", "sort", "-f", NULL);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(sort_fd[1]);
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run uniq in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_uniq = fork()) == 0) {
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(sort_fd[0], STDIN_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(7);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(uniq_fd[1], STDOUT_FILENO) < 0)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(sort_fd[0]);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(uniq_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
execlp("uniq", "uniq", NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* When some child process was not forked successfully... */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
|
2020-08-01 09:34:23 +00:00
|
|
|
statusline(ALERT, _("Could not fork: %s"), strerror(errno));
|
2020-08-03 09:36:35 +00:00
|
|
|
close(uniq_fd[0]);
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the system pipe buffer size. */
|
2019-04-11 07:14:30 +00:00
|
|
|
pipesize = fpathconf(uniq_fd[0], _PC_PIPE_BUF);
|
2019-04-11 06:51:15 +00:00
|
|
|
|
2019-04-11 07:14:30 +00:00
|
|
|
if (pipesize < 1) {
|
2020-08-01 09:49:28 +00:00
|
|
|
statusline(ALERT, _("Could not get size of pipe buffer"));
|
2020-08-03 09:36:35 +00:00
|
|
|
close(uniq_fd[0]);
|
2020-08-01 09:56:52 +00:00
|
|
|
return;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-02-03 12:50:33 +00:00
|
|
|
/* Leave curses mode so that error messages go to the original screen. */
|
|
|
|
endwin();
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Block SIGWINCHes while reading misspelled words from the third pipe. */
|
2019-03-30 18:31:42 +00:00
|
|
|
block_sigwinch(TRUE);
|
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
totalread = 0;
|
2019-04-11 07:14:30 +00:00
|
|
|
buffersize = pipesize + 1;
|
2020-08-30 02:57:45 +00:00
|
|
|
misspellings = nmalloc(buffersize);
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer = misspellings;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-11 07:14:30 +00:00
|
|
|
while ((bytesread = read(uniq_fd[0], pointer, pipesize)) > 0) {
|
2019-03-30 18:08:17 +00:00
|
|
|
totalread += bytesread;
|
2019-04-11 07:14:30 +00:00
|
|
|
buffersize += pipesize;
|
2020-08-30 02:57:45 +00:00
|
|
|
misspellings = nrealloc(misspellings, buffersize);
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer = misspellings + totalread;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
*pointer = '\0';
|
2005-07-25 02:33:45 +00:00
|
|
|
close(uniq_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-03-30 18:31:42 +00:00
|
|
|
block_sigwinch(FALSE);
|
|
|
|
|
2020-02-03 12:50:33 +00:00
|
|
|
/* Re-enter curses mode. */
|
|
|
|
terminal_init();
|
|
|
|
doupdate();
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Do any replacements case-sensitively, forward, and without regexes. */
|
2018-04-25 10:27:01 +00:00
|
|
|
SET(CASE_SENSITIVE);
|
|
|
|
UNSET(BACKWARDS_SEARCH);
|
|
|
|
UNSET(USE_REGEXP);
|
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer = misspellings;
|
|
|
|
oneword = misspellings;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-04-25 10:27:01 +00:00
|
|
|
/* Process each of the misspelled words. */
|
2019-03-30 18:08:17 +00:00
|
|
|
while (*pointer != '\0') {
|
|
|
|
if ((*pointer == '\r') || (*pointer == '\n')) {
|
|
|
|
*pointer = '\0';
|
|
|
|
if (oneword != pointer) {
|
|
|
|
if (!fix_spello(oneword)) {
|
|
|
|
oneword = pointer;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-03-30 18:08:17 +00:00
|
|
|
oneword = pointer + 1;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer++;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Special case: the last word doesn't end with '\r' or '\n'. */
|
2019-03-30 18:08:17 +00:00
|
|
|
if (oneword != pointer)
|
|
|
|
fix_spello(oneword);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
free(misspellings);
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Process the end of the three processes. */
|
|
|
|
waitpid(pid_spell, &spell_status, 0);
|
|
|
|
waitpid(pid_sort, &sort_status, 0);
|
|
|
|
waitpid(pid_uniq, &uniq_status, 0);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-05 13:04:18 +00:00
|
|
|
if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
|
2020-08-01 09:49:28 +00:00
|
|
|
statusline(ALERT, _("Error invoking \"uniq\""));
|
|
|
|
else if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
|
|
|
|
statusline(ALERT, _("Error invoking \"sort\""));
|
|
|
|
else if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
|
|
|
|
statusline(ALERT, _("Error invoking \"spell\""));
|
|
|
|
else
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Finished checking spelling"));
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Spell check the current file. If an alternate spell checker is
|
|
|
|
* specified, use it. Otherwise, use the internal spell checker. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_spell(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2019-10-16 09:46:22 +00:00
|
|
|
FILE *stream;
|
|
|
|
char *temp_name;
|
2018-04-25 09:33:22 +00:00
|
|
|
unsigned stash[sizeof(flags) / sizeof(flags[0])];
|
2019-10-16 09:46:22 +00:00
|
|
|
bool okay;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
ran_a_tool = TRUE;
|
|
|
|
|
2019-10-09 16:58:30 +00:00
|
|
|
if (in_restricted_mode())
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
2010-11-12 06:22:12 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
temp_name = safe_tempfile(&stream);
|
2016-01-02 16:01:04 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
if (temp_name == NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Save the settings of the global flags. */
|
|
|
|
memcpy(stash, flags, sizeof(flags));
|
|
|
|
|
|
|
|
/* Don't add an extra newline when writing out the (selected) text. */
|
2019-04-07 06:47:29 +00:00
|
|
|
SET(NO_NEWLINES);
|
2018-04-25 09:33:22 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark)
|
2021-09-29 13:23:49 +00:00
|
|
|
okay = write_region_to_file(temp_name, stream, TEMPORARY, OVERWRITE);
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2005-07-25 02:33:45 +00:00
|
|
|
#endif
|
2021-09-29 13:18:17 +00:00
|
|
|
okay = write_file(temp_name, stream, TEMPORARY, OVERWRITE, NONOTES);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
if (!okay) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
2019-10-16 09:46:22 +00:00
|
|
|
free(temp_name);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-05-12 11:50:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
blank_bottombars();
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2021-01-11 14:52:46 +00:00
|
|
|
if (alt_speller && *alt_speller)
|
2020-08-01 09:56:52 +00:00
|
|
|
treat(temp_name, alt_speller, TRUE);
|
2019-10-09 17:42:22 +00:00
|
|
|
else
|
2020-08-01 09:56:52 +00:00
|
|
|
do_int_speller(temp_name);
|
2018-09-23 12:51:40 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
unlink(temp_name);
|
|
|
|
free(temp_name);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Restore the settings of the global flags. */
|
|
|
|
memcpy(flags, stash, sizeof(flags));
|
|
|
|
|
2019-05-24 10:23:42 +00:00
|
|
|
/* Ensure the help lines will be redrawn and a selection is retained. */
|
2019-05-21 17:08:14 +00:00
|
|
|
currmenu = MMOST;
|
2019-05-24 10:23:42 +00:00
|
|
|
shift_held = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 18:32:42 +00:00
|
|
|
#endif /* ENABLE_SPELLER */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2019-10-26 09:18:38 +00:00
|
|
|
/* Run a linting program on the current buffer. */
|
2014-02-24 10:18:15 +00:00
|
|
|
void do_linter(void)
|
|
|
|
{
|
2019-04-11 07:36:49 +00:00
|
|
|
char *lintings, *pointer, *onelint;
|
2019-04-11 07:14:30 +00:00
|
|
|
long pipesize;
|
2019-04-11 07:36:49 +00:00
|
|
|
size_t buffersize, bytesread, totalread;
|
2019-10-26 09:18:38 +00:00
|
|
|
bool parsesuccess = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
int lint_status, lint_fd[2];
|
|
|
|
pid_t pid_lint;
|
2018-10-21 18:47:37 +00:00
|
|
|
bool helpless = ISSET(NO_HELP);
|
2017-12-29 18:27:33 +00:00
|
|
|
lintstruct *lints = NULL, *tmplint = NULL, *curlint = NULL;
|
2018-10-22 01:14:38 +00:00
|
|
|
time_t last_wait = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
ran_a_tool = TRUE;
|
|
|
|
|
2019-10-09 16:58:30 +00:00
|
|
|
if (in_restricted_mode())
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->linter) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("No linter is defined for this type of file"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2018-01-09 11:23:41 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
2018-01-09 11:23:41 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
|
|
|
|
|
|
|
if (openfile->modified) {
|
2019-02-10 14:40:08 +00:00
|
|
|
int choice = do_yesno_prompt(FALSE, _("Save modified buffer before linting?"));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-02-10 14:40:08 +00:00
|
|
|
if (choice == -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Cancelled"));
|
|
|
|
return;
|
2019-02-10 14:40:08 +00:00
|
|
|
} else if (choice == 1 && (do_writeout(FALSE, FALSE) != 1))
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a pipe up front. */
|
|
|
|
if (pipe(lint_fd) == -1) {
|
2020-08-06 06:40:17 +00:00
|
|
|
statusline(ALERT, _("Could not create pipe: %s"), strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
blank_bottombars();
|
2018-05-24 09:34:22 +00:00
|
|
|
currmenu = MLINTER;
|
2020-08-03 09:31:02 +00:00
|
|
|
statusbar(_("Invoking linter..."));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-11-17 07:15:02 +00:00
|
|
|
/* Fork a process to run the linter in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_lint = fork()) == 0) {
|
2021-01-11 18:36:34 +00:00
|
|
|
char **lintargs = NULL;
|
|
|
|
|
2019-11-17 07:15:02 +00:00
|
|
|
/* Redirect standard output and standard error into the pipe. */
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(lint_fd[1], STDOUT_FILENO) < 0)
|
2019-11-17 07:15:02 +00:00
|
|
|
exit(7);
|
2020-10-10 09:43:55 +00:00
|
|
|
if (dup2(lint_fd[1], STDERR_FILENO) < 0)
|
2019-11-17 07:15:02 +00:00
|
|
|
exit(8);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-11-17 07:15:02 +00:00
|
|
|
close(lint_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(lint_fd[1]);
|
|
|
|
|
2021-01-11 18:36:34 +00:00
|
|
|
construct_argument_list(&lintargs, openfile->syntax->linter, openfile->filename);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the linter program; we are using $PATH. */
|
|
|
|
execvp(lintargs[0], lintargs);
|
|
|
|
|
|
|
|
/* This is only reached when the linter is not found. */
|
|
|
|
exit(9);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent continues here. */
|
2014-07-16 08:53:16 +00:00
|
|
|
close(lint_fd[1]);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the child process was not forked successfully... */
|
|
|
|
if (pid_lint < 0) {
|
2020-08-01 09:34:23 +00:00
|
|
|
statusline(ALERT, _("Could not fork: %s"), strerror(errno));
|
2020-08-03 09:36:35 +00:00
|
|
|
close(lint_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the system pipe buffer size. */
|
2019-04-11 07:14:30 +00:00
|
|
|
pipesize = fpathconf(lint_fd[0], _PC_PIPE_BUF);
|
2019-04-11 06:51:15 +00:00
|
|
|
|
2019-04-11 07:14:30 +00:00
|
|
|
if (pipesize < 1) {
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("Could not get size of pipe buffer"));
|
2020-08-03 09:36:35 +00:00
|
|
|
close(lint_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2021-05-06 11:06:22 +00:00
|
|
|
/* Block resizing signals while reading from the pipe. */
|
|
|
|
block_sigwinch(TRUE);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Read in the returned syntax errors. */
|
2019-04-11 07:36:49 +00:00
|
|
|
totalread = 0;
|
|
|
|
buffersize = pipesize + 1;
|
2020-08-30 02:57:45 +00:00
|
|
|
lintings = nmalloc(buffersize);
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer = lintings;
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2019-04-11 07:36:49 +00:00
|
|
|
while ((bytesread = read(lint_fd[0], pointer, pipesize)) > 0) {
|
|
|
|
totalread += bytesread;
|
|
|
|
buffersize += pipesize;
|
2020-08-30 02:57:45 +00:00
|
|
|
lintings = nrealloc(lintings, buffersize);
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer = lintings + totalread;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2019-04-11 07:36:49 +00:00
|
|
|
*pointer = '\0';
|
2014-02-24 10:18:15 +00:00
|
|
|
close(lint_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-05-06 11:06:22 +00:00
|
|
|
block_sigwinch(FALSE);
|
|
|
|
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer = lintings;
|
|
|
|
onelint = lintings;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-05-07 10:40:25 +00:00
|
|
|
/* Now parse the output of the linter. */
|
2019-04-11 07:36:49 +00:00
|
|
|
while (*pointer != '\0') {
|
|
|
|
if ((*pointer == '\r') || (*pointer == '\n')) {
|
|
|
|
*pointer = '\0';
|
|
|
|
if (onelint != pointer) {
|
2017-12-29 18:27:33 +00:00
|
|
|
char *filename = NULL, *linestr = NULL, *maybecol = NULL;
|
2019-10-13 10:24:27 +00:00
|
|
|
char *message = copy_of(onelint);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-05-07 10:40:25 +00:00
|
|
|
/* The recognized format is "filename:line:column: message",
|
|
|
|
* where ":column" may be absent or be ",column" instead. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (strstr(message, ": ") != NULL) {
|
2019-04-11 07:36:49 +00:00
|
|
|
filename = strtok(onelint, ":");
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((linestr = strtok(NULL, ":")) != NULL) {
|
|
|
|
if ((maybecol = strtok(NULL, ":")) != NULL) {
|
|
|
|
ssize_t tmplineno = 0, tmpcolno = 0;
|
|
|
|
char *tmplinecol;
|
|
|
|
|
|
|
|
tmplineno = strtol(linestr, NULL, 10);
|
|
|
|
if (tmplineno <= 0) {
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer++;
|
2017-12-29 18:27:33 +00:00
|
|
|
free(message);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpcolno = strtol(maybecol, NULL, 10);
|
|
|
|
/* Check if the middle field is in comma format. */
|
|
|
|
if (tmpcolno <= 0) {
|
|
|
|
strtok(linestr, ",");
|
|
|
|
if ((tmplinecol = strtok(NULL, ",")) != NULL)
|
|
|
|
tmpcolno = strtol(tmplinecol, NULL, 10);
|
|
|
|
else
|
|
|
|
tmpcolno = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Nice. We have a lint message we can use. */
|
2019-10-26 09:18:38 +00:00
|
|
|
parsesuccess = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = curlint;
|
|
|
|
curlint = nmalloc(sizeof(lintstruct));
|
|
|
|
curlint->next = NULL;
|
|
|
|
curlint->prev = tmplint;
|
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint->prev->next = curlint;
|
2021-05-30 09:30:32 +00:00
|
|
|
curlint->msg = copy_of(strstr(message, ": ") + 2);
|
2017-12-29 18:27:33 +00:00
|
|
|
curlint->lineno = tmplineno;
|
|
|
|
curlint->colno = tmpcolno;
|
2019-10-13 10:24:27 +00:00
|
|
|
curlint->filename = copy_of(filename);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
if (lints == NULL)
|
|
|
|
lints = curlint;
|
|
|
|
}
|
|
|
|
}
|
2019-04-11 12:17:32 +00:00
|
|
|
}
|
|
|
|
free(message);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2019-04-11 07:36:49 +00:00
|
|
|
onelint = pointer + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer++;
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
|
|
|
|
2019-04-11 07:55:18 +00:00
|
|
|
free(lintings);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Process the end of the linting process. */
|
|
|
|
waitpid(pid_lint, &lint_status, 0);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!WIFEXITED(lint_status) || WEXITSTATUS(lint_status) > 2) {
|
2019-10-26 09:52:53 +00:00
|
|
|
statusline(ALERT, _("Error invoking '%s'"), openfile->syntax->linter);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2019-10-26 09:18:38 +00:00
|
|
|
if (!parsesuccess) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("Got 0 parsable lines from command: %s"),
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->syntax->linter);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2018-10-21 18:47:37 +00:00
|
|
|
if (helpless && LINES > 4) {
|
|
|
|
UNSET(NO_HELP);
|
|
|
|
window_init();
|
|
|
|
}
|
|
|
|
|
2018-10-17 18:17:03 +00:00
|
|
|
/* Show that we are in the linter now. */
|
|
|
|
titlebar(NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
bottombars(MLINTER);
|
2018-10-17 18:17:03 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
|
|
|
curlint = lints;
|
2016-02-11 17:25:37 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
int kbinput;
|
|
|
|
functionptrtype func;
|
2018-03-25 19:36:13 +00:00
|
|
|
struct stat lintfileinfo;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-03-25 19:36:13 +00:00
|
|
|
if (stat(curlint->filename, &lintfileinfo) != -1 &&
|
2020-05-28 12:31:15 +00:00
|
|
|
(openfile->statinfo == NULL ||
|
|
|
|
openfile->statinfo->st_ino != lintfileinfo.st_ino)) {
|
2018-03-25 19:41:46 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-24 16:28:59 +00:00
|
|
|
const openfilestruct *started_at = openfile;
|
2018-05-24 16:17:58 +00:00
|
|
|
|
2018-05-24 16:28:59 +00:00
|
|
|
openfile = openfile->next;
|
2020-05-28 12:31:15 +00:00
|
|
|
while (openfile != started_at && (openfile->statinfo == NULL ||
|
|
|
|
openfile->statinfo->st_ino != lintfileinfo.st_ino))
|
2018-05-24 16:28:59 +00:00
|
|
|
openfile = openfile->next;
|
2018-05-24 16:14:35 +00:00
|
|
|
|
2020-05-28 12:31:15 +00:00
|
|
|
if (openfile->statinfo == NULL ||
|
|
|
|
openfile->statinfo->st_ino != lintfileinfo.st_ino) {
|
2020-08-30 02:57:45 +00:00
|
|
|
char *msg = nmalloc(1024 + strlen(curlint->filename));
|
2019-02-20 16:39:04 +00:00
|
|
|
int choice;
|
2018-03-25 19:36:13 +00:00
|
|
|
|
|
|
|
sprintf(msg, _("This message is for unopened file %s,"
|
|
|
|
" open it in a new buffer?"), curlint->filename);
|
2019-02-20 16:39:04 +00:00
|
|
|
choice = do_yesno_prompt(FALSE, msg);
|
2018-10-17 18:17:03 +00:00
|
|
|
currmenu = MLINTER;
|
2018-03-25 19:36:13 +00:00
|
|
|
free(msg);
|
|
|
|
|
2019-02-20 16:39:04 +00:00
|
|
|
if (choice == -1) {
|
2018-03-25 19:36:13 +00:00
|
|
|
statusbar(_("Cancelled"));
|
2018-10-24 08:23:39 +00:00
|
|
|
break;
|
2019-02-20 16:39:04 +00:00
|
|
|
} else if (choice == 1) {
|
2018-03-25 19:36:13 +00:00
|
|
|
open_buffer(curlint->filename, TRUE);
|
|
|
|
} else {
|
2018-03-25 19:41:46 +00:00
|
|
|
#endif
|
2019-10-13 10:24:27 +00:00
|
|
|
char *dontwantfile = copy_of(curlint->filename);
|
2018-03-25 19:36:13 +00:00
|
|
|
lintstruct *restlint = NULL;
|
|
|
|
|
|
|
|
while (curlint != NULL) {
|
|
|
|
if (strcmp(curlint->filename, dontwantfile) == 0) {
|
|
|
|
if (curlint == lints)
|
|
|
|
lints = curlint->next;
|
|
|
|
else
|
|
|
|
curlint->prev->next = curlint->next;
|
|
|
|
if (curlint->next != NULL)
|
|
|
|
curlint->next->prev = curlint->prev;
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
free(tmplint->msg);
|
|
|
|
free(tmplint->filename);
|
|
|
|
free(tmplint);
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL)
|
|
|
|
restlint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
}
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-07-14 19:02:32 +00:00
|
|
|
free(dontwantfile);
|
|
|
|
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(REMARK, _("No messages for this file"));
|
2018-03-25 19:36:13 +00:00
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
curlint = restlint;
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-25 19:41:46 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-24 16:14:35 +00:00
|
|
|
}
|
2018-05-24 16:28:59 +00:00
|
|
|
}
|
2018-03-25 19:41:46 +00:00
|
|
|
#endif
|
2018-03-25 19:36:13 +00:00
|
|
|
}
|
2018-03-25 17:46:44 +00:00
|
|
|
|
|
|
|
if (tmplint != curlint) {
|
2020-11-30 12:27:09 +00:00
|
|
|
/* Put the cursor at the reported position, but don't go beyond EOL
|
|
|
|
* when the second number is a column number instead of an index. */
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(curlint->lineno, curlint->colno - 1);
|
2020-11-06 09:36:08 +00:00
|
|
|
openfile->current_x = actual_x(openfile->current->data, openfile->placewewant);
|
2017-12-29 18:27:33 +00:00
|
|
|
titlebar(NULL);
|
|
|
|
adjust_viewport(CENTERING);
|
2018-05-25 15:57:04 +00:00
|
|
|
#ifdef ENABLE_LINENUMBERS
|
2018-05-25 18:09:24 +00:00
|
|
|
confirm_margin();
|
2018-05-25 15:57:04 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2017-12-29 18:27:33 +00:00
|
|
|
bottombars(MLINTER);
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Place the cursor to indicate the affected line. */
|
|
|
|
place_the_cursor();
|
|
|
|
wnoutrefresh(edit);
|
2016-02-13 19:41:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
kbinput = get_kbinput(bottomwin, VISIBLE);
|
2015-05-28 13:02:29 +00:00
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (kbinput == KEY_WINCH)
|
|
|
|
continue;
|
2015-05-28 13:02:29 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
func = func_from_key(&kbinput);
|
|
|
|
tmplint = curlint;
|
|
|
|
|
2018-10-17 18:17:03 +00:00
|
|
|
if (func == do_cancel || func == do_enter) {
|
2018-10-24 08:23:39 +00:00
|
|
|
wipe_statusbar();
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2019-12-15 18:47:05 +00:00
|
|
|
} else if (func == do_help) {
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
2019-12-15 18:47:05 +00:00
|
|
|
do_help();
|
2020-03-11 18:45:06 +00:00
|
|
|
} else if (func == do_page_up || func == to_prev_block) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint = curlint->prev;
|
2018-10-22 01:14:38 +00:00
|
|
|
else if (last_wait != time(NULL)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("At first message"));
|
2019-12-15 14:34:21 +00:00
|
|
|
beep();
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
2020-03-11 18:45:06 +00:00
|
|
|
} else if (func == do_page_down || func == to_next_block) {
|
2018-09-29 07:46:05 +00:00
|
|
|
if (curlint->next != NULL)
|
|
|
|
curlint = curlint->next;
|
2018-10-22 01:14:38 +00:00
|
|
|
else if (last_wait != time(NULL)) {
|
2018-09-29 07:46:05 +00:00
|
|
|
statusbar(_("At last message"));
|
2019-12-15 14:34:21 +00:00
|
|
|
beep();
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
2020-01-17 23:37:19 +00:00
|
|
|
} else
|
|
|
|
beep();
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2016-02-11 17:25:37 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (curlint = lints; curlint != NULL;) {
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
free(tmplint->msg);
|
|
|
|
free(tmplint->filename);
|
|
|
|
free(tmplint);
|
|
|
|
}
|
2018-10-21 18:47:37 +00:00
|
|
|
|
|
|
|
if (helpless) {
|
|
|
|
SET(NO_HELP);
|
|
|
|
window_init();
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2018-10-24 08:23:39 +00:00
|
|
|
|
2020-12-04 10:50:23 +00:00
|
|
|
lastmessage = VACUUM;
|
2018-10-24 08:23:39 +00:00
|
|
|
currmenu = MMOST;
|
|
|
|
titlebar(NULL);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2019-10-09 17:42:22 +00:00
|
|
|
|
|
|
|
/* Run a manipulation program on the contents of the buffer. */
|
2019-10-25 15:17:48 +00:00
|
|
|
void do_formatter(void)
|
2019-10-09 17:42:22 +00:00
|
|
|
{
|
|
|
|
FILE *stream;
|
|
|
|
char *temp_name;
|
|
|
|
bool okay = FALSE;
|
|
|
|
|
2020-05-22 09:46:30 +00:00
|
|
|
ran_a_tool = TRUE;
|
|
|
|
|
2019-10-09 17:42:22 +00:00
|
|
|
if (in_restricted_mode())
|
|
|
|
return;
|
|
|
|
|
2019-10-25 15:17:48 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->formatter) {
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("No formatter is defined for this type of file"));
|
2019-10-09 17:42:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:10:40 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-08-02 15:28:09 +00:00
|
|
|
openfile->mark = NULL;
|
2020-08-11 18:10:40 +00:00
|
|
|
#endif
|
2020-08-02 15:28:09 +00:00
|
|
|
|
2019-10-09 17:42:22 +00:00
|
|
|
temp_name = safe_tempfile(&stream);
|
|
|
|
|
|
|
|
if (temp_name != NULL)
|
2021-09-29 13:18:17 +00:00
|
|
|
okay = write_file(temp_name, stream, TEMPORARY, OVERWRITE, NONOTES);
|
2019-10-09 17:42:22 +00:00
|
|
|
|
|
|
|
if (!okay) {
|
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
free(temp_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-01 09:56:52 +00:00
|
|
|
treat(temp_name, openfile->syntax->formatter, FALSE);
|
2019-10-09 17:42:22 +00:00
|
|
|
|
|
|
|
unlink(temp_name);
|
|
|
|
free(temp_name);
|
|
|
|
}
|
2017-11-01 18:45:33 +00:00
|
|
|
#endif /* ENABLE_COLOR */
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2021-10-22 09:47:15 +00:00
|
|
|
/* Our own version of "wc". Note that the character count is in
|
2005-08-10 22:51:49 +00:00
|
|
|
* multibyte characters instead of single-byte characters. */
|
2021-10-22 09:47:15 +00:00
|
|
|
void count_lines_words_and_characters(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2019-05-31 15:45:51 +00:00
|
|
|
linestruct *was_current = openfile->current;
|
|
|
|
size_t was_x = openfile->current_x;
|
2017-03-01 15:56:23 +00:00
|
|
|
linestruct *topline, *botline;
|
|
|
|
size_t top_x, bot_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t words = 0, chars = 0;
|
2019-05-30 17:34:57 +00:00
|
|
|
ssize_t lines = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2017-03-01 15:56:23 +00:00
|
|
|
/* Set the start and end point of the area to measure: either the marked
|
|
|
|
* region or the whole buffer. Then compute the number of characters. */
|
2019-05-31 15:34:22 +00:00
|
|
|
if (openfile->mark) {
|
2017-03-01 15:56:23 +00:00
|
|
|
get_region(&topline, &top_x, &botline, &bot_x);
|
|
|
|
|
|
|
|
if (topline != botline)
|
2020-04-09 10:52:36 +00:00
|
|
|
chars = number_of_characters_in(topline->next, botline) + 1;
|
2017-03-01 15:56:23 +00:00
|
|
|
|
|
|
|
chars += mbstrlen(topline->data + top_x) - mbstrlen(botline->data + bot_x);
|
|
|
|
} else {
|
|
|
|
topline = openfile->filetop;
|
|
|
|
top_x = 0;
|
|
|
|
botline = openfile->filebot;
|
|
|
|
bot_x = strlen(botline->data);
|
|
|
|
|
|
|
|
chars = openfile->totsize;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 15:56:23 +00:00
|
|
|
/* Compute the number of lines. */
|
|
|
|
lines = botline->lineno - topline->lineno;
|
|
|
|
lines += (bot_x == 0 || (topline == botline && top_x == bot_x)) ? 0 : 1;
|
|
|
|
|
|
|
|
openfile->current = topline;
|
|
|
|
openfile->current_x = top_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2017-03-01 15:56:23 +00:00
|
|
|
/* Keep stepping to the next word (considering punctuation as part of a
|
|
|
|
* word, as "wc -w" does), until we reach the end of the relevant area,
|
2020-06-26 09:24:55 +00:00
|
|
|
* incrementing the word count for each successful step. */
|
2017-03-01 15:56:23 +00:00
|
|
|
while (openfile->current->lineno < botline->lineno ||
|
|
|
|
(openfile->current == botline && openfile->current_x < bot_x)) {
|
2021-10-24 07:34:35 +00:00
|
|
|
if (do_next_word(FALSE, ISSET(WORD_BOUNDS)))
|
2017-12-29 18:27:33 +00:00
|
|
|
words++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore where we were. */
|
2019-05-31 15:45:51 +00:00
|
|
|
openfile->current = was_current;
|
|
|
|
openfile->current_x = was_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-11-21 14:03:06 +00:00
|
|
|
/* Report on the status bar the number of lines, words, and characters. */
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(INFO, _("%s%zd %s, %zu %s, %zu %s"),
|
2020-11-21 14:03:06 +00:00
|
|
|
openfile->mark ? _("In Selection: ") : "",
|
|
|
|
lines, P_("line", "lines", lines),
|
|
|
|
words, P_("word", "words", words),
|
|
|
|
chars, P_("character", "characters", chars));
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Get verbatim input. */
|
2005-11-07 06:06:05 +00:00
|
|
|
void do_verbatim_input(void)
|
|
|
|
{
|
2020-08-10 05:53:41 +00:00
|
|
|
size_t count = 1;
|
2020-02-12 15:20:20 +00:00
|
|
|
char *bytes;
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-02-14 15:03:15 +00:00
|
|
|
/* TRANSLATORS: Shown when the next keystroke will be inserted verbatim. */
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(INFO, _("Verbatim Input"));
|
2017-12-29 18:27:33 +00:00
|
|
|
place_the_cursor();
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-02-12 15:20:20 +00:00
|
|
|
/* Read in the first one or two bytes of the next keystroke. */
|
2020-02-12 16:16:27 +00:00
|
|
|
bytes = get_verbatim_kbinput(edit, &count);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-07-06 14:59:35 +00:00
|
|
|
/* When something valid was obtained, unsuppress cursor-position display,
|
|
|
|
* insert the bytes into the edit buffer, and blank the status bar. */
|
2020-08-10 10:12:52 +00:00
|
|
|
if (count > 0) {
|
2020-12-04 10:50:23 +00:00
|
|
|
if (ISSET(CONSTANT_SHOW) || ISSET(MINIBAR))
|
2020-07-06 14:59:35 +00:00
|
|
|
lastmessage = VACUUM;
|
2006-05-27 17:39:19 +00:00
|
|
|
|
2020-08-10 10:12:52 +00:00
|
|
|
if (count < 999)
|
|
|
|
inject(bytes, count);
|
|
|
|
|
2020-07-06 14:59:35 +00:00
|
|
|
wipe_statusbar();
|
2020-08-10 10:12:52 +00:00
|
|
|
} else
|
2020-07-06 14:59:35 +00:00
|
|
|
/* TRANSLATORS: An invalid verbatim Unicode code was typed. */
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("Invalid code"));
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-02-12 15:20:20 +00:00
|
|
|
free(bytes);
|
2005-11-07 06:06:05 +00:00
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2016-12-07 12:10:40 +00:00
|
|
|
#ifdef ENABLE_WORDCOMPLETION
|
2019-01-02 16:44:15 +00:00
|
|
|
/* Return a copy of the found completion candidate. */
|
2019-01-06 16:50:53 +00:00
|
|
|
char *copy_completion(char *text)
|
2016-12-07 04:13:47 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *word;
|
2019-01-06 16:50:53 +00:00
|
|
|
size_t length = 0, index = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-01-06 16:50:53 +00:00
|
|
|
/* Find the end of the candidate word to get its length. */
|
2020-03-12 11:38:01 +00:00
|
|
|
while (is_word_char(&text[length], FALSE))
|
2019-06-09 17:37:56 +00:00
|
|
|
length = step_right(text, length);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2019-01-02 16:52:11 +00:00
|
|
|
/* Now copy this candidate to a new string. */
|
2020-08-30 02:57:45 +00:00
|
|
|
word = nmalloc(length + 1);
|
2019-01-06 16:50:53 +00:00
|
|
|
while (index < length)
|
|
|
|
word[index++] = *(text++);
|
2017-12-29 18:27:33 +00:00
|
|
|
word[index] = '\0';
|
2019-01-02 16:52:11 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return word;
|
2016-12-07 04:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Look at the fragment the user has typed, then search the current buffer for
|
|
|
|
* the first word that starts with this fragment, and tentatively complete the
|
|
|
|
* fragment. If the user types 'Complete' again, search and paste the next
|
|
|
|
* possible completion. */
|
|
|
|
void complete_a_word(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *shard, *completion = NULL;
|
2018-03-22 18:32:17 +00:00
|
|
|
size_t start_of_shard, shard_length = 0;
|
|
|
|
size_t i = 0, j = 0;
|
2021-09-11 15:57:02 +00:00
|
|
|
completionstruct *some_word;
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2019-01-29 19:03:59 +00:00
|
|
|
bool was_set_wrapping = ISSET(BREAK_LONG_LINES);
|
2017-01-01 15:33:40 +00:00
|
|
|
#endif
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If this is a fresh completion attempt... */
|
|
|
|
if (pletion_line == NULL) {
|
|
|
|
/* Clear the list of words of a previous completion run. */
|
|
|
|
while (list_of_completions != NULL) {
|
2021-09-11 15:57:02 +00:00
|
|
|
completionstruct *dropit = list_of_completions;
|
2017-12-29 18:27:33 +00:00
|
|
|
list_of_completions = list_of_completions->next;
|
|
|
|
free(dropit->word);
|
|
|
|
free(dropit);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prevent a completion from being merged with typed text. */
|
|
|
|
openfile->last_action = OTHER;
|
|
|
|
|
|
|
|
/* Initialize the starting point for searching. */
|
2019-03-21 16:23:49 +00:00
|
|
|
pletion_line = openfile->filetop;
|
2017-12-29 18:27:33 +00:00
|
|
|
pletion_x = 0;
|
|
|
|
|
|
|
|
/* Wipe the "No further matches" message. */
|
|
|
|
wipe_statusbar();
|
|
|
|
} else {
|
|
|
|
/* Remove the attempted completion from the buffer. */
|
|
|
|
do_undo();
|
2016-12-07 04:13:47 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Find the start of the fragment that the user typed. */
|
|
|
|
start_of_shard = openfile->current_x;
|
|
|
|
while (start_of_shard > 0) {
|
2019-06-09 17:37:56 +00:00
|
|
|
size_t oneleft = step_left(openfile->current->data, start_of_shard);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2020-03-12 11:38:01 +00:00
|
|
|
if (!is_word_char(&openfile->current->data[oneleft], FALSE))
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2019-06-09 17:30:29 +00:00
|
|
|
start_of_shard = oneleft;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there is no word fragment before the cursor, do nothing. */
|
|
|
|
if (start_of_shard == openfile->current_x) {
|
2019-09-22 15:30:22 +00:00
|
|
|
/* TRANSLATORS: Shown when no text is directly left of the cursor. */
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("No word fragment"));
|
2017-12-29 18:27:33 +00:00
|
|
|
pletion_line = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-30 02:57:45 +00:00
|
|
|
shard = nmalloc(openfile->current_x - start_of_shard + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Copy the fragment that has to be searched for. */
|
|
|
|
while (start_of_shard < openfile->current_x)
|
|
|
|
shard[shard_length++] = openfile->current->data[start_of_shard++];
|
|
|
|
shard[shard_length] = '\0';
|
|
|
|
|
|
|
|
/* Run through all of the lines in the buffer, looking for shard. */
|
|
|
|
while (pletion_line != NULL) {
|
2018-12-31 02:34:40 +00:00
|
|
|
ssize_t threshold = strlen(pletion_line->data) - shard_length - 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The point where we can stop searching for shard. */
|
|
|
|
|
|
|
|
/* Traverse the whole line, looking for shard. */
|
2018-12-31 02:34:40 +00:00
|
|
|
for (i = pletion_x; (ssize_t)i < threshold; i++) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the first byte doesn't match, run on. */
|
|
|
|
if (pletion_line->data[i] != shard[0])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Compare the rest of the bytes in shard. */
|
|
|
|
for (j = 1; j < shard_length; j++)
|
|
|
|
if (pletion_line->data[i + j] != shard[j])
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* If not all of the bytes matched, continue searching. */
|
|
|
|
if (j < shard_length)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If the found match is not /longer/ than shard, skip it. */
|
2020-03-12 11:38:01 +00:00
|
|
|
if (!is_word_char(&pletion_line->data[i + j], FALSE))
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If the match is not a separate word, skip it. */
|
2020-03-12 11:38:01 +00:00
|
|
|
if (i > 0 && is_word_char(&pletion_line->data[
|
2019-06-09 17:37:56 +00:00
|
|
|
step_left(pletion_line->data, i)], FALSE))
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If this match is the shard itself, ignore it. */
|
|
|
|
if (pletion_line == openfile->current &&
|
2020-02-14 15:03:15 +00:00
|
|
|
i == openfile->current_x - shard_length)
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
|
2019-01-06 16:50:53 +00:00
|
|
|
completion = copy_completion(pletion_line->data + i);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Look among earlier attempted completions for a duplicate. */
|
|
|
|
some_word = list_of_completions;
|
|
|
|
while (some_word && strcmp(some_word->word, completion) != 0)
|
|
|
|
some_word = some_word->next;
|
|
|
|
|
|
|
|
/* If we've already tried this word, skip it. */
|
|
|
|
if (some_word != NULL) {
|
|
|
|
free(completion);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add the found word to the list of completions. */
|
2021-09-11 15:57:02 +00:00
|
|
|
some_word = nmalloc(sizeof(completionstruct));
|
2017-12-29 18:27:33 +00:00
|
|
|
some_word->word = completion;
|
|
|
|
some_word->next = list_of_completions;
|
|
|
|
list_of_completions = some_word;
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Temporarily disable wrapping so only one undo item is added. */
|
2019-01-29 19:03:59 +00:00
|
|
|
UNSET(BREAK_LONG_LINES);
|
2017-01-01 15:33:40 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Inject the completion into the buffer. */
|
2020-02-12 10:12:34 +00:00
|
|
|
inject(&completion[shard_length], strlen(completion) - shard_length);
|
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If needed, reenable wrapping and wrap the current line. */
|
|
|
|
if (was_set_wrapping) {
|
2019-01-29 19:03:59 +00:00
|
|
|
SET(BREAK_LONG_LINES);
|
2019-04-19 07:44:57 +00:00
|
|
|
do_wrap();
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-01-01 15:33:40 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Set the position for a possible next search attempt. */
|
|
|
|
pletion_x = ++i;
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(shard);
|
|
|
|
return;
|
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
pletion_line = pletion_line->next;
|
|
|
|
pletion_x = 0;
|
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The search has reached the end of the file. */
|
|
|
|
if (list_of_completions != NULL) {
|
2021-05-11 13:52:55 +00:00
|
|
|
edit_refresh();
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("No further matches"));
|
2017-12-29 18:27:33 +00:00
|
|
|
} else
|
|
|
|
/* TRANSLATORS: Shown when there are zero possible completions. */
|
2020-12-08 16:09:07 +00:00
|
|
|
statusline(AHEM, _("No matches"));
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(shard);
|
2016-12-07 04:13:47 +00:00
|
|
|
}
|
2016-12-07 12:10:40 +00:00
|
|
|
#endif /* ENABLE_WORDCOMPLETION */
|