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
|
|
|
* *
|
2019-02-24 18:32:17 +00:00
|
|
|
* Copyright (C) 1999-2011, 2013-2019 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 *
|
2019-03-10 15:29:57 +00:00
|
|
|
* Copyright (C) 2015-2019 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
|
|
|
* *
|
|
|
|
**************************************************************************/
|
|
|
|
|
2005-12-08 02:47:10 +00:00
|
|
|
#include "proto.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>
|
|
|
|
#include <unistd.h>
|
2005-07-24 19:57:51 +00:00
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-07-30 18:07:25 +00:00
|
|
|
static pid_t pid_of_command = -1;
|
|
|
|
/* The PID of the forked process -- needed when wanting to abort it. */
|
2005-07-24 19:57:51 +00:00
|
|
|
#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. */
|
2016-12-07 04:13:47 +00:00
|
|
|
static completion_word *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;
|
|
|
|
statusbar(_("Mark Set"));
|
|
|
|
openfile->kind_of_mark = HARDMARK;
|
|
|
|
} else {
|
|
|
|
openfile->mark = NULL;
|
|
|
|
statusbar(_("Mark Unset"));
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#if defined(ENABLE_COLOR) || defined(ENABLE_SPELLER)
|
2019-04-23 09:48:10 +00:00
|
|
|
/* Return an error message about invoking the given name. The message
|
|
|
|
* should not be freed; this leak is not worth the trouble avoiding. */
|
|
|
|
const char *invocation_error(const char *name)
|
2015-04-17 09:24:17 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *message, *invoke_error = _("Error invoking \"%s\"");
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
message = charalloc(strlen(invoke_error) + strlen(name) + 1);
|
|
|
|
sprintf(message, invoke_error, name);
|
|
|
|
return message;
|
2015-04-17 09:24:17 +00:00
|
|
|
}
|
2015-06-28 14:12:25 +00:00
|
|
|
#endif
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Insert a tab. If the TABS_TO_SPACES flag is set, insert the number
|
|
|
|
* of spaces that a tab would normally take up. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_tab(void)
|
|
|
|
{
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
|
|
|
char *spaces = charalloc(tabsize + 1);
|
|
|
|
size_t length = tabsize - (xplustabs() % tabsize);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
charset(spaces, ' ', length);
|
|
|
|
spaces[length] = '\0';
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output(spaces, length, TRUE);
|
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
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output((char *)"\t", 1, TRUE);
|
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. */
|
|
|
|
line->data = charealloc(line->data, length + indent_len + 1);
|
|
|
|
charmove(line->data + indent_len, line->data, length + 1);
|
|
|
|
strncpy(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. */
|
2019-03-21 16:08:52 +00:00
|
|
|
get_range((const linestruct **)&top, (const linestruct **)&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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation = charalloc(tabsize + 1);
|
2017-08-16 15:16:18 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Set the indentation to either a bunch of spaces or a single tab. */
|
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
|
|
|
charset(indentation, ' ', tabsize);
|
|
|
|
indentation[tabsize] = '\0';
|
|
|
|
} else {
|
|
|
|
indentation[0] = '\t';
|
|
|
|
indentation[1] = '\0';
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(INDENT);
|
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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t bytes_of_white = 0;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
if (*text == '\t')
|
|
|
|
return ++bytes_of_white;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*text != ' ')
|
|
|
|
return bytes_of_white;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 20:35:14 +00:00
|
|
|
if (++bytes_of_white == 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. */
|
|
|
|
charmove(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. */
|
2019-03-21 16:08:52 +00:00
|
|
|
get_range((const linestruct **)&top, (const linestruct **)&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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(UNINDENT);
|
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);
|
|
|
|
char *indentation = mallocstrncpy(NULL, line->data, indent_len + 1);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation[indent_len] = '\0';
|
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. */
|
|
|
|
void handle_indent_action(undo *u, bool undoing, bool add_indent)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_group *group = u->grouping;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *line = fsfromline(group->top_line);
|
2018-04-01 09:36:27 +00:00
|
|
|
|
|
|
|
if (group->next != NULL)
|
|
|
|
statusline(ALERT, "Multiple groups -- please report a bug");
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the indenter adjust it. */
|
|
|
|
if (!undoing)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
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. */
|
|
|
|
while (line && line->lineno <= group->bottom_line) {
|
|
|
|
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)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
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 */
|
|
|
|
|
|
|
|
/* Test whether the string is empty or consists of only blanks. */
|
|
|
|
bool white_string(const char *s)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*s != '\0' && (is_blank_mbchar(s) || *s == '\r'))
|
|
|
|
s += move_mbright(s, 0);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return !*s;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
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. */
|
|
|
|
line->data = charealloc(line->data, line_len + pre_len + post_len + 1);
|
|
|
|
charmove(line->data + pre_len, line->data, line_len + 1);
|
|
|
|
charmove(line->data, comment_seq, pre_len);
|
|
|
|
if (post_len > 0)
|
|
|
|
charmove(line->data + pre_len + line_len, post_seq, post_len + 1);
|
|
|
|
|
|
|
|
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. */
|
|
|
|
charmove(line->data, line->data + pre_len, line_len - pre_len);
|
|
|
|
/* 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') {
|
|
|
|
statusbar(_("Commenting is not supported for this file type"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Determine which lines to work on. */
|
2019-03-21 16:08:52 +00:00
|
|
|
get_range((const linestruct **)&top, (const linestruct **)&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)) {
|
2018-04-01 08:53:15 +00:00
|
|
|
statusbar(_("Cannot comment past end of file"));
|
|
|
|
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;
|
|
|
|
|
|
|
|
add_undo(action);
|
|
|
|
|
|
|
|
/* Store the comment sequence used for the operation, because it could
|
|
|
|
* change when the file name changes; we need to know what it was. */
|
|
|
|
openfile->current_undo->strdata = mallocstrcpy(NULL, comment_seq);
|
|
|
|
|
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. */
|
2018-04-01 08:53:15 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
if (comment_line(action, line, comment_seq))
|
|
|
|
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. */
|
|
|
|
void handle_comment_action(undo *u, bool undoing, bool add_comment)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_group *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)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (group) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *f = fsfromline(group->top_line);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (f && f->lineno <= group->bottom_line) {
|
|
|
|
comment_line(undoing ^ add_comment ?
|
|
|
|
COMMENT : UNCOMMENT, f, u->strdata);
|
|
|
|
f = f->next;
|
|
|
|
}
|
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)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
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
|
2014-05-15 20:00:46 +00:00
|
|
|
#define redo_paste undo_cut
|
|
|
|
#define undo_paste redo_cut
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Undo a cut, or redo an uncut. */
|
2008-09-16 21:35:19 +00:00
|
|
|
void undo_cut(undo *u)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get to where we need to uncut from. */
|
2018-12-27 21:56:30 +00:00
|
|
|
if (u->xflags & WAS_WHOLE_LINE)
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, 0);
|
|
|
|
else
|
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
|
2018-07-08 15:36:17 +00:00
|
|
|
/* If nothing was actually cut, positioning the cursor was enough. */
|
2018-06-09 21:50:30 +00:00
|
|
|
if (!u->cutbuffer)
|
|
|
|
return;
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
copy_from_buffer(u->cutbuffer);
|
2014-05-25 19:41:49 +00:00
|
|
|
|
2019-01-06 14:35:31 +00:00
|
|
|
/* If the final line was originally cut, remove the extra magic line. */
|
2019-04-07 06:47:29 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_LINE) && !ISSET(NO_NEWLINES) &&
|
2018-12-30 17:21:56 +00:00
|
|
|
openfile->current != openfile->filebot)
|
2018-12-27 22:09:19 +00:00
|
|
|
remove_magicline();
|
|
|
|
|
2018-12-27 21:56:30 +00:00
|
|
|
if (!(u->xflags & WAS_MARKED_FORWARD) && u->type != PASTE)
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Redo a cut, or undo an uncut. */
|
2014-04-08 18:38:45 +00:00
|
|
|
void redo_cut(undo *u)
|
|
|
|
{
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *oldcutbuffer = cutbuffer;
|
2017-07-01 11:25:11 +00:00
|
|
|
|
2018-06-09 21:50:30 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
|
2018-07-08 15:36:17 +00:00
|
|
|
/* If nothing was actually cut, positioning the cursor was enough. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!u->cutbuffer)
|
|
|
|
return;
|
2009-07-27 04:16:44 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
cutbuffer = NULL;
|
2014-07-02 20:52:27 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = fsfromline(u->mark_begin_lineno);
|
2018-12-27 21:56:30 +00:00
|
|
|
openfile->mark_x = (u->xflags & WAS_WHOLE_LINE) ? 0 : u->mark_begin_x;
|
2014-06-22 11:03:49 +00:00
|
|
|
|
2018-10-24 02:25:22 +00:00
|
|
|
do_cut_text(FALSE, 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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *f = NULL, *t = NULL;
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *oldcutbuffer;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *data, *undidmsg = NULL;
|
|
|
|
size_t from_x, to_x;
|
|
|
|
|
2019-04-04 10:53:03 +00:00
|
|
|
if (u == NULL) {
|
|
|
|
statusbar(_("Nothing to undo"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2018-03-03 15:54:55 +00:00
|
|
|
if (u->type <= REPLACE) {
|
|
|
|
f = fsfromline(u->mark_begin_lineno);
|
|
|
|
if (f == NULL)
|
|
|
|
return;
|
|
|
|
}
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = u->begin;
|
|
|
|
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");
|
2019-04-07 06:47:29 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_LINE) && !ISSET(NO_NEWLINES))
|
2017-12-29 18:27:33 +00:00
|
|
|
remove_magicline();
|
|
|
|
data = charalloc(strlen(f->data) - strlen(u->strdata) + 1);
|
|
|
|
strncpy(data, f->data, u->begin);
|
|
|
|
strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]);
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
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");
|
|
|
|
from_x = (u->begin == 0) ? 0 : u->mark_begin_x;
|
|
|
|
to_x = (u->begin == 0) ? u->mark_begin_x : u->begin;
|
|
|
|
f->data = charealloc(f->data, strlen(f->data) +
|
|
|
|
strlen(&u->strdata[from_x]) + 1);
|
|
|
|
strcat(f->data, &u->strdata[from_x]);
|
|
|
|
unlink_node(f->next);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(f);
|
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
|
|
|
goto_line_posx(u->lineno, to_x);
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
|
|
|
case DEL:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("deletion");
|
2017-12-29 18:27:33 +00:00
|
|
|
data = charalloc(strlen(f->data) + strlen(u->strdata) + 1);
|
|
|
|
strncpy(data, f->data, u->begin);
|
|
|
|
strcpy(&data[u->begin], u->strdata);
|
|
|
|
strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]);
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
|
|
|
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. */
|
2019-04-07 06:47:29 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_BACKSPACE) && !ISSET(NO_NEWLINES)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(openfile->filebot->lineno, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
t = make_new_node(f);
|
|
|
|
t->data = mallocstrcpy(NULL, u->strdata);
|
|
|
|
data = mallocstrncpy(NULL, f->data, u->mark_begin_x + 1);
|
|
|
|
data[u->mark_begin_x] = '\0';
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
splice_node(f, t);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(t);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
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");
|
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
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
data = u->strdata;
|
|
|
|
u->strdata = f->data;
|
|
|
|
f->data = data;
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_END:
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
while (openfile->current_undo->type != SPLIT_BEGIN)
|
|
|
|
do_undo();
|
|
|
|
u = openfile->current_undo;
|
|
|
|
case SPLIT_BEGIN:
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("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:
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
openfile->mark = fsfromline(u->mark_begin_lineno);
|
|
|
|
openfile->mark_x = u->mark_begin_x;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
cut_marked(NULL);
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(u->cutbuffer);
|
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;
|
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;
|
2018-08-16 13:59:30 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
|
|
|
case COUPLE_END:
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* When at the point where the file was last saved, unset "Modified". */
|
|
|
|
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)
|
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *f = NULL, *shoveline;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *data, *redidmsg = NULL;
|
|
|
|
undo *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) {
|
2019-04-04 10:53:03 +00:00
|
|
|
statusbar(_("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
|
|
|
|
2018-03-03 15:54:55 +00:00
|
|
|
if (u->type <= REPLACE) {
|
|
|
|
f = fsfromline(u->mark_begin_lineno);
|
|
|
|
if (f == NULL)
|
|
|
|
return;
|
|
|
|
}
|
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");
|
2019-04-07 06:47:29 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_LINE) && !ISSET(NO_NEWLINES))
|
2017-12-29 18:27:33 +00:00
|
|
|
new_magicline();
|
|
|
|
data = charalloc(strlen(f->data) + strlen(u->strdata) + 1);
|
|
|
|
strncpy(data, f->data, u->begin);
|
|
|
|
strcpy(&data[u->begin], u->strdata);
|
|
|
|
strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]);
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
|
|
|
break;
|
|
|
|
case ENTER:
|
|
|
|
redidmsg = _("line break");
|
|
|
|
shoveline = make_new_node(f);
|
|
|
|
shoveline->data = mallocstrcpy(NULL, u->strdata);
|
|
|
|
data = mallocstrncpy(NULL, f->data, u->begin + 1);
|
|
|
|
data[u->begin] = '\0';
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
splice_node(f, shoveline);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(shoveline);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->lineno + 1, u->mark_begin_x);
|
|
|
|
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");
|
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 = charalloc(strlen(f->data) + strlen(u->strdata) + 1);
|
|
|
|
strncpy(data, f->data, u->begin);
|
|
|
|
strcpy(&data[u->begin], &f->data[u->begin + strlen(u->strdata)]);
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
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. */
|
2019-04-07 06:47:29 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_BACKSPACE) && !ISSET(NO_NEWLINES)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
f->data = charealloc(f->data, strlen(f->data) + strlen(u->strdata) + 1);
|
|
|
|
strcat(f->data, u->strdata);
|
|
|
|
unlink_node(f->next);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(f);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
|
|
|
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");
|
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;
|
|
|
|
u->strdata = f->data;
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
openfile->current_undo = u;
|
|
|
|
while (openfile->current_undo->type != SPLIT_END)
|
|
|
|
do_redo();
|
|
|
|
u = openfile->current_undo;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
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");
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
copy_from_buffer(u->cutbuffer);
|
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;
|
2018-08-16 13:59:30 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
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;
|
|
|
|
|
|
|
|
/* When at the point where the file was last saved, unset "Modified". */
|
|
|
|
if (openfile->current_undo == openfile->last_saved) {
|
|
|
|
openfile->modified = FALSE;
|
|
|
|
titlebar(NULL);
|
|
|
|
} else
|
|
|
|
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;
|
|
|
|
|
|
|
|
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 */
|
2017-12-29 18:27:33 +00:00
|
|
|
newnode->data = charalloc(strlen(openfile->current->data +
|
|
|
|
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
|
2017-12-29 18:27:33 +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
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(ENTER);
|
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
|
2018-07-30 18:22:24 +00:00
|
|
|
/* Send an unconditional kill signal to the running external command. */
|
2018-07-30 18:07:25 +00:00
|
|
|
RETSIGTYPE cancel_the_command(int signal)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2018-07-30 18:22:24 +00:00
|
|
|
kill(pid_of_command, SIGKILL);
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* Send the text that starts at the given line to file descriptor fd. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void send_data(const linestruct *line, int fd)
|
2018-05-16 01:20:11 +00:00
|
|
|
{
|
|
|
|
FILE *tube = fdopen(fd, "w");
|
|
|
|
|
|
|
|
if (tube == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Send each line, except a final empty line. */
|
|
|
|
while (line != NULL && (line->next != NULL || line->data[0] != '\0')) {
|
|
|
|
fprintf(tube, "%s%s", line->data, line->next == NULL ? "" : "\n");
|
|
|
|
line = line->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(tube);
|
|
|
|
}
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Execute the given command in a shell. Return TRUE on success. */
|
2005-07-24 19:57:51 +00:00
|
|
|
bool execute_command(const char *command)
|
|
|
|
{
|
2018-05-16 18:42:44 +00:00
|
|
|
int from_fd[2], to_fd[2];
|
2018-05-16 01:20:11 +00:00
|
|
|
/* The pipes through which text will written and read. */
|
|
|
|
const bool should_pipe = (command[0] == '|');
|
2018-04-23 11:04:31 +00:00
|
|
|
FILE *stream;
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *shellenv;
|
|
|
|
struct sigaction oldaction, newaction;
|
|
|
|
/* Original and temporary handlers for SIGINT. */
|
2018-04-23 10:30:14 +00:00
|
|
|
bool setup_failed = FALSE;
|
|
|
|
/* Whether setting up the temporary SIGINT handler failed. */
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* Create a pipe to read the command's output from, and, if needed,
|
|
|
|
* a pipe to feed the command's input through. */
|
2018-05-16 18:42:44 +00:00
|
|
|
if (pipe(from_fd) == -1 || (should_pipe && pipe(to_fd) == -1)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Could not create pipe"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Check which shell to use. If none is specified, use /bin/sh. */
|
2017-12-29 18:27:33 +00:00
|
|
|
shellenv = getenv("SHELL");
|
|
|
|
if (shellenv == NULL)
|
|
|
|
shellenv = (char *) "/bin/sh";
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Fork a child process to run the command in. */
|
2018-07-30 18:07:25 +00:00
|
|
|
if ((pid_of_command = fork()) == 0) {
|
2018-05-16 18:42:44 +00:00
|
|
|
/* Child: close the unused read end of the output pipe. */
|
|
|
|
close(from_fd[0]);
|
2018-04-23 12:01:55 +00:00
|
|
|
|
2018-05-16 18:42:44 +00:00
|
|
|
/* Connect the write end of the output pipe to the process' output streams. */
|
|
|
|
dup2(from_fd[1], fileno(stdout));
|
|
|
|
dup2(from_fd[1], fileno(stderr));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* If the parent sends text, connect the read end of the
|
|
|
|
* feeding pipe to the child's input stream. */
|
|
|
|
if (should_pipe) {
|
|
|
|
dup2(to_fd[0], fileno(stdin));
|
|
|
|
close(to_fd[1]);
|
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Run the given command inside the preferred shell. */
|
2018-05-16 01:20:11 +00:00
|
|
|
execl(shellenv, tail(shellenv), "-c", should_pipe ? &command[1] : command, NULL);
|
2018-04-23 12:01:55 +00:00
|
|
|
|
|
|
|
/* If the exec call returns, there was an error. */
|
|
|
|
exit(1);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Parent: close the unused write end of the pipe. */
|
2018-05-16 18:42:44 +00:00
|
|
|
close(from_fd[1]);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-07-30 18:07:25 +00:00
|
|
|
if (pid_of_command == -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Could not fork"));
|
2018-05-16 18:42:44 +00:00
|
|
|
close(from_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-31 15:57:58 +00:00
|
|
|
statusbar(_("Executing..."));
|
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* If the command starts with "|", pipe buffer or region to the command. */
|
|
|
|
if (should_pipe) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *was_cutbuffer = cutbuffer;
|
2018-05-16 01:20:11 +00:00
|
|
|
cutbuffer = NULL;
|
|
|
|
|
2018-05-24 19:09:14 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-17 10:10:06 +00:00
|
|
|
if (ISSET(MULTIBUFFER)) {
|
2018-05-16 01:20:11 +00:00
|
|
|
switch_to_prev_buffer();
|
2018-05-24 19:09:14 +00:00
|
|
|
if (openfile->mark)
|
2018-10-24 02:25:22 +00:00
|
|
|
do_cut_text(TRUE, TRUE, FALSE, FALSE);
|
2018-05-24 19:09:14 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
2018-05-17 10:10:06 +00:00
|
|
|
add_undo(COUPLE_BEGIN);
|
2018-08-04 07:51:32 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("filtering"));
|
2018-05-24 19:09:14 +00:00
|
|
|
if (openfile->mark == NULL) {
|
2019-03-21 16:23:49 +00:00
|
|
|
openfile->current = openfile->filetop;
|
2018-05-16 01:20:11 +00:00
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
|
|
|
add_undo(CUT);
|
2019-03-17 18:04:18 +00:00
|
|
|
do_cut_text(FALSE, openfile->mark != NULL, openfile->mark == NULL, FALSE);
|
2018-05-16 01:20:11 +00:00
|
|
|
update_undo(CUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fork() == 0) {
|
|
|
|
close(to_fd[0]);
|
2019-03-21 16:23:49 +00:00
|
|
|
send_data(cutbuffer != NULL ? cutbuffer : openfile->filetop, to_fd[1]);
|
2018-05-16 01:20:11 +00:00
|
|
|
close(to_fd[1]);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(to_fd[0]);
|
|
|
|
close(to_fd[1]);
|
|
|
|
|
2018-05-24 19:09:14 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-16 01:20:11 +00:00
|
|
|
if (ISSET(MULTIBUFFER))
|
|
|
|
switch_to_next_buffer();
|
2018-05-24 19:09:14 +00:00
|
|
|
#endif
|
2019-03-21 16:18:50 +00:00
|
|
|
free_lines(cutbuffer);
|
2018-05-16 01:20:11 +00:00
|
|
|
cutbuffer = was_cutbuffer;
|
|
|
|
}
|
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Re-enable interpretation of the special control keys so that we get
|
2017-12-29 18:27:33 +00:00
|
|
|
* SIGINT when Ctrl-C is pressed. */
|
|
|
|
enable_signals();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Set things up so that Ctrl-C will terminate the forked process. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (sigaction(SIGINT, NULL, &newaction) == -1) {
|
2018-04-23 10:30:14 +00:00
|
|
|
setup_failed = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
|
|
|
} else {
|
2018-07-30 18:07:25 +00:00
|
|
|
newaction.sa_handler = cancel_the_command;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
|
2018-04-23 10:30:14 +00:00
|
|
|
setup_failed = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 18:42:44 +00:00
|
|
|
stream = fdopen(from_fd[0], "rb");
|
2018-04-23 11:04:31 +00:00
|
|
|
if (stream == NULL)
|
2018-04-23 15:56:58 +00:00
|
|
|
statusline(ALERT, _("Failed to open pipe: %s"), strerror(errno));
|
|
|
|
else
|
|
|
|
read_file(stream, 0, "pipe", TRUE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-04 07:51:32 +00:00
|
|
|
if (should_pipe && !ISSET(MULTIBUFFER)) {
|
2018-05-17 10:10:06 +00:00
|
|
|
add_undo(COUPLE_END);
|
2018-08-04 07:51:32 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("filtering"));
|
|
|
|
}
|
2018-05-17 10:10:06 +00:00
|
|
|
|
2018-08-19 07:54:16 +00:00
|
|
|
/* Wait for the external command (and possibly data sender) to terminate. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (wait(NULL) == -1)
|
|
|
|
nperror("wait");
|
2018-08-19 07:54:16 +00:00
|
|
|
if (should_pipe && (wait(NULL) == -1))
|
|
|
|
nperror("wait");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* If it was changed, restore the handler for SIGINT. */
|
2018-04-23 10:30:14 +00:00
|
|
|
if (!setup_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Restore the terminal to its desired state, and disable
|
|
|
|
* interpretation of the special control keys again. */
|
2017-12-29 18:27:33 +00:00
|
|
|
terminal_init();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-11-27 15:11:54 +00:00
|
|
|
/* Discard undo items that are newer than the given one, or all if NULL.
|
2017-12-18 19:08:06 +00:00
|
|
|
* When keep is TRUE, do not touch the last_saved pointer. */
|
2017-11-27 15:11:54 +00:00
|
|
|
void discard_until(const undo *thisitem, openfilestruct *thefile, bool keep)
|
2015-12-03 08:50:34 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *dropit = thefile->undotop;
|
|
|
|
undo_group *group;
|
|
|
|
|
|
|
|
while (dropit != NULL && dropit != thisitem) {
|
|
|
|
thefile->undotop = dropit->next;
|
|
|
|
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) {
|
|
|
|
undo_group *next = group->next;
|
|
|
|
free_chararray(group->indentations,
|
|
|
|
group->bottom_line - group->top_line);
|
|
|
|
free(group);
|
|
|
|
group = next;
|
|
|
|
}
|
2018-03-11 19:06:24 +00:00
|
|
|
free(dropit);
|
2017-12-29 18:27:33 +00:00
|
|
|
dropit = thefile->undotop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust the pointer to the top of the undo stack. */
|
|
|
|
thefile->current_undo = (undo *)thisitem;
|
|
|
|
|
|
|
|
/* Prevent a chain of editing actions from continuing. */
|
|
|
|
thefile->last_action = OTHER;
|
|
|
|
|
|
|
|
/* When requested, record that the undo stack was chopped, and
|
|
|
|
* that thus there is no point at which the file was last saved. */
|
|
|
|
if (!keep)
|
|
|
|
thefile->last_saved = (undo *)0xbeeb;
|
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. */
|
2015-06-17 11:18:20 +00:00
|
|
|
void add_undo(undo_type action)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
|
|
|
/* The thing we did previously. */
|
|
|
|
|
|
|
|
/* Blow away newer undo items if we add somewhere in the middle. */
|
|
|
|
discard_until(u, openfile, TRUE);
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Allocate and initialize a new undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
u = (undo *) nmalloc(sizeof(undo));
|
|
|
|
u->type = action;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->strdata = NULL;
|
|
|
|
u->cutbuffer = NULL;
|
|
|
|
u->lineno = openfile->current->lineno;
|
|
|
|
u->begin = openfile->current_x;
|
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
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->xflags = 0;
|
|
|
|
u->grouping = NULL;
|
|
|
|
|
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) {
|
|
|
|
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. */
|
|
|
|
if (openfile->current == openfile->filebot)
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_FINAL_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
u->wassize--;
|
|
|
|
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. */
|
|
|
|
if (openfile->current->next == openfile->filebot &&
|
|
|
|
openfile->current->data[0] != '\0')
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_FINAL_BACKSPACE;
|
2017-12-29 18:27:33 +00:00
|
|
|
case DEL:
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When not at the end of a line, store the deleted character,
|
|
|
|
* else purposely fall into the line-joining code. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current->data[openfile->current_x] != '\0') {
|
|
|
|
char *char_buf = charalloc(MAXCHARLEN + 1);
|
|
|
|
int char_len = parse_mbchar(&openfile->current->data[u->begin],
|
|
|
|
char_buf, NULL);
|
|
|
|
char_buf[char_len] = '\0';
|
|
|
|
u->strdata = char_buf;
|
|
|
|
if (u->type == BACK)
|
|
|
|
u->mark_begin_x += char_len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case JOIN:
|
|
|
|
if (openfile->current->next) {
|
|
|
|
if (u->type == BACK) {
|
|
|
|
u->lineno = openfile->current->next->lineno;
|
|
|
|
u->begin = 0;
|
|
|
|
}
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->next->data);
|
|
|
|
}
|
|
|
|
action = u->type = JOIN;
|
|
|
|
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:
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->data);
|
|
|
|
break;
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
case SPLIT_BEGIN:
|
|
|
|
action = openfile->undotop->type;
|
|
|
|
break;
|
|
|
|
case SPLIT_END:
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
case CUT_TO_EOF:
|
2018-12-27 22:09:19 +00:00
|
|
|
u->xflags |= WAS_FINAL_LINE;
|
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) {
|
|
|
|
u->mark_begin_lineno = openfile->mark->lineno;
|
|
|
|
u->mark_begin_x = openfile->mark_x;
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= MARK_WAS_SET;
|
2018-12-27 22:09:19 +00:00
|
|
|
if (openfile->current == openfile->filebot ||
|
|
|
|
openfile->mark == openfile->filebot)
|
|
|
|
u->xflags |= WAS_FINAL_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. */
|
|
|
|
u->begin = 0;
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_WHOLE_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PASTE:
|
2019-03-21 16:18:50 +00:00
|
|
|
u->cutbuffer = copy_buffer(cutbuffer);
|
2017-12-29 18:27:33 +00:00
|
|
|
u->lineno += cutbottom->lineno - cutbuffer->lineno;
|
|
|
|
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:
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
|
|
|
case COUPLE_END:
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case INDENT:
|
|
|
|
case UNINDENT:
|
|
|
|
break;
|
2016-05-25 20:13:50 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2017-12-29 18:27:33 +00:00
|
|
|
case COMMENT:
|
|
|
|
case UNCOMMENT:
|
|
|
|
break;
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
size_t number_of_lines;
|
|
|
|
|
|
|
|
u->grouping->bottom_line++;
|
|
|
|
|
|
|
|
number_of_lines = u->grouping->bottom_line - u->grouping->top_line + 1;
|
|
|
|
u->grouping->indentations = (char **)nrealloc(u->grouping->indentations,
|
|
|
|
number_of_lines * sizeof(char *));
|
|
|
|
u->grouping->indentations[number_of_lines - 1] = mallocstrcpy(NULL,
|
|
|
|
indentation);
|
|
|
|
} else {
|
|
|
|
undo_group *born = (undo_group *)nmalloc(sizeof(undo_group));
|
|
|
|
|
|
|
|
born->next = u->grouping;
|
|
|
|
u->grouping = born;
|
|
|
|
born->top_line = lineno;
|
|
|
|
born->bottom_line = lineno;
|
|
|
|
|
|
|
|
u->grouping->indentations = (char **)nmalloc(sizeof(char *));
|
|
|
|
u->grouping->indentations[0] = mallocstrcpy(NULL, indentation);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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
|
|
|
{
|
2018-03-05 09:18:11 +00:00
|
|
|
undo *u = openfile->undotop;
|
|
|
|
char *char_buf;
|
|
|
|
int char_len;
|
2008-08-01 03:50:20 +00:00
|
|
|
|
2019-03-17 18:46:40 +00:00
|
|
|
if (u->type != action)
|
|
|
|
statusline(ALERT, "Mismatching undo type -- please report a bug");
|
|
|
|
|
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:
|
|
|
|
char_buf = charalloc(MAXCHARLEN);
|
|
|
|
char_len = parse_mbchar(&openfile->current->data[u->mark_begin_x],
|
|
|
|
char_buf, NULL);
|
|
|
|
u->strdata = addstrings(u->strdata, u->strdata ? strlen(u->strdata) : 0,
|
|
|
|
char_buf, char_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
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:
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->data);
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
2018-03-05 09:18:11 +00:00
|
|
|
case DEL:
|
|
|
|
char_buf = charalloc(MAXCHARLEN);
|
|
|
|
char_len = parse_mbchar(&openfile->current->data[openfile->current_x],
|
|
|
|
char_buf, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current_x == u->begin) {
|
|
|
|
/* They deleted more: add removed character after earlier stuff. */
|
|
|
|
u->strdata = addstrings(u->strdata, strlen(u->strdata), char_buf, char_len);
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
} else if (openfile->current_x == u->begin - char_len) {
|
|
|
|
/* They backspaced further: add removed character before earlier. */
|
|
|
|
u->strdata = addstrings(char_buf, char_len, u->strdata, strlen(u->strdata));
|
|
|
|
u->begin = openfile->current_x;
|
|
|
|
} else {
|
|
|
|
/* They deleted *elsewhere* on the line: start a new undo item. */
|
|
|
|
free(char_buf);
|
|
|
|
add_undo(u->type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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 JOIN:
|
|
|
|
break;
|
|
|
|
case REPLACE:
|
|
|
|
case PASTE:
|
|
|
|
u->lineno = openfile->current->lineno;
|
2018-09-25 18:25:21 +00:00
|
|
|
u->begin = 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;
|
|
|
|
#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:
|
|
|
|
if (!cutbuffer)
|
|
|
|
break;
|
2018-10-24 02:25:22 +00:00
|
|
|
if (u->type == ZAP)
|
|
|
|
u->cutbuffer = cutbuffer;
|
|
|
|
else {
|
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
|
|
|
}
|
2018-12-27 21:56:30 +00:00
|
|
|
if (u->xflags & MARK_WAS_SET) {
|
2019-05-01 17:10:56 +00:00
|
|
|
/* If the region was marked backwards, swap the end points. */
|
|
|
|
if (u->lineno < u->mark_begin_lineno ||
|
|
|
|
(u->lineno == u->mark_begin_lineno &&
|
|
|
|
u->begin < u->mark_begin_x)) {
|
2019-05-01 17:14:06 +00:00
|
|
|
ssize_t number = u->lineno;
|
|
|
|
size_t position = u->begin;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-05-01 17:10:56 +00:00
|
|
|
u->lineno = u->mark_begin_lineno;
|
2017-12-29 18:27:33 +00:00
|
|
|
u->begin = u->mark_begin_x;
|
|
|
|
|
2019-05-01 17:14:06 +00:00
|
|
|
u->mark_begin_lineno = number;
|
|
|
|
u->mark_begin_x = position;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_MARKED_FORWARD;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
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++;
|
|
|
|
}
|
|
|
|
u->lineno = u->mark_begin_lineno + count;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(CUT_FROM_CURSOR) || u->type == CUT_TO_EOF) {
|
2019-04-28 17:21:40 +00:00
|
|
|
u->begin = strlen(bottomline->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->lineno == u->mark_begin_lineno)
|
|
|
|
u->begin += u->mark_begin_x;
|
|
|
|
} else if (openfile->current == openfile->filebot &&
|
2019-04-07 06:47:29 +00:00
|
|
|
ISSET(NO_NEWLINES))
|
2019-04-28 17:21:40 +00:00
|
|
|
u->begin = strlen(bottomline->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case INSERT:
|
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
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:
|
2018-08-16 13:59:30 +00:00
|
|
|
u->lineno = openfile->current->lineno;
|
|
|
|
u->begin = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
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
|
|
|
|
* whitespace character, and (if possible) prepend the remainder of the line
|
|
|
|
* to the next line. Return TRUE if wrapping occurred, and FALSE otherwise. */
|
2019-04-19 07:44:57 +00:00
|
|
|
bool 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-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. */
|
2018-10-22 23:01:23 +00:00
|
|
|
wrap_loc = break_line(line->data, wrap_at, 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. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
|
|
|
|
return FALSE;
|
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* Step forward to the character just after the blank. */
|
2017-12-29 18:27:33 +00:00
|
|
|
wrap_loc += move_mbright(line->data + wrap_loc, 0);
|
|
|
|
|
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')
|
|
|
|
return FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-04-20 12:00:07 +00:00
|
|
|
/* When autoindenting, we don't wrap right after the indentation. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(AUTOINDENT) && wrap_loc == indent_length(line->data))
|
|
|
|
return FALSE;
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(SPLIT_BEGIN);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
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. */
|
2019-04-21 09:41:54 +00:00
|
|
|
if (!is_blank_mbchar(remainder + move_mbleft(remainder, rest_length))) {
|
2014-06-20 16:13:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(ADD);
|
|
|
|
#endif
|
|
|
|
line->data = charealloc(line->data, line_len + 2);
|
|
|
|
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-21 09:58:08 +00:00
|
|
|
/* Join the next line to this one, and delete any extra blanks. */
|
|
|
|
do {
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2019-04-21 09:58:08 +00:00
|
|
|
} while (is_blank_mbchar(&line->data[openfile->current_x]));
|
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)) {
|
2018-01-23 12:09:12 +00:00
|
|
|
size_t tail_x = move_mbleft(line->data, wrap_loc);
|
2019-04-19 08:10:14 +00:00
|
|
|
size_t typed_x = move_mbleft(line->data, cursor_x);
|
2017-12-02 09:06:12 +00:00
|
|
|
|
2019-04-20 17:57:43 +00:00
|
|
|
while ((tail_x != typed_x || cursor_x >= wrap_loc) &&
|
|
|
|
is_blank_mbchar(line->data + tail_x)) {
|
2018-01-23 12:09:12 +00:00
|
|
|
openfile->current_x = tail_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2018-01-23 12:09:12 +00:00
|
|
|
tail_x = move_mbleft(line->data, tail_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-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
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(SPLIT_END);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return 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)
|
2005-07-25 02:33:45 +00:00
|
|
|
/* We are trying to break a chunk off line. We find the last blank such
|
2005-09-20 06:12:54 +00:00
|
|
|
* that the display length to there is at most (goal + 1). If there is
|
|
|
|
* no such blank, then we find the first blank. We then take the last
|
2005-07-25 02:33:45 +00:00
|
|
|
* blank in that group of blanks. The terminating '\0' counts as a
|
2017-02-14 19:53:25 +00:00
|
|
|
* blank, as does a '\n' if snap_at_nl is TRUE. */
|
|
|
|
ssize_t break_line(const char *line, ssize_t goal, bool snap_at_nl)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t lastblank = -1;
|
|
|
|
/* The index of the last blank we found. */
|
|
|
|
ssize_t index = 0;
|
|
|
|
/* The index of the character we are looking at. */
|
|
|
|
size_t column = 0;
|
|
|
|
/* The column position that corresponds with index. */
|
|
|
|
int char_len = 0;
|
|
|
|
/* The length of the current character, in bytes. */
|
|
|
|
|
|
|
|
/* Find the last blank that does not overshoot the target column. */
|
2018-05-27 08:24:02 +00:00
|
|
|
while (*line != '\0' && ((ssize_t)column <= goal)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (is_blank_mbchar(line) || (snap_at_nl && *line == '\n')) {
|
|
|
|
lastblank = index;
|
|
|
|
|
|
|
|
if (*line == '\n')
|
|
|
|
break;
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
char_len = parse_mbchar(line, NULL, &column);
|
|
|
|
line += char_len;
|
|
|
|
index += char_len;
|
|
|
|
}
|
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)
|
2017-12-29 18:27:33 +00:00
|
|
|
return index;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-04-25 15:51:45 +00:00
|
|
|
#ifdef ENABLE_HELP
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we're wrapping a help text and no blank was found, or was
|
|
|
|
* found only as the first character, force a line break. */
|
|
|
|
if (snap_at_nl && lastblank < 1)
|
|
|
|
return (index - char_len);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* If no blank was found within the goal width, seek one after it. */
|
|
|
|
if (lastblank < 0) {
|
|
|
|
while (*line != '\0') {
|
|
|
|
if (is_blank_mbchar(line))
|
|
|
|
lastblank = index;
|
|
|
|
else if (lastblank > 0)
|
|
|
|
return lastblank;
|
|
|
|
|
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
|
|
|
index += char_len;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return -1;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Move the pointer back to the last blank, and then step beyond it. */
|
|
|
|
line = line - index + lastblank;
|
2014-04-14 13:02:43 +00:00
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any consecutive blanks after the last blank. */
|
|
|
|
while (*line != '\0' && is_blank_mbchar(line)) {
|
|
|
|
lastblank += char_len;
|
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastblank;
|
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
|
|
|
|
2017-10-31 16:34:07 +00:00
|
|
|
#if !defined(NANO_TINY) || defined(ENABLE_JUSTIFY)
|
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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t len = 0;
|
|
|
|
char onechar[MAXCHARLEN];
|
|
|
|
int charlen;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*line != '\0') {
|
|
|
|
charlen = parse_mbchar(line, onechar, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!is_blank_mbchar(onechar))
|
|
|
|
break;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
line += charlen;
|
|
|
|
len += charlen;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return len;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 16:34:07 +00:00
|
|
|
#endif /* !NANO_TINY || ENABLE_JUSTIFY */
|
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
|
|
|
/* 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. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void squeeze(linestruct *line, size_t skip)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2019-04-04 11:48:34 +00:00
|
|
|
char *from, *to, *newdata;
|
2019-04-04 11:52:27 +00:00
|
|
|
size_t shrunk = 0;
|
2019-04-04 12:23:07 +00:00
|
|
|
int charlen;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-19 20:15:57 +00:00
|
|
|
newdata = charalloc(strlen(line->data) + 1);
|
|
|
|
strncpy(newdata, line->data, skip);
|
2019-04-04 12:23:07 +00:00
|
|
|
from = line->data + skip;
|
2019-04-04 11:48:34 +00:00
|
|
|
to = newdata + skip;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
while (*from != '\0') {
|
2019-03-19 18:56:58 +00:00
|
|
|
/* If this character is blank, change it to a space,
|
|
|
|
* and pass over all blanks after it. */
|
2019-04-04 11:48:34 +00:00
|
|
|
if (is_blank_mbchar(from)) {
|
2019-04-04 12:11:52 +00:00
|
|
|
from += parse_mbchar(from, NULL, NULL);
|
|
|
|
*(to++) = ' ';
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
while (*from != '\0' && is_blank_mbchar(from)) {
|
|
|
|
charlen = parse_mbchar(from, NULL, NULL);
|
|
|
|
from += charlen;
|
2019-04-04 11:52:27 +00:00
|
|
|
shrunk += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-04-04 12:23:07 +00:00
|
|
|
/* If this character is punctuation, then copy it plus a possible
|
|
|
|
* bracket, and change at most two of subsequent blanks to spaces,
|
|
|
|
* and pass over all blanks after these. */
|
2019-04-04 11:48:34 +00:00
|
|
|
} else if (mbstrchr(punct, from) != NULL) {
|
|
|
|
charlen = parse_mbchar(from, NULL, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-04-04 11:39:11 +00:00
|
|
|
while (charlen > 0) {
|
2019-04-04 12:11:52 +00:00
|
|
|
*(to++) = *(from++);
|
2019-04-04 11:39:11 +00:00
|
|
|
charlen--;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
if (*from != '\0' && mbstrchr(brackets, from) != NULL) {
|
|
|
|
charlen = parse_mbchar(from, NULL, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:39:11 +00:00
|
|
|
while (charlen > 0) {
|
2019-04-04 12:11:52 +00:00
|
|
|
*(to++) = *(from++);
|
2019-04-04 11:39:11 +00:00
|
|
|
charlen--;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
if (*from != '\0' && is_blank_mbchar(from)) {
|
2019-04-04 12:11:52 +00:00
|
|
|
from += parse_mbchar(from, NULL, NULL);
|
|
|
|
*(to++) = ' ';
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-04-04 11:48:34 +00:00
|
|
|
if (*from != '\0' && is_blank_mbchar(from)) {
|
2019-04-04 12:11:52 +00:00
|
|
|
from += parse_mbchar(from, NULL, NULL);
|
|
|
|
*(to++) = ' ';
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
while (*from != '\0' && is_blank_mbchar(from)) {
|
|
|
|
charlen = parse_mbchar(from, NULL, NULL);
|
|
|
|
from += charlen;
|
2019-04-04 11:52:27 +00:00
|
|
|
shrunk += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Leave unchanged anything that is neither blank nor punctuation. */
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2019-04-04 11:48:34 +00:00
|
|
|
charlen = parse_mbchar(from, NULL, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-04-04 11:39:11 +00:00
|
|
|
while (charlen > 0) {
|
2019-04-04 12:11:52 +00:00
|
|
|
*(to++) = *(from++);
|
2019-04-04 11:39:11 +00:00
|
|
|
charlen--;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there are spaces at the end of the line, remove them. */
|
2019-04-04 11:48:34 +00:00
|
|
|
while (to > newdata + skip && *(to - 1) == ' ') {
|
|
|
|
to--;
|
2019-04-04 11:52:27 +00:00
|
|
|
shrunk++;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:52:27 +00:00
|
|
|
if (shrunk > 0) {
|
2019-04-04 11:54:32 +00:00
|
|
|
*to = '\0';
|
2019-03-19 20:15:57 +00:00
|
|
|
free(line->data);
|
|
|
|
line->data = newdata;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else
|
2019-03-19 20:15:57 +00:00
|
|
|
free(newdata);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
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. */
|
|
|
|
#define RECURSION_LIMIT 222
|
|
|
|
|
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
|
|
|
{
|
2018-05-21 19:10:51 +00:00
|
|
|
size_t quote_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;
|
|
|
|
|
2018-05-18 10:48:45 +00:00
|
|
|
quote_len = quote_length(line->data);
|
|
|
|
indent_len = indent_length(line->data + quote_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. */
|
2018-05-18 10:48:45 +00:00
|
|
|
if (line->data[quote_len + indent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the quote part of the preceding line differs, this is a BOP. */
|
2018-05-21 19:29:21 +00:00
|
|
|
if (quote_len != quote_length(line->prev->data) ||
|
|
|
|
strncmp(line->data, line->prev->data, quote_len) != 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 19:10:51 +00:00
|
|
|
prev_dent_len = indent_length(line->prev->data + quote_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. */
|
2018-05-21 19:10:51 +00:00
|
|
|
if (line->prev->data[quote_len + prev_dent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the indentation of the preceding line equals the indentation
|
|
|
|
* of this line, this is not a BOP. */
|
2018-05-21 19:19:47 +00:00
|
|
|
if (prev_dent_len == indent_len && strncmp(line->prev->data + quote_len,
|
|
|
|
line->data + quote_len, indent_len) == 0)
|
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
|
|
|
{
|
2018-05-20 18:56:36 +00:00
|
|
|
size_t quote_len = quote_length(line->data);
|
2018-11-19 12:49:35 +00:00
|
|
|
size_t indent_len = indent_length(line->data + quote_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-19 12:49:35 +00:00
|
|
|
return (line->data[quote_len + indent_len] != '\0');
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-26 09:24:01 +00:00
|
|
|
/* Determine the beginning, length, and quoting of the first found paragraph.
|
2018-11-22 19:00:18 +00:00
|
|
|
* Return TRUE if we found a paragraph, and FALSE otherwise. Furthermore,
|
2018-12-26 18:55:39 +00:00
|
|
|
* return in firstline the first line of the paragraph,
|
|
|
|
* and in *parlen the length of the paragraph. */
|
2019-03-21 16:08:52 +00:00
|
|
|
bool find_paragraph(linestruct **firstline, size_t *const parlen)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *line = *firstline;
|
2018-08-31 17:42:41 +00:00
|
|
|
/* The line of the current paragraph we're searching in. */
|
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
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* Move down to the last line of the paragraph. */
|
|
|
|
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;
|
|
|
|
|
2018-12-26 18:55:39 +00:00
|
|
|
/* We found a paragraph; determine number of lines. */
|
2018-11-25 18:24:08 +00:00
|
|
|
*parlen = line->lineno - (*firstline)->lineno + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2018-12-26 19:52:06 +00:00
|
|
|
/* Tack all the lines of the paragraph (that starts at *line, and consists of
|
|
|
|
* par_len lines) together, skipping
|
|
|
|
* the quoting and indentation on all lines after the first. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void concat_paragraph(linestruct **line, size_t par_len)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2018-08-25 22:53:17 +00:00
|
|
|
while (par_len > 1) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *next_line = (*line)->next;
|
2018-12-26 13:56:29 +00:00
|
|
|
size_t line_len = strlen((*line)->data);
|
2018-08-25 22:53:17 +00:00
|
|
|
size_t next_line_len = strlen(next_line->data);
|
2018-12-26 18:55:39 +00:00
|
|
|
size_t next_quote_len = quote_length(next_line->data);
|
|
|
|
size_t next_lead_len = next_quote_len +
|
|
|
|
indent_length(next_line->data + next_quote_len);
|
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. */
|
2018-12-26 13:56:29 +00:00
|
|
|
if (line_len > 0 && (*line)->data[line_len - 1] != ' ') {
|
|
|
|
(*line)->data = charealloc((*line)->data, line_len + 2);
|
|
|
|
(*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
|
|
|
|
2018-12-26 13:56:29 +00:00
|
|
|
(*line)->data = charealloc((*line)->data,
|
2018-12-26 18:55:39 +00:00
|
|
|
line_len + next_line_len - next_lead_len + 1);
|
|
|
|
strcat((*line)->data, next_line->data + next_lead_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
unlink_node(next_line);
|
|
|
|
par_len--;
|
|
|
|
}
|
2018-12-26 19:52:06 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
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. */
|
|
|
|
if (break_pos == -1 || break_pos + lead_len == line_len)
|
|
|
|
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));
|
|
|
|
(*line)->next->data = charalloc(lead_len + line_len - break_pos + 1);
|
|
|
|
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
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* When requested, snip all trailing blanks. */
|
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
|
|
|
while (break_pos > 0 &&
|
2018-12-26 13:56:29 +00:00
|
|
|
is_blank_mbchar(&(*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
|
|
|
|
* of par_len lines) so they all fit within the target width (wrap_at) and have
|
|
|
|
* their whitespace normalized. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void justify_paragraph(linestruct **line, size_t par_len)
|
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. */
|
2018-12-26 19:52:06 +00:00
|
|
|
size_t quote_len;
|
|
|
|
/* 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. */
|
|
|
|
sampleline = (par_len == 1 ? *line : (*line)->next);
|
|
|
|
|
|
|
|
/* Copy the leading part (quoting + indentation) of the sample line. */
|
|
|
|
quote_len = quote_length(sampleline->data);
|
|
|
|
lead_len = quote_len + indent_length(sampleline->data + quote_len);
|
|
|
|
lead_string = mallocstrncpy(NULL, sampleline->data, lead_len + 1);
|
|
|
|
lead_string[lead_len] = '\0';
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Concatenate all lines of the paragraph into a single line. */
|
2018-12-26 19:52:06 +00:00
|
|
|
concat_paragraph(line, par_len);
|
|
|
|
|
|
|
|
/* Change all blank characters to spaces and remove excess spaces. */
|
2019-03-19 20:15:57 +00:00
|
|
|
squeeze(*line, quote_len + indent_length((*line)->data + quote_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);
|
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Justify the current paragraph, or the entire buffer when full_justify is
|
|
|
|
* TRUE. But if the mark is on, justify only the marked text instead. */
|
2018-08-25 22:44:20 +00:00
|
|
|
void do_justify(bool full_justify)
|
|
|
|
{
|
2018-08-25 22:53:17 +00:00
|
|
|
size_t par_len;
|
2019-03-19 18:56:58 +00:00
|
|
|
/* The number of lines in the original paragraph. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *first_par_line;
|
2019-03-19 18:56:58 +00:00
|
|
|
/* The first line of the paragraph. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *last_par_line;
|
2019-03-19 18:56:58 +00:00
|
|
|
/* The line after the last line of the paragraph. */
|
2018-09-10 22:34:33 +00:00
|
|
|
size_t top_x;
|
|
|
|
/* The top x-coordinate of the paragraph we justify. */
|
2019-01-08 21:21:56 +00:00
|
|
|
size_t bot_x;
|
|
|
|
/* The bottom x-coordinate of the paragraph we justify. */
|
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
|
|
|
|
/* Stash the cursor position, to be stored in the undo item. */
|
|
|
|
ssize_t was_lineno = openfile->current->lineno;
|
|
|
|
size_t was_current_x = openfile->current_x;
|
2018-09-10 22:34:33 +00:00
|
|
|
|
|
|
|
/* We need these to restore the coordinates of the mark after justifying
|
|
|
|
* marked text. */
|
|
|
|
ssize_t was_top_lineno = 0;
|
|
|
|
size_t was_top_x = 0;
|
|
|
|
bool right_side_up = FALSE;
|
2019-01-08 23:48:40 +00:00
|
|
|
|
2019-01-08 21:58:08 +00:00
|
|
|
/* Whether the bottom of the mark is at the end of its line, in which case
|
|
|
|
* we don't need to add a new line after it. */
|
|
|
|
bool ends_at_eol = FALSE;
|
|
|
|
|
2019-01-08 23:48:40 +00:00
|
|
|
/* We need these to hold the leading part (quoting + indentation) of the
|
|
|
|
* line where the marked text begins, whether or not that part is covered
|
|
|
|
* by the mark. */
|
|
|
|
char *the_lead = NULL;
|
|
|
|
size_t lead_len = 0;
|
|
|
|
|
|
|
|
/* We need these to hold the leading part of the line after the line where
|
|
|
|
* the marked text begins (if any). */
|
|
|
|
char *the_second_lead = NULL;
|
|
|
|
size_t second_lead_len = 0;
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
|
|
|
|
2018-09-10 22:34:33 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
/* If the mark is on, do as Pico: treat all marked text as one paragraph. */
|
|
|
|
if (openfile->mark) {
|
2019-01-08 23:48:40 +00:00
|
|
|
size_t quote_len;
|
|
|
|
|
2019-04-28 09:01:51 +00:00
|
|
|
get_region((const linestruct **)&first_par_line, &top_x,
|
|
|
|
(const linestruct **)&last_par_line, &bot_x, &right_side_up);
|
2018-09-10 22:34:33 +00:00
|
|
|
|
2019-04-28 09:01:51 +00:00
|
|
|
/* Save the starting point of the marked region. */
|
2018-09-10 22:34:33 +00:00
|
|
|
was_top_lineno = first_par_line->lineno;
|
|
|
|
was_top_x = top_x;
|
|
|
|
|
|
|
|
par_len = last_par_line->lineno - first_par_line->lineno +
|
|
|
|
(bot_x > 0 ? 1 : 0);
|
2019-01-08 23:48:40 +00:00
|
|
|
|
2019-01-08 21:58:08 +00:00
|
|
|
/* Remember whether the end of the region was at the end of a line. */
|
|
|
|
ends_at_eol = last_par_line->data[bot_x] == '\0';
|
|
|
|
|
2019-01-08 23:48:40 +00:00
|
|
|
/* Copy the leading part that is to be used for the new paragraph. */
|
|
|
|
quote_len = quote_length(first_par_line->data);
|
|
|
|
lead_len = quote_len + indent_length(first_par_line->data + quote_len);
|
|
|
|
the_lead = mallocstrncpy(the_lead, first_par_line->data, lead_len + 1);
|
|
|
|
the_lead[lead_len] = '\0';
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
if (first_par_line != last_par_line) {
|
|
|
|
size_t sample_quote_len = quote_length(first_par_line->next->data);
|
|
|
|
size_t sample_indent_len = indent_length(first_par_line->next->data +
|
|
|
|
sample_quote_len);
|
|
|
|
|
|
|
|
second_lead_len = quote_len + sample_indent_len;
|
|
|
|
the_second_lead = charalloc(second_lead_len + 1);
|
|
|
|
strncpy(the_second_lead, first_par_line->data, quote_len);
|
|
|
|
strncpy(the_second_lead + quote_len, first_par_line->next->data +
|
|
|
|
sample_quote_len, sample_indent_len);
|
|
|
|
the_second_lead[second_lead_len] = '\0';
|
|
|
|
}
|
2018-09-10 22:34:33 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
size_t jus_len;
|
|
|
|
/* The number of lines we're storing in the current cutbuffer. */
|
|
|
|
|
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. */
|
|
|
|
if (full_justify)
|
|
|
|
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. */
|
|
|
|
if (!find_paragraph(&openfile->current, &par_len)) {
|
|
|
|
openfile->current_x = strlen(openfile->filebot->data);
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
first_par_line = openfile->current;
|
2019-03-12 21:36:32 +00:00
|
|
|
top_x = 0;
|
2018-09-01 00:45:27 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
/* Set the number of lines to be pulled into the cutbuffer. */
|
|
|
|
if (full_justify)
|
2019-04-11 13:25:44 +00:00
|
|
|
jus_len = openfile->filebot->lineno - first_par_line->lineno + 1;
|
2019-01-02 17:56:22 +00:00
|
|
|
else
|
|
|
|
jus_len = par_len;
|
2018-08-25 22:44:20 +00:00
|
|
|
|
2019-01-02 17:56:22 +00:00
|
|
|
/* Move down to the last line to be extracted. */
|
2019-04-11 13:12:04 +00:00
|
|
|
for (last_par_line = openfile->current; jus_len > 1; jus_len--)
|
2019-01-02 17:56:22 +00:00
|
|
|
last_par_line = last_par_line->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. */
|
|
|
|
if (last_par_line->next != NULL) {
|
|
|
|
last_par_line = last_par_line->next;
|
|
|
|
bot_x = 0;
|
|
|
|
} else
|
|
|
|
bot_x = strlen(last_par_line->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
|
|
|
|
add_undo(COUPLE_BEGIN);
|
2019-02-24 15:05:22 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("justification"));
|
2018-09-07 17:01:43 +00:00
|
|
|
|
|
|
|
/* Store the original cursor position, in case we unjustify. */
|
|
|
|
openfile->undotop->lineno = was_lineno;
|
|
|
|
openfile->undotop->begin = was_current_x;
|
|
|
|
|
|
|
|
add_undo(CUT);
|
|
|
|
#endif
|
2019-04-11 13:12:04 +00:00
|
|
|
|
|
|
|
/* Do the equivalent of a marked cut into an empty cutbuffer. */
|
|
|
|
cutbuffer = NULL;
|
2019-05-01 09:26:26 +00:00
|
|
|
extract(first_par_line, top_x, last_par_line, bot_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) {
|
2019-01-08 22:42:04 +00:00
|
|
|
size_t line_len = strlen(cutbuffer->data), indent_len;
|
2019-01-08 21:58:08 +00:00
|
|
|
size_t needed_top_extra = (top_x < lead_len ? top_x : lead_len);
|
|
|
|
size_t needed_bot_extra = (bot_x < lead_len ? lead_len - bot_x : 0);
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *line;
|
2018-09-10 22:34:33 +00:00
|
|
|
|
2019-01-08 21:58:08 +00:00
|
|
|
/* If the marked region starts in the middle of a line, and this line
|
|
|
|
* has a leading part, prepend any missing portion of this leading part
|
|
|
|
* to the first line of the extracted region. */
|
|
|
|
if (needed_top_extra > 0) {
|
|
|
|
cutbuffer->data = charealloc(cutbuffer->data,
|
|
|
|
line_len + needed_top_extra + 1);
|
|
|
|
charmove(cutbuffer->data + needed_top_extra, cutbuffer->data,
|
|
|
|
line_len + 1);
|
|
|
|
strncpy(cutbuffer->data, the_lead, needed_top_extra);
|
2019-01-08 22:42:04 +00:00
|
|
|
line_len += needed_top_extra;
|
2019-01-08 21:58:08 +00:00
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* When no portion was missing, nothing needs removing later. */
|
2019-01-08 21:58:08 +00:00
|
|
|
if (top_x > lead_len)
|
|
|
|
needed_top_extra = 0;
|
|
|
|
}
|
|
|
|
|
2019-01-08 22:42:04 +00:00
|
|
|
indent_len = indent_length(cutbuffer->data + lead_len);
|
|
|
|
|
|
|
|
/* Remove extra whitespace after the leading part. */
|
|
|
|
if (indent_len > 0)
|
|
|
|
charmove(cutbuffer->data + lead_len,
|
|
|
|
cutbuffer->data + lead_len + indent_len,
|
|
|
|
line_len - indent_len + 1);
|
|
|
|
|
2019-01-08 21:58:08 +00:00
|
|
|
/* If the marked region ends in the middle of a line, and this line
|
|
|
|
* has a leading part, check if the last line of the extracted region
|
|
|
|
* contains a missing portion of this leading part. If it has no
|
|
|
|
* missing portion, we don't need to append anything below. */
|
2019-03-19 18:56:58 +00:00
|
|
|
if (strncmp(cutbottom->data, the_lead, lead_len - needed_bot_extra) != 0)
|
2019-01-08 21:58:08 +00:00
|
|
|
needed_bot_extra = 0;
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Now justify the extracted region. */
|
2018-09-10 22:34:33 +00:00
|
|
|
concat_paragraph(&cutbuffer, par_len);
|
2019-03-19 20:15:57 +00:00
|
|
|
squeeze(cutbuffer, lead_len);
|
2018-09-10 22:34:33 +00:00
|
|
|
line = cutbuffer;
|
2019-01-08 23:48:40 +00:00
|
|
|
if (the_second_lead != NULL) {
|
|
|
|
rewrap_paragraph(&line, the_second_lead, second_lead_len);
|
|
|
|
free(the_second_lead);
|
|
|
|
} else
|
|
|
|
rewrap_paragraph(&line, the_lead, lead_len);
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* If the marked region started after the beginning of a line, insert
|
|
|
|
* a new line before the new paragraph. But if the region started in
|
|
|
|
* the middle of the line's leading part, no new line is needed: just
|
|
|
|
* remove the (now-redundant) addition we made earlier. */
|
2019-01-08 21:58:08 +00:00
|
|
|
if (top_x > 0) {
|
|
|
|
if (needed_top_extra > 0)
|
|
|
|
charmove(cutbuffer->data, cutbuffer->data + needed_top_extra,
|
|
|
|
strlen(cutbuffer->data) - needed_top_extra + 1);
|
|
|
|
else {
|
|
|
|
cutbuffer->prev = make_new_node(NULL);
|
|
|
|
cutbuffer->prev->data = mallocstrcpy(NULL, "");
|
|
|
|
cutbuffer->prev->next = cutbuffer;
|
|
|
|
cutbuffer = cutbuffer->prev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* If the marked region ended in the middle of a line, insert a new
|
|
|
|
* line after the new paragraph. If the region ended in the middle
|
|
|
|
* of a line's leading part, make the new line start with the missing
|
|
|
|
* portion, so it will become a full leading part when the justified
|
|
|
|
* region is "pasted" back. */
|
2019-01-08 21:58:08 +00:00
|
|
|
if (bot_x > 0 && !ends_at_eol) {
|
2019-04-28 17:21:40 +00:00
|
|
|
line->next = make_new_node(line);
|
|
|
|
line->next->data = mallocstrcpy(NULL, the_lead + needed_bot_extra);
|
2019-01-08 21:58:08 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 23:48:40 +00:00
|
|
|
free(the_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. */
|
|
|
|
justify_paragraph(&jusline, par_len);
|
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. */
|
|
|
|
if (full_justify) {
|
|
|
|
while (find_paragraph(&jusline, &par_len)) {
|
|
|
|
justify_paragraph(&jusline, par_len);
|
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
|
|
|
|
add_undo(PASTE);
|
|
|
|
#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);
|
|
|
|
|
|
|
|
add_undo(COUPLE_END);
|
2019-02-24 15:05:22 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("justification"));
|
2018-09-10 22:34:33 +00:00
|
|
|
|
|
|
|
/* If we justified marked text, restore mark or cursor position. */
|
|
|
|
if (openfile->mark) {
|
|
|
|
if (right_side_up) {
|
|
|
|
openfile->mark = fsfromline(was_top_lineno);
|
|
|
|
openfile->mark_x = was_top_x;
|
|
|
|
} else {
|
|
|
|
openfile->current = fsfromline(was_top_lineno);
|
|
|
|
openfile->current_x = was_top_x;
|
|
|
|
}
|
|
|
|
update_undo(COUPLE_END);
|
|
|
|
}
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-01 00:45:27 +00:00
|
|
|
/* We're done justifying. Restore the old cutbuffer. */
|
|
|
|
cutbuffer = was_cutbuffer;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-10 20:12:07 +00:00
|
|
|
/* Show what we justified on the status bar. */
|
2018-09-10 22:34:33 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->mark)
|
|
|
|
statusbar(_("Justified selection"));
|
|
|
|
else
|
|
|
|
#endif
|
2018-09-10 20:12:07 +00:00
|
|
|
if (full_justify)
|
|
|
|
statusbar(_("Justified file"));
|
|
|
|
else
|
|
|
|
statusbar(_("Justified paragraph"));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
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. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_justify_void(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_justify(FALSE);
|
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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_justify(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)
|
|
|
|
{
|
|
|
|
char *copy_of_command = mallocstrcpy(NULL, 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) {
|
2018-04-22 10:05:19 +00:00
|
|
|
*arguments = (char **)nrealloc(*arguments, ++count * sizeof(char *));
|
|
|
|
(*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
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
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
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *save_search;
|
|
|
|
size_t firstcolumn_save = openfile->firstcolumn;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *edittop_save = openfile->edittop;
|
|
|
|
linestruct *current_save = openfile->current;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Save where we are. */
|
|
|
|
bool proceed = FALSE;
|
|
|
|
/* The return value of this function. */
|
|
|
|
bool result;
|
|
|
|
/* The return value of searching for a misspelled word. */
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
bool right_side_up = FALSE;
|
|
|
|
/* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
|
|
|
|
* FALSE if (current, current_x) is. */
|
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
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Save the current search string, then set it to the misspelled word. */
|
|
|
|
save_search = last_search;
|
|
|
|
last_search = mallocstrcpy(NULL, word);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the mark is on, start at the beginning of the marked region. */
|
|
|
|
if (openfile->mark) {
|
2019-04-28 09:01:51 +00:00
|
|
|
get_region((const linestruct **)&top, &top_x,
|
|
|
|
(const linestruct **)&bot, &bot_x, &right_side_up);
|
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);
|
|
|
|
lastmessage = HUSH;
|
|
|
|
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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Let the user supply a correctly spelled alternative. */
|
|
|
|
proceed = (do_prompt(FALSE, FALSE, MSPELL, word, NULL,
|
|
|
|
edit_refresh, _("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) {
|
|
|
|
do_replace_loop(word, TRUE, current_save, ¤t_x_save);
|
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. */
|
|
|
|
openfile->current = current_save;
|
|
|
|
openfile->current_x = current_x_save;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the string that was last searched for. */
|
|
|
|
free(last_search);
|
|
|
|
last_search = save_search;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the viewport to where it was. */
|
|
|
|
openfile->edittop = edittop_save;
|
|
|
|
openfile->firstcolumn = firstcolumn_save;
|
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
|
|
|
}
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Internal (integrated) spell checking using the spell program,
|
|
|
|
* filtered through the sort and uniq programs. Return NULL for normal
|
|
|
|
* termination, and the error string otherwise. */
|
2019-04-23 09:48:10 +00:00
|
|
|
const char *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. */
|
|
|
|
if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1)
|
|
|
|
return _("Could not create pipe");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Creating misspelled word list, please wait..."));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run spell in. */
|
|
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future spell process). */
|
|
|
|
close(spell_fd[0]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Replace the standard input with the temp file. */
|
|
|
|
if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-07-14 19:27:50 +00:00
|
|
|
if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) {
|
|
|
|
close(tempfile_fd);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto close_pipes_and_exit;
|
2018-07-14 19:27:50 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(tempfile_fd);
|
|
|
|
|
|
|
|
/* Send spell's standard output to the pipe. */
|
|
|
|
if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the spell program; we are using $PATH. */
|
|
|
|
execlp("spell", "spell", NULL);
|
|
|
|
|
|
|
|
/* This should not be reached if spell is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent continues here. */
|
2005-07-25 02:33:45 +00:00
|
|
|
close(spell_fd[1]);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run sort in. */
|
|
|
|
if ((pid_sort = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future sort process). Replace the
|
|
|
|
* standard input with the standard output of the old pipe. */
|
|
|
|
if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Send sort's standard output to the new pipe. */
|
|
|
|
if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[1]);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the sort program. Use -f to ignore case. */
|
|
|
|
execlp("sort", "sort", "-f", NULL);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* This should not be reached if sort is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
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]);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run uniq in. */
|
|
|
|
if ((pid_uniq = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future uniq process). Replace the
|
|
|
|
* standard input with the standard output of the old pipe. */
|
|
|
|
if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[0]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Send uniq's standard output to the new pipe. */
|
|
|
|
if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
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
|
|
|
/* Start the uniq program; we are using PATH. */
|
|
|
|
execlp("uniq", "uniq", NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* This should not be reached if uniq is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The child process was not forked successfully. */
|
|
|
|
if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
|
|
|
|
close(uniq_fd[0]);
|
|
|
|
return _("Could not fork");
|
|
|
|
}
|
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) {
|
2017-12-29 18:27:33 +00:00
|
|
|
close(uniq_fd[0]);
|
|
|
|
return _("Could not get size of pipe buffer");
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-30 18:31:42 +00:00
|
|
|
/* Block SIGWINCHes while reading misspelled words from the pipe. */
|
|
|
|
block_sigwinch(TRUE);
|
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
totalread = 0;
|
2019-04-11 07:14:30 +00:00
|
|
|
buffersize = pipesize + 1;
|
2019-03-30 18:08:17 +00:00
|
|
|
misspellings = charalloc(buffersize);
|
|
|
|
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;
|
2019-03-30 18:08:17 +00:00
|
|
|
misspellings = charealloc(misspellings, buffersize);
|
|
|
|
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);
|
|
|
|
|
2018-04-25 10:27:01 +00:00
|
|
|
/* Do any replacements case sensitive, forward, and without regexes. */
|
|
|
|
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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
|
|
|
|
return _("Error invoking \"spell\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
|
|
|
|
return _("Error invoking \"sort -f\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
|
|
|
|
return _("Error invoking \"uniq\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When all went okay. */
|
|
|
|
return NULL;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-07-25 02:33:45 +00:00
|
|
|
close_pipes_and_exit:
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Don't leak any handles. */
|
|
|
|
close(spell_fd[0]);
|
|
|
|
close(spell_fd[1]);
|
|
|
|
close(sort_fd[0]);
|
|
|
|
close(sort_fd[1]);
|
|
|
|
close(uniq_fd[0]);
|
|
|
|
close(uniq_fd[1]);
|
|
|
|
exit(1);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* External (alternate) spell checking. Return NULL for normal
|
|
|
|
* termination, and the error string otherwise. */
|
2019-04-23 09:48:10 +00:00
|
|
|
const char *do_alt_speller(char *tempfile_name)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
int alt_spell_status;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
|
|
|
size_t pww_save = openfile->placewewant;
|
|
|
|
ssize_t lineno_save = openfile->current->lineno;
|
|
|
|
bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
|
|
|
|
struct stat spellfileinfo;
|
|
|
|
time_t timestamp;
|
|
|
|
pid_t pid_spell;
|
|
|
|
static char **spellargs = NULL;
|
|
|
|
|
|
|
|
/* Get the timestamp and the size of the temporary file. */
|
|
|
|
stat(tempfile_name, &spellfileinfo);
|
|
|
|
timestamp = spellfileinfo.st_mtime;
|
|
|
|
|
|
|
|
/* If the number of bytes to check is zero, get out. */
|
|
|
|
if (spellfileinfo.st_size == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Exit from curses mode. */
|
|
|
|
endwin();
|
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
construct_argument_list(&spellargs, alt_speller, tempfile_name);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Fork a child process and run the alternate spell program in it. */
|
|
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
execvp(spellargs[0], spellargs);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Terminate the child process if no alternate speller is found. */
|
|
|
|
exit(1);
|
|
|
|
} else if (pid_spell < 0)
|
|
|
|
return _("Could not fork");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-27 18:06:23 +00:00
|
|
|
/* Block SIGWINCHes while waiting for the alternate spell checker's end,
|
|
|
|
* so nano doesn't get pushed past the wait(). */
|
2019-03-27 18:44:50 +00:00
|
|
|
block_sigwinch(TRUE);
|
2017-12-29 18:27:33 +00:00
|
|
|
wait(&alt_spell_status);
|
2019-03-27 18:44:50 +00:00
|
|
|
block_sigwinch(FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Reenter curses mode. */
|
|
|
|
doupdate();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the terminal to its previous state. */
|
|
|
|
terminal_init();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0)
|
|
|
|
return invocation_error(alt_speller);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Stat the temporary file again. */
|
2018-08-08 17:44:40 +00:00
|
|
|
stat(tempfile_name, &spellfileinfo);
|
2018-08-08 17:53:05 +00:00
|
|
|
|
|
|
|
/* Use the spell-checked file only when it changed. */
|
2018-08-08 17:44:40 +00:00
|
|
|
if (spellfileinfo.st_mtime != timestamp) {
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Replace the marked text (or entire text) with the corrected text. */
|
|
|
|
if (openfile->mark) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot;
|
2018-08-08 17:53:05 +00:00
|
|
|
size_t top_x, bot_x;
|
|
|
|
bool right_side_up;
|
|
|
|
ssize_t was_mark_lineno = openfile->mark->lineno;
|
2017-02-28 20:57:53 +00:00
|
|
|
|
2019-04-28 09:01:51 +00:00
|
|
|
get_region((const linestruct **)&top, &top_x,
|
|
|
|
(const linestruct **)&bot, &bot_x, &right_side_up);
|
2017-05-12 16:07:32 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
replace_marked_buffer(tempfile_name);
|
2016-12-10 12:20:06 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Adjust the end point of the marked region for any change in
|
|
|
|
* length of the region's last line. */
|
|
|
|
if (right_side_up)
|
|
|
|
current_x_save = openfile->current_x;
|
|
|
|
else
|
|
|
|
openfile->mark_x = openfile->current_x;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Restore the mark. */
|
|
|
|
openfile->mark = fsfromline(was_mark_lineno);
|
|
|
|
} else
|
2017-02-10 01:04:14 +00:00
|
|
|
#endif
|
2018-08-08 17:53:05 +00:00
|
|
|
replace_buffer(tempfile_name);
|
|
|
|
|
|
|
|
/* Go back to the old position. */
|
|
|
|
goto_line_posx(lineno_save, current_x_save);
|
|
|
|
if (was_at_eol || openfile->current_x > strlen(openfile->current->data))
|
|
|
|
openfile->current_x = strlen(openfile->current->data);
|
2018-08-16 13:59:30 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(COUPLE_END);
|
|
|
|
#endif
|
2018-08-08 17:53:05 +00:00
|
|
|
openfile->placewewant = pww_save;
|
|
|
|
adjust_viewport(STATIONARY);
|
2018-08-08 17:44:40 +00:00
|
|
|
}
|
2017-03-01 08:56:38 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return NULL;
|
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
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
bool status;
|
|
|
|
FILE *temp_file;
|
|
|
|
char *temp;
|
2018-04-25 09:33:22 +00:00
|
|
|
unsigned stash[sizeof(flags) / sizeof(flags[0])];
|
|
|
|
/* A storage place for the current flag settings. */
|
2019-04-23 09:48:10 +00:00
|
|
|
const char *result_msg;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(RESTRICTED)) {
|
|
|
|
show_restricted_warning();
|
|
|
|
return;
|
|
|
|
}
|
2010-11-12 06:22:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
temp = safe_tempfile(&temp_file);
|
2016-01-02 16:01:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (temp == NULL) {
|
|
|
|
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)
|
|
|
|
status = write_marked_file(temp, temp_file, TRUE, OVERWRITE);
|
|
|
|
else
|
2005-07-25 02:33:45 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
status = write_file(temp, temp_file, TRUE, OVERWRITE, TRUE);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!status) {
|
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
free(temp);
|
|
|
|
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
|
|
|
|
2018-09-23 12:51:40 +00:00
|
|
|
result_msg = (alt_speller ? do_alt_speller(temp) : do_int_speller(temp));
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
unlink(temp);
|
|
|
|
free(temp);
|
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));
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the spell-checker printed any error messages onscreen, make
|
|
|
|
* sure that they're cleared off. */
|
|
|
|
total_refresh();
|
|
|
|
|
2018-09-23 12:51:40 +00:00
|
|
|
if (result_msg != NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (errno == 0)
|
|
|
|
/* Don't display an error message of "Success". */
|
2018-09-23 12:51:40 +00:00
|
|
|
statusline(ALERT, _("Spell checking failed: %s"), result_msg);
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2018-09-23 12:51:40 +00:00
|
|
|
statusline(ALERT, _("Spell checking failed: %s: %s"), result_msg,
|
2017-12-29 18:27:33 +00:00
|
|
|
strerror(errno));
|
|
|
|
} else
|
|
|
|
statusbar(_("Finished checking spelling"));
|
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
|
2016-01-04 10:05:52 +00:00
|
|
|
/* Run a linting program on the current buffer. Return NULL for normal
|
2014-02-24 10:18:15 +00:00
|
|
|
* termination, and the error string otherwise. */
|
|
|
|
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;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t parsesuccess = 0;
|
|
|
|
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
|
|
|
static char **lintargs = NULL;
|
|
|
|
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
|
|
|
|
|
|
|
if (ISSET(RESTRICTED)) {
|
|
|
|
show_restricted_warning();
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->linter) {
|
|
|
|
statusbar(_("No linter defined for this type of file!"));
|
|
|
|
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) {
|
|
|
|
statusbar(_("Could not create pipe"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
blank_bottombars();
|
2018-05-24 09:34:22 +00:00
|
|
|
currmenu = MLINTER;
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Invoking linter, please wait"));
|
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
construct_argument_list(&lintargs, openfile->syntax->linter, openfile->filename);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Start a new process to run the linter in. */
|
|
|
|
if ((pid_lint = fork()) == 0) {
|
|
|
|
|
|
|
|
/* Child continues here (i.e. the future linting process). */
|
|
|
|
close(lint_fd[0]);
|
|
|
|
|
|
|
|
/* Send the linter's standard output + err to the pipe. */
|
|
|
|
if (dup2(lint_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
exit(9);
|
|
|
|
if (dup2(lint_fd[1], STDERR_FILENO) != STDERR_FILENO)
|
|
|
|
exit(9);
|
|
|
|
|
|
|
|
close(lint_fd[1]);
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
close(lint_fd[0]);
|
|
|
|
statusbar(_("Could not fork"));
|
|
|
|
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) {
|
2017-12-29 18:27:33 +00:00
|
|
|
close(lint_fd[0]);
|
|
|
|
statusbar(_("Could not get size of pipe buffer"));
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
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;
|
|
|
|
lintings = charalloc(buffersize);
|
|
|
|
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;
|
|
|
|
lintings = charealloc(lintings, buffersize);
|
|
|
|
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
|
|
|
|
|
|
|
/* Process the linter output. */
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer = lintings;
|
|
|
|
onelint = lintings;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
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-04-11 07:36:49 +00:00
|
|
|
char *message = mallocstrcpy(NULL, onelint);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* At the moment we handle the following formats:
|
|
|
|
*
|
|
|
|
* filenameorcategory:line:column:message (e.g. splint)
|
|
|
|
* filenameorcategory:line,column:message (e.g. pylint)
|
2017-12-29 20:35:14 +00:00
|
|
|
* filenameorcategory:line:message (e.g. pyflakes) */
|
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. */
|
|
|
|
parsesuccess++;
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = nmalloc(sizeof(lintstruct));
|
|
|
|
curlint->next = NULL;
|
|
|
|
curlint->prev = tmplint;
|
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint->prev->next = curlint;
|
|
|
|
curlint->msg = mallocstrcpy(NULL, message);
|
|
|
|
curlint->lineno = tmplineno;
|
|
|
|
curlint->colno = tmpcolno;
|
|
|
|
curlint->filename = mallocstrcpy(NULL, filename);
|
|
|
|
|
|
|
|
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-04-23 09:48:10 +00:00
|
|
|
statusbar(invocation_error(openfile->syntax->linter));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (parsesuccess == 0) {
|
|
|
|
statusline(HUSH, _("Got 0 parsable lines from command: %s"),
|
|
|
|
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 &&
|
2018-05-25 17:25:11 +00:00
|
|
|
(openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->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;
|
2018-05-25 17:25:11 +00:00
|
|
|
while (openfile != started_at && (openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->st_ino != lintfileinfo.st_ino))
|
2018-05-24 16:28:59 +00:00
|
|
|
openfile = openfile->next;
|
2018-05-24 16:14:35 +00:00
|
|
|
|
2018-05-25 17:25:11 +00:00
|
|
|
if (openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->st_ino != lintfileinfo.st_ino) {
|
2018-03-25 19:36:13 +00:00
|
|
|
char *msg = charalloc(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
|
2018-03-25 19:36:13 +00:00
|
|
|
char *dontwantfile = mallocstrcpy(NULL, curlint->filename);
|
|
|
|
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) {
|
2018-10-24 08:23:39 +00:00
|
|
|
statusbar(_("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) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(curlint->lineno, curlint->colno - 1);
|
|
|
|
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;
|
2018-10-17 18:17:03 +00:00
|
|
|
} else if (func == do_help_void) {
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
|
|
|
do_help_void();
|
2018-09-29 07:40:53 +00:00
|
|
|
} else if (func == do_page_up || func == do_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"));
|
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
|
|
|
}
|
2018-09-29 07:46:05 +00:00
|
|
|
} else if (func == do_page_down || func == do_next_block) {
|
|
|
|
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"));
|
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
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
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
|
|
|
|
|
|
|
currmenu = MMOST;
|
|
|
|
titlebar(NULL);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
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
|
2005-08-10 22:51:49 +00:00
|
|
|
/* Our own version of "wc". Note that its character counts are in
|
|
|
|
* multibyte characters instead of single-byte characters. */
|
2005-07-25 04:21:46 +00:00
|
|
|
void do_wordlinechar_count(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t words = 0, chars = 0;
|
|
|
|
ssize_t nlines = 0;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *current_save = openfile->current;
|
|
|
|
linestruct *was_mark = openfile->mark;
|
|
|
|
linestruct *top, *bot;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t top_x, bot_x;
|
|
|
|
|
|
|
|
/* If the mark is on, partition the buffer so that it
|
|
|
|
* contains only the marked text, and turn the mark off. */
|
|
|
|
if (was_mark) {
|
2019-04-28 09:01:51 +00:00
|
|
|
get_region((const linestruct **)&top, &top_x,
|
|
|
|
(const linestruct **)&bot, &bot_x, NULL);
|
2019-03-21 16:18:50 +00:00
|
|
|
filepart = partition_buffer(top, top_x, bot, bot_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start at 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;
|
|
|
|
|
|
|
|
/* Keep moving to the next word (counting punctuation characters as
|
|
|
|
* part of a word, as "wc -w" does), without updating the screen,
|
|
|
|
* until we reach the end of the file, incrementing the total word
|
|
|
|
* count whenever we're on a word just before moving. */
|
|
|
|
while (openfile->current != openfile->filebot ||
|
|
|
|
openfile->current->data[openfile->current_x] != '\0') {
|
2018-09-09 19:01:09 +00:00
|
|
|
if (do_next_word(FALSE, TRUE))
|
2017-12-29 18:27:33 +00:00
|
|
|
words++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the total line and character counts, as "wc -l" and "wc -c"
|
|
|
|
* do, but get the latter in multibyte characters. */
|
|
|
|
if (was_mark) {
|
2019-03-21 16:23:49 +00:00
|
|
|
nlines = openfile->filebot->lineno - openfile->filetop->lineno + 1;
|
|
|
|
chars = get_totsize(openfile->filetop, openfile->filebot);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Unpartition the buffer so that it contains all the text
|
|
|
|
* again, and turn the mark back on. */
|
2019-03-21 16:18:50 +00:00
|
|
|
unpartition_buffer(&filepart);
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = was_mark;
|
|
|
|
} else {
|
|
|
|
nlines = openfile->filebot->lineno;
|
|
|
|
chars = openfile->totsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore where we were. */
|
|
|
|
openfile->current = current_save;
|
|
|
|
openfile->current_x = current_x_save;
|
|
|
|
|
|
|
|
/* Display the total word, line, and character counts on the statusbar. */
|
|
|
|
statusline(HUSH, _("%sWords: %zu Lines: %zd Chars: %zu"), was_mark ?
|
|
|
|
_("In Selection: ") : "", words, nlines, 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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
int *kbinput;
|
|
|
|
size_t kbinput_len, i;
|
|
|
|
char *output;
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* TRANSLATORS: This is displayed when the next keystroke will be
|
|
|
|
* inserted verbatim. */
|
|
|
|
statusbar(_("Verbatim Input"));
|
|
|
|
place_the_cursor();
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Read in all the verbatim characters. */
|
|
|
|
kbinput = get_verbatim_kbinput(edit, &kbinput_len);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Unsuppress cursor-position display or blank the statusbar. */
|
|
|
|
if (ISSET(CONSTANT_SHOW))
|
|
|
|
suppress_cursorpos = FALSE;
|
|
|
|
else
|
|
|
|
wipe_statusbar();
|
2006-05-27 17:39:19 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Display all the verbatim characters at once, not filtering out
|
|
|
|
* control characters. */
|
|
|
|
output = charalloc(kbinput_len + 1);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (i = 0; i < kbinput_len; i++)
|
|
|
|
output[i] = (char)kbinput[i];
|
|
|
|
output[i] = '\0';
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(kbinput);
|
2006-12-02 17:22:21 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output(output, kbinput_len, TRUE);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(output);
|
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. */
|
|
|
|
while (is_word_mbchar(&text[length], FALSE))
|
|
|
|
length = move_mbright(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. */
|
2019-01-06 16:50:53 +00:00
|
|
|
word = charalloc(length + 1);
|
|
|
|
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;
|
2017-12-29 18:27:33 +00:00
|
|
|
completion_word *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) {
|
|
|
|
completion_word *dropit = list_of_completions;
|
|
|
|
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) {
|
2018-12-31 02:34:40 +00:00
|
|
|
size_t step_left = move_mbleft(openfile->current->data, start_of_shard);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!is_word_mbchar(&openfile->current->data[step_left], FALSE))
|
|
|
|
break;
|
|
|
|
start_of_shard = step_left;
|
|
|
|
}
|
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) {
|
2018-07-13 11:37:33 +00:00
|
|
|
statusbar(_("No word fragment"));
|
2017-12-29 18:27:33 +00:00
|
|
|
pletion_line = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
shard = charalloc(openfile->current_x - start_of_shard + 1);
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
if (!is_word_mbchar(&pletion_line->data[i + j], FALSE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If the match is not a separate word, skip it. */
|
|
|
|
if (i > 0 && is_word_mbchar(&pletion_line->data[
|
|
|
|
move_mbleft(pletion_line->data, i)], FALSE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If this match is the shard itself, ignore it. */
|
|
|
|
if (pletion_line == openfile->current &&
|
|
|
|
i == openfile->current_x - shard_length)
|
|
|
|
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. */
|
|
|
|
some_word = (completion_word *)nmalloc(sizeof(completion_word));
|
|
|
|
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. */
|
|
|
|
do_output(&completion[shard_length],
|
|
|
|
strlen(completion) - shard_length, FALSE);
|
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) {
|
|
|
|
statusline(ALERT, _("No further matches"));
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
} else
|
|
|
|
/* TRANSLATORS: Shown when there are zero possible completions. */
|
|
|
|
statusline(ALERT, _("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 */
|