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
|
|
|
* *
|
2020-01-15 10:42:38 +00:00
|
|
|
* Copyright (C) 1999-2011, 2013-2020 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>
|
|
|
|
|
2019-12-06 11:19:32 +00:00
|
|
|
#if defined(__APPLE__) && !defined(st_mtim)
|
|
|
|
#define st_mtim st_mtimespec
|
|
|
|
#endif
|
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
{
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2020-01-05 13:21:46 +00:00
|
|
|
if (openfile->syntax && openfile->syntax->tab && !bracketed_paste)
|
2020-01-08 10:09:57 +00:00
|
|
|
inject(openfile->syntax->tab, strlen(openfile->syntax->tab), FALSE);
|
2018-10-29 19:40:55 +00:00
|
|
|
else
|
|
|
|
#endif
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2020-01-05 13:21:46 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES) && !bracketed_paste) {
|
2017-12-29 18:27:33 +00:00
|
|
|
char *spaces = charalloc(tabsize + 1);
|
|
|
|
size_t length = tabsize - (xplustabs() % tabsize);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2019-09-18 13:20:08 +00:00
|
|
|
memset(spaces, ' ', length);
|
2017-12-29 18:27:33 +00:00
|
|
|
spaces[length] = '\0';
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2020-01-08 10:09:57 +00:00
|
|
|
inject(spaces, length, FALSE);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(spaces);
|
|
|
|
} else
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2020-01-08 10:09:57 +00:00
|
|
|
inject((char *)"\t", 1, FALSE);
|
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);
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + indent_len, line->data, length + 1);
|
2019-12-06 11:34:57 +00:00
|
|
|
memcpy(line->data, indentation, indent_len);
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize += indent_len;
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Compensate for the change in the current line. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
2017-12-29 20:35:14 +00:00
|
|
|
openfile->mark_x += indent_len;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += indent_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2017-07-13 22:22:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Indent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* This inserts either a tab character or a tab's worth of spaces,
|
|
|
|
* depending on whether --tabstospaces is in effect. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_indent(void)
|
2006-04-28 13:19:56 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *indentation;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Use either all the marked lines or just the current line. */
|
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. */
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax && openfile->syntax->tab)
|
|
|
|
indentation = mallocstrcpy(indentation, openfile->syntax->tab);
|
|
|
|
else
|
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
2019-09-18 13:20:08 +00:00
|
|
|
memset(indentation, ' ', tabsize);
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation[tabsize] = '\0';
|
|
|
|
} else {
|
|
|
|
indentation[0] = '\t';
|
|
|
|
indentation[1] = '\0';
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(INDENT, NULL);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, adding an indent to the non-empty ones,
|
|
|
|
* and recording whatever was added in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
char *real_indent = (line->data[0] == '\0') ? "" : indentation;
|
2017-07-07 16:06:55 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indent_a_line(line, real_indent);
|
|
|
|
update_multiline_undo(line->lineno, real_indent);
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(indentation);
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
2019-04-13 09:43:02 +00:00
|
|
|
ensure_firstcolumn_is_aligned();
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
2006-04-28 13:19:56 +00:00
|
|
|
}
|
|
|
|
|
2017-12-07 20:09:11 +00:00
|
|
|
/* Return the number of bytes of whitespace at the start of the given text,
|
|
|
|
* but at most a tab's worth. */
|
2017-07-07 18:53:00 +00:00
|
|
|
size_t length_of_white(const char *text)
|
|
|
|
{
|
2019-09-22 15:38:22 +00:00
|
|
|
size_t white_count = 0;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2018-10-29 19:40:55 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax && openfile->syntax->tab) {
|
|
|
|
size_t thelength = strlen(openfile->syntax->tab);
|
|
|
|
|
|
|
|
while (text[white_count] == openfile->syntax->tab[white_count])
|
|
|
|
if (++white_count == thelength)
|
|
|
|
return thelength;
|
|
|
|
|
|
|
|
white_count = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
if (*text == '\t')
|
2019-09-22 15:38:22 +00:00
|
|
|
return ++white_count;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*text != ' ')
|
2019-09-22 15:38:22 +00:00
|
|
|
return white_count;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2019-09-22 15:38:22 +00:00
|
|
|
if (++white_count == tabsize)
|
2017-12-29 18:27:33 +00:00
|
|
|
return tabsize;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
text++;
|
|
|
|
}
|
2017-07-07 18:53:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-24 10:25:10 +00:00
|
|
|
/* Adjust the positions of mark and cursor when they are on the given line. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void compensate_leftward(linestruct *line, size_t leftshift)
|
2017-12-24 10:25:10 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->mark) {
|
|
|
|
if (openfile->mark_x < leftshift)
|
|
|
|
openfile->mark_x = 0;
|
|
|
|
else
|
|
|
|
openfile->mark_x -= leftshift;
|
|
|
|
}
|
2017-12-24 10:25:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->current) {
|
|
|
|
if (openfile->current_x < leftshift)
|
|
|
|
openfile->current_x = 0;
|
|
|
|
else
|
|
|
|
openfile->current_x -= leftshift;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2017-12-24 10:25:10 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 11:25:20 +00:00
|
|
|
/* Remove an indent from the given line. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void unindent_a_line(linestruct *line, size_t indent_len)
|
2017-07-13 22:38:26 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t length = strlen(line->data);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the indent is empty, don't change the line. */
|
|
|
|
if (indent_len == 0)
|
|
|
|
return;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Remove the first tab's worth of whitespace from this line. */
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data, line->data + indent_len, length - indent_len + 1);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= indent_len;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when they are affected. */
|
|
|
|
compensate_leftward(line, indent_len);
|
2017-07-13 22:38:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Unindent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* The removed indent can be a mixture of spaces plus at most one tab. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_unindent(void)
|
2017-07-10 20:48:50 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Use either all the marked lines or just the current line. */
|
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
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(UNINDENT, NULL);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, removing their leading indent where
|
|
|
|
* possible, and saving the removed whitespace in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
size_t indent_len = length_of_white(line->data);
|
2019-10-14 17:51:24 +00:00
|
|
|
char *indentation = measured_copy(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. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void handle_indent_action(undostruct *u, bool undoing, bool add_indent)
|
2017-07-13 23:15:58 +00:00
|
|
|
{
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group = u->grouping;
|
2019-05-24 16:56:34 +00:00
|
|
|
linestruct *line = line_from_number(group->top_line);
|
2018-04-01 09:36:27 +00:00
|
|
|
|
|
|
|
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 */
|
|
|
|
|
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);
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + pre_len, line->data, line_len + 1);
|
|
|
|
memmove(line->data, comment_seq, pre_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (post_len > 0)
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + pre_len + line_len, post_seq, post_len + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
openfile->totsize += pre_len + post_len;
|
|
|
|
|
|
|
|
/* If needed, adjust the position of the mark and of the cursor. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
|
|
|
openfile->mark_x += pre_len;
|
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += pre_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the line is commented, report it as uncommentable, or uncomment it. */
|
|
|
|
if (strncmp(line->data, comment_seq, pre_len) == 0 && (post_len == 0 ||
|
|
|
|
strcmp(line->data + line_len - post_len, post_seq) == 0)) {
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == PREFLIGHT)
|
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Erase the comment prefix by moving the non-comment part. */
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data, line->data + pre_len, line_len - pre_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Truncate the postfix if there was one. */
|
|
|
|
line->data[line_len - pre_len - post_len] = '\0';
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= pre_len + post_len;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when needed. */
|
|
|
|
compensate_leftward(line, pre_len);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-04-01 08:53:15 +00:00
|
|
|
/* Comment or uncomment the current line or the marked lines. */
|
|
|
|
void do_comment(void)
|
|
|
|
{
|
|
|
|
const char *comment_seq = GENERAL_COMMENT_CHARACTER;
|
|
|
|
undo_type action = UNCOMMENT;
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *top, *bot, *line;
|
2018-04-01 08:53:15 +00:00
|
|
|
bool empty, all_empty = TRUE;
|
|
|
|
|
|
|
|
#ifdef ENABLE_COLOR
|
|
|
|
if (openfile->syntax)
|
|
|
|
comment_seq = openfile->syntax->comment;
|
|
|
|
|
|
|
|
if (*comment_seq == '\0') {
|
|
|
|
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;
|
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(action, NULL);
|
2018-04-01 08:53:15 +00:00
|
|
|
|
|
|
|
/* Store the comment sequence used for the operation, because it could
|
|
|
|
* change when the file name changes; we need to know what it was. */
|
2019-10-13 10:24:27 +00:00
|
|
|
openfile->current_undo->strdata = copy_of(comment_seq);
|
2018-04-01 08:53:15 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Comment/uncomment each of the selected lines when possible, and
|
|
|
|
* store undo data when a line changed. */
|
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. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void handle_comment_action(undostruct *u, bool undoing, bool add_comment)
|
2016-05-25 20:13:50 +00:00
|
|
|
{
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group = u->grouping;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the commenter adjust it. */
|
|
|
|
if (!undoing)
|
|
|
|
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-05-24 16:56:34 +00:00
|
|
|
linestruct *f = line_from_number(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
|
|
|
|
|
2019-10-02 15:46:20 +00:00
|
|
|
/* Undo a cut, or redo a paste. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void undo_cut(undostruct *u)
|
2008-09-16 21:35:19 +00:00
|
|
|
{
|
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
|
|
|
}
|
|
|
|
|
2019-10-02 15:46:20 +00:00
|
|
|
/* Redo a cut, or undo a paste. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void redo_cut(undostruct *u)
|
2014-04-08 18:38:45 +00:00
|
|
|
{
|
2019-04-28 17:21:40 +00:00
|
|
|
linestruct *oldcutbuffer = cutbuffer;
|
2017-07-01 11:25:11 +00:00
|
|
|
|
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
|
|
|
|
2019-05-24 16:56:34 +00:00
|
|
|
openfile->mark = line_from_number(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
|
|
|
|
2019-08-09 17:34:03 +00:00
|
|
|
do_snip(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)
|
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *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
|
|
|
|
2019-05-24 17:02:14 +00:00
|
|
|
if (u->type <= REPLACE)
|
2019-05-24 16:56:34 +00:00
|
|
|
f = line_from_number(u->mark_begin_lineno);
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = u->begin;
|
2019-10-02 15:46:20 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
2018-05-27 15:53:36 +00:00
|
|
|
/* TRANSLATORS: The next thirteen strings describe actions
|
2018-05-27 16:35:52 +00:00
|
|
|
* that are undone or redone. They are all nouns, not verbs. */
|
2019-02-24 15:05:22 +00:00
|
|
|
undidmsg = _("addition");
|
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);
|
2019-10-13 10:24:27 +00:00
|
|
|
t->data = copy_of(u->strdata);
|
2019-10-14 17:51:24 +00:00
|
|
|
data = measured_copy(f->data, u->mark_begin_x + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
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;
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case 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;
|
2019-05-24 16:56:34 +00:00
|
|
|
openfile->mark = line_from_number(u->mark_begin_lineno);
|
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
|
|
|
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);
|
2019-10-11 13:29:15 +00:00
|
|
|
openfile->current_y = u->mark_begin_lineno;
|
|
|
|
adjust_viewport(STATIONARY);
|
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;
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->undotop;
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u == NULL || u == openfile->current_undo) {
|
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
|
|
|
|
2019-05-24 17:02:14 +00:00
|
|
|
if (u->type <= REPLACE)
|
2019-05-24 16:56:34 +00:00
|
|
|
f = line_from_number(u->mark_begin_lineno);
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
2019-02-25 09:04:10 +00:00
|
|
|
redidmsg = _("addition");
|
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);
|
2019-10-13 10:24:27 +00:00
|
|
|
shoveline->data = copy_of(u->strdata);
|
2019-10-14 17:51:24 +00:00
|
|
|
data = measured_copy(f->data, u->begin + 1);
|
2017-12-29 18:27:33 +00:00
|
|
|
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);
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case 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);
|
2019-10-11 13:29:15 +00:00
|
|
|
adjust_viewport(STATIONARY);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case INDENT:
|
|
|
|
handle_indent_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("indent");
|
|
|
|
break;
|
|
|
|
case UNINDENT:
|
|
|
|
handle_indent_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("unindent");
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_COMMENT
|
|
|
|
case COMMENT:
|
|
|
|
handle_comment_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("comment");
|
|
|
|
break;
|
|
|
|
case UNCOMMENT:
|
|
|
|
handle_comment_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("uncomment");
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (redidmsg)
|
2019-02-24 15:05:22 +00:00
|
|
|
statusline(HUSH, _("Redid %s"), redidmsg);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2014-06-09 10:01:54 +00:00
|
|
|
openfile->current_undo = u;
|
|
|
|
openfile->last_action = OTHER;
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
|
|
|
|
openfile->totsize = u->newsize;
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2020-01-05 13:21:46 +00:00
|
|
|
if (ISSET(AUTOINDENT) && !bracketed_paste) {
|
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
|
2020-01-05 13:21:46 +00:00
|
|
|
if (ISSET(AUTOINDENT) && !bracketed_paste) {
|
2018-05-22 19:10:10 +00:00
|
|
|
/* Copy the whitespace from the sample line to the new one. */
|
|
|
|
strncpy(newnode->data, sampleline->data, extra);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there were only blanks before the cursor, trim them. */
|
|
|
|
if (allblanks)
|
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2019-04-04 12:44:44 +00:00
|
|
|
/* Make the current line end at the cursor position. */
|
|
|
|
openfile->current->data[openfile->current_x] = '\0';
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(ENTER, NULL);
|
2017-04-06 18:34:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the mark if it was on the current line after the cursor. */
|
|
|
|
if (openfile->mark == openfile->current &&
|
|
|
|
openfile->mark_x > openfile->current_x) {
|
|
|
|
openfile->mark = newnode;
|
|
|
|
openfile->mark_x += extra - openfile->current_x;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Insert the newly created line after the current one and renumber. */
|
2017-12-29 18:27:33 +00:00
|
|
|
splice_node(openfile->current, newnode);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(newnode);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Put the cursor on the new line, after any automatic whitespace. */
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current = newnode;
|
|
|
|
openfile->current_x = extra;
|
|
|
|
openfile->placewewant = xplustabs();
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize++;
|
|
|
|
set_modified();
|
2005-09-13 04:45:46 +00:00
|
|
|
|
2015-11-11 19:15:36 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-07-22 07:08:35 +00:00
|
|
|
if (ISSET(AUTOINDENT) && !allblanks)
|
|
|
|
openfile->totsize += extra;
|
2017-12-29 18:27:33 +00:00
|
|
|
update_undo(ENTER);
|
2015-11-11 19:15:36 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2018-02-16 10:40:03 +00:00
|
|
|
focusing = FALSE;
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
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];
|
2019-10-27 09:10:03 +00:00
|
|
|
/* The pipes through which text will be written and read. */
|
2018-05-16 01:20:11 +00:00
|
|
|
const bool should_pipe = (command[0] == '|');
|
2018-04-23 11:04:31 +00:00
|
|
|
FILE *stream;
|
2017-12-29 18:27:33 +00:00
|
|
|
struct sigaction oldaction, newaction;
|
|
|
|
/* Original and temporary handlers for SIGINT. */
|
|
|
|
|
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)) {
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("Could not create pipe"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
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) {
|
2019-11-27 10:58:40 +00:00
|
|
|
const char *theshell = getenv("SHELL");
|
2019-11-17 07:20:32 +00:00
|
|
|
|
2019-11-27 10:58:40 +00:00
|
|
|
if (theshell == NULL)
|
|
|
|
theshell = (char *)"/bin/sh";
|
2019-11-17 07:20:32 +00:00
|
|
|
|
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. */
|
2019-11-27 10:58:40 +00:00
|
|
|
execl(theshell, tail(theshell), "-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) {
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("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)) {
|
2019-05-30 15:32:09 +00:00
|
|
|
openfile = openfile->prev;
|
2018-05-24 19:09:14 +00:00
|
|
|
if (openfile->mark)
|
2019-08-09 17:34:03 +00:00
|
|
|
do_snip(TRUE, TRUE, FALSE, FALSE);
|
2018-05-24 19:09:14 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
2019-10-27 15:26:03 +00:00
|
|
|
add_undo(COUPLE_BEGIN, "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;
|
|
|
|
}
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(CUT, NULL);
|
2019-08-09 17:34:03 +00:00
|
|
|
do_snip(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))
|
2019-05-30 15:32:09 +00:00
|
|
|
openfile = openfile->next;
|
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. */
|
2019-05-27 15:24:35 +00:00
|
|
|
enable_kb_interrupt();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-05-24 08:04:59 +00:00
|
|
|
/* Set up a signal handler so that ^C will terminate the forked process. */
|
|
|
|
newaction.sa_handler = cancel_the_command;
|
|
|
|
newaction.sa_flags = 0;
|
|
|
|
sigaction(SIGINT, &newaction, &oldaction);
|
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)) {
|
2019-10-27 15:26:03 +00:00
|
|
|
/* TRANSLATORS: The next two go with Undid/Redid messages. */
|
2019-10-26 15:05:03 +00:00
|
|
|
add_undo(COUPLE_END, N_("filtering"));
|
2018-08-04 07:51:32 +00:00
|
|
|
}
|
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. */
|
2019-05-28 15:39:19 +00:00
|
|
|
wait(NULL);
|
|
|
|
if (should_pipe)
|
|
|
|
wait(NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-05-24 08:04:59 +00:00
|
|
|
/* Restore the original handler for SIGINT. */
|
|
|
|
sigaction(SIGINT, &oldaction, NULL);
|
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. */
|
2019-10-02 15:18:51 +00:00
|
|
|
void discard_until(const undostruct *thisitem, openfilestruct *thefile, bool keep)
|
2015-12-03 08:50:34 +00:00
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *dropit = thefile->undotop;
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *group;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
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) {
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *next = group->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
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. */
|
2019-10-02 15:18:51 +00:00
|
|
|
thefile->current_undo = (undostruct *)thisitem;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* 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)
|
2019-10-02 15:18:51 +00:00
|
|
|
thefile->last_saved = (undostruct *)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. */
|
2019-10-09 17:12:07 +00:00
|
|
|
void add_undo(undo_type action, const char *message)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2019-10-02 15:39:45 +00:00
|
|
|
undostruct *u = nmalloc(sizeof(undostruct));
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2019-10-02 15:39:45 +00:00
|
|
|
/* Initialize the newly allocated undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
u->type = action;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->strdata = NULL;
|
|
|
|
u->cutbuffer = NULL;
|
|
|
|
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->grouping = NULL;
|
2019-10-02 15:39:45 +00:00
|
|
|
u->xflags = 0;
|
|
|
|
|
|
|
|
/* Blow away any undone items. */
|
|
|
|
discard_until(openfile->current_undo, openfile, TRUE);
|
2018-06-01 14:45:37 +00:00
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2018-06-01 14:45:37 +00:00
|
|
|
/* If some action caused automatic long-line wrapping, insert the
|
|
|
|
* SPLIT_BEGIN item underneath that action's undo item. Otherwise,
|
|
|
|
* just add the new item to the top of the undo stack. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->type == SPLIT_BEGIN) {
|
|
|
|
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;
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
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);
|
2019-10-21 10:35:14 +00:00
|
|
|
int charlen = collect_char(&openfile->current->data[u->begin],
|
|
|
|
char_buf);
|
2019-06-09 15:07:02 +00:00
|
|
|
char_buf[charlen] = '\0';
|
2017-12-29 18:27:33 +00:00
|
|
|
u->strdata = char_buf;
|
|
|
|
if (u->type == BACK)
|
2019-06-09 15:07:02 +00:00
|
|
|
u->mark_begin_x += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
2017-12-29 18:27:33 +00:00
|
|
|
case JOIN:
|
|
|
|
if (openfile->current->next) {
|
|
|
|
if (u->type == BACK) {
|
|
|
|
u->lineno = openfile->current->next->lineno;
|
|
|
|
u->begin = 0;
|
|
|
|
}
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(openfile->current->next->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
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:
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(openfile->current->data);
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
case SPLIT_BEGIN:
|
|
|
|
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:
|
2019-10-13 15:31:53 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2019-10-11 13:29:15 +00:00
|
|
|
u->mark_begin_lineno = openfile->current_y;
|
2019-10-11 15:55:46 +00:00
|
|
|
/* Fall-through. */
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_END:
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(_(message));
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case INDENT:
|
|
|
|
case UNINDENT:
|
2016-05-25 20:13:50 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2017-12-29 18:27:33 +00:00
|
|
|
case COMMENT:
|
|
|
|
case UNCOMMENT:
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
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
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->current_undo;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If there already is a group and the current line is contiguous with it,
|
|
|
|
* extend the group; otherwise, create a new group. */
|
|
|
|
if (u->grouping && u->grouping->bottom_line + 1 == lineno) {
|
|
|
|
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 *));
|
2019-10-13 10:24:27 +00:00
|
|
|
u->grouping->indentations[number_of_lines - 1] = copy_of(indentation);
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2019-10-02 17:08:24 +00:00
|
|
|
groupstruct *born = nmalloc(sizeof(groupstruct));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
born->next = u->grouping;
|
|
|
|
u->grouping = born;
|
|
|
|
born->top_line = lineno;
|
|
|
|
born->bottom_line = lineno;
|
|
|
|
|
|
|
|
u->grouping->indentations = (char **)nmalloc(sizeof(char *));
|
2019-10-13 10:24:27 +00:00
|
|
|
u->grouping->indentations[0] = copy_of(indentation);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Store the file size after the change, to be used when redoing. */
|
|
|
|
u->newsize = openfile->totsize;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 18:25:21 +00:00
|
|
|
/* Update an undo item with (among other things) the file size and
|
|
|
|
* cursor position after the given action. */
|
2008-08-03 04:48:05 +00:00
|
|
|
void update_undo(undo_type action)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2019-10-02 15:18:51 +00:00
|
|
|
undostruct *u = openfile->undotop;
|
2018-03-05 09:18:11 +00:00
|
|
|
char *char_buf;
|
2019-06-09 15:07:02 +00:00
|
|
|
int charlen;
|
2008-08-01 03:50:20 +00:00
|
|
|
|
2019-03-17 18:46:40 +00:00
|
|
|
if (u->type != action)
|
|
|
|
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);
|
2019-10-21 10:35:14 +00:00
|
|
|
charlen = collect_char(&openfile->current->data[u->mark_begin_x],
|
|
|
|
char_buf);
|
2018-03-05 09:18:11 +00:00
|
|
|
u->strdata = addstrings(u->strdata, u->strdata ? strlen(u->strdata) : 0,
|
2019-06-09 15:07:02 +00:00
|
|
|
char_buf, charlen);
|
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:
|
2019-10-13 10:24:27 +00:00
|
|
|
u->strdata = copy_of(openfile->current->data);
|
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->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);
|
2019-10-21 10:35:14 +00:00
|
|
|
charlen = collect_char(&openfile->current->data[openfile->current_x],
|
|
|
|
char_buf);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current_x == u->begin) {
|
|
|
|
/* They deleted more: add removed character after earlier stuff. */
|
2019-06-09 15:07:02 +00:00
|
|
|
u->strdata = addstrings(u->strdata, strlen(u->strdata), char_buf, charlen);
|
2017-12-29 18:27:33 +00:00
|
|
|
u->mark_begin_x = openfile->current_x;
|
2019-06-09 15:07:02 +00:00
|
|
|
} else if (openfile->current_x == u->begin - charlen) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* They backspaced further: add removed character before earlier. */
|
2019-06-09 15:07:02 +00:00
|
|
|
u->strdata = addstrings(char_buf, charlen, u->strdata, strlen(u->strdata));
|
2017-12-29 18:27:33 +00:00
|
|
|
u->begin = openfile->current_x;
|
|
|
|
} else {
|
|
|
|
/* They deleted *elsewhere* on the line: start a new undo item. */
|
|
|
|
free(char_buf);
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(u->type, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
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;
|
2019-10-11 15:55:46 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2018-08-16 13:59:30 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_END:
|
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-12-22 10:28:56 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-12-19 09:52:06 +00:00
|
|
|
size_t quot_len = quote_length(line->data);
|
|
|
|
/* The length of the quoting part of this line. */
|
|
|
|
size_t lead_len = quot_len + indent_length(line->data + quot_len);
|
|
|
|
/* The length of the quoting part plus subsequent whitespace. */
|
2019-12-22 10:28:56 +00:00
|
|
|
#else
|
|
|
|
size_t lead_len = indent_length(line->data);
|
|
|
|
#endif
|
2019-04-19 08:10:14 +00:00
|
|
|
size_t cursor_x = openfile->current_x;
|
|
|
|
/* The current cursor position, for comparison with the wrap point. */
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t wrap_loc;
|
2019-04-20 12:00:07 +00:00
|
|
|
/* The position in the line's text where we wrap. */
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *remainder;
|
|
|
|
/* The text after the wrap point. */
|
|
|
|
size_t rest_length;
|
|
|
|
/* The length of the remainder. */
|
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* First find the last blank character where we can break the line. */
|
2019-12-19 09:52:06 +00:00
|
|
|
wrap_loc = break_line(line->data + lead_len,
|
|
|
|
wrap_at - wideness(line->data, lead_len), FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* If no wrapping point was found before end-of-line, we don't wrap. */
|
2019-12-19 10:06:02 +00:00
|
|
|
if (wrap_loc < 0 || lead_len + wrap_loc == line_len)
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2019-12-19 10:06:02 +00:00
|
|
|
/* Adjust the wrap location to its position in the full line,
|
|
|
|
* and step forward to the character just after the blank. */
|
|
|
|
wrap_loc = lead_len + step_right(line->data + lead_len, wrap_loc);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-04-20 12:00:07 +00:00
|
|
|
/* When now at end-of-line, no need to wrap. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line->data[wrap_loc] == '\0')
|
|
|
|
return FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(SPLIT_BEGIN, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
2019-04-26 08:37:04 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
|
|
|
bool autowhite = ISSET(AUTOINDENT);
|
|
|
|
|
2019-12-19 09:52:06 +00:00
|
|
|
if (quot_len > 0)
|
2019-04-26 08:37:04 +00:00
|
|
|
UNSET(AUTOINDENT);
|
|
|
|
#endif
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The remainder is the text that will be wrapped to the next line. */
|
|
|
|
remainder = line->data + wrap_loc;
|
|
|
|
rest_length = line_len - wrap_loc;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-21 09:58:08 +00:00
|
|
|
/* When prepending and the remainder of this line will not make the next
|
|
|
|
* line too long, then join the two lines, so that, after the line wrap,
|
|
|
|
* the remainder will effectively have been prefixed to the next line. */
|
2019-04-21 15:31:29 +00:00
|
|
|
if (openfile->spillage_line && openfile->spillage_line == line->next &&
|
2019-04-24 06:49:18 +00:00
|
|
|
rest_length + breadth(line->next->data) <= wrap_at) {
|
2019-04-21 09:58:08 +00:00
|
|
|
/* Go to the end of this line. */
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = line_len;
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the remainder doesn't end in a blank, add a space. */
|
2019-06-09 17:37:56 +00:00
|
|
|
if (!is_blank_mbchar(remainder + step_left(remainder, rest_length))) {
|
2014-06-20 16:13:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(ADD, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
#endif
|
|
|
|
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-26 08:37:04 +00:00
|
|
|
/* Join the next line to this one. */
|
|
|
|
do_delete();
|
|
|
|
|
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-12-19 10:06:02 +00:00
|
|
|
/* If the leading part of the current line equals the leading part of
|
|
|
|
* what was the next line, then strip this second leading part. */
|
2019-04-26 08:37:04 +00:00
|
|
|
if (strncmp(line->data, line->data + openfile->current_x, lead_len) == 0)
|
|
|
|
for (size_t i = lead_len; i > 0; i--)
|
|
|
|
do_delete();
|
|
|
|
#endif
|
|
|
|
/* Remove any extra blanks. */
|
|
|
|
while (is_blank_mbchar(&line->data[openfile->current_x]))
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Go to the wrap location. */
|
|
|
|
openfile->current_x = wrap_loc;
|
2017-12-02 09:06:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When requested, snip trailing blanks off the wrapped line. */
|
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
2019-06-09 17:37:56 +00:00
|
|
|
size_t tail_x = step_left(line->data, wrap_loc);
|
|
|
|
size_t typed_x = step_left(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();
|
2019-06-09 17:37:56 +00:00
|
|
|
tail_x = step_left(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-26 08:37:04 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
|
|
|
/* If the original line has quoting, copy it to the spillage line. */
|
2019-12-19 09:52:06 +00:00
|
|
|
if (quot_len > 0) {
|
2019-04-26 08:37:04 +00:00
|
|
|
line = line->next;
|
|
|
|
line_len = strlen(line->data);
|
|
|
|
line->data = charealloc(line->data, lead_len + line_len + 1);
|
|
|
|
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(line->data + lead_len, line->data, line_len + 1);
|
2019-04-26 08:37:04 +00:00
|
|
|
strncpy(line->data, line->prev->data, lead_len);
|
|
|
|
|
|
|
|
openfile->current_x += lead_len;
|
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(ENTER);
|
|
|
|
#endif
|
|
|
|
if (autowhite)
|
|
|
|
SET(AUTOINDENT);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-04-21 15:31:29 +00:00
|
|
|
openfile->spillage_line = openfile->current;
|
|
|
|
|
2019-04-19 08:10:14 +00:00
|
|
|
if (cursor_x < wrap_loc) {
|
2019-04-19 08:06:57 +00:00
|
|
|
openfile->current = openfile->current->prev;
|
2019-04-19 08:10:14 +00:00
|
|
|
openfile->current_x = cursor_x;
|
2019-04-21 15:31:29 +00:00
|
|
|
} else
|
2019-04-19 08:10:14 +00:00
|
|
|
openfile->current_x += (cursor_x - wrap_loc);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->placewewant = xplustabs();
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(SPLIT_END, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
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. */
|
2019-06-09 15:07:02 +00:00
|
|
|
int charlen = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* 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)) {
|
2019-12-19 15:16:09 +00:00
|
|
|
if (is_blank_mbchar(line))
|
2017-12-29 18:27:33 +00:00
|
|
|
lastblank = index;
|
2019-12-19 15:16:09 +00:00
|
|
|
#ifdef ENABLE_HELP
|
|
|
|
else if (snap_at_nl && *line == '\n') {
|
|
|
|
lastblank = index;
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-12-19 15:16:09 +00:00
|
|
|
#endif
|
2019-10-21 10:21:46 +00:00
|
|
|
charlen = advance_over(line, &column);
|
2019-06-09 15:07:02 +00:00
|
|
|
line += charlen;
|
|
|
|
index += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the whole line displays shorter than goal, we're done. */
|
2018-05-27 08:24:02 +00:00
|
|
|
if ((ssize_t)column <= goal)
|
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)
|
2019-06-09 15:07:02 +00:00
|
|
|
return (index - charlen);
|
2017-12-29 18:27:33 +00:00
|
|
|
#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;
|
|
|
|
|
2019-06-09 15:36:29 +00:00
|
|
|
charlen = char_length(line);
|
2019-06-09 15:07:02 +00:00
|
|
|
line += charlen;
|
|
|
|
index += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
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;
|
2019-06-09 15:36:29 +00:00
|
|
|
charlen = char_length(line);
|
2019-06-09 15:07:02 +00:00
|
|
|
line += charlen;
|
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)) {
|
2019-06-09 15:07:02 +00:00
|
|
|
lastblank += charlen;
|
2019-06-09 15:36:29 +00:00
|
|
|
charlen = char_length(line);
|
2019-06-09 15:07:02 +00:00
|
|
|
line += charlen;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2019-12-22 11:56:18 +00:00
|
|
|
#if !defined(NANO_TINY) || defined(ENABLED_WRAPORJUSTIFY)
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Return the length of the indentation part of the given line. The
|
|
|
|
* "indentation" of a line is the leading consecutive whitespace. */
|
2005-07-25 02:33:45 +00:00
|
|
|
size_t indent_length(const char *line)
|
|
|
|
{
|
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') {
|
2019-10-21 10:35:14 +00:00
|
|
|
charlen = collect_char(line, onechar);
|
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
|
|
|
}
|
2019-12-22 11:56:18 +00:00
|
|
|
#endif
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:34:07 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2019-06-08 12:17:41 +00:00
|
|
|
/* Copy a character from one place to another. */
|
|
|
|
void copy_character(char **from, char **to)
|
|
|
|
{
|
2019-06-09 15:36:29 +00:00
|
|
|
int charlen = char_length(*from);
|
2019-06-08 12:17:41 +00:00
|
|
|
|
2019-06-08 14:12:14 +00:00
|
|
|
if (*from == *to) {
|
|
|
|
*from += charlen;
|
|
|
|
*to += charlen;
|
|
|
|
} else
|
|
|
|
while (--charlen >= 0)
|
|
|
|
*((*to)++) = *((*from)++);
|
2019-06-08 12:17:41 +00:00
|
|
|
}
|
|
|
|
|
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-06-08 14:12:14 +00:00
|
|
|
char *start = line->data + skip;
|
|
|
|
char *from = start, *to = start;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* For each character, 1) when a blank, change it to a space, and pass over
|
|
|
|
* all blanks after it; 2) if it is punctuation, copy it plus a possible
|
|
|
|
* tailing bracket, and change at most two subsequent blanks to spaces, and
|
|
|
|
* pass over all blanks after these; 3) leave anything else unchanged. */
|
2019-04-04 11:48:34 +00:00
|
|
|
while (*from != '\0') {
|
|
|
|
if (is_blank_mbchar(from)) {
|
2019-06-09 15:36:29 +00:00
|
|
|
from += char_length(from);
|
2019-04-04 12:11:52 +00:00
|
|
|
*(to++) = ' ';
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 11:34:15 +00:00
|
|
|
while (*from != '\0' && is_blank_mbchar(from))
|
2019-06-09 15:36:29 +00:00
|
|
|
from += char_length(from);
|
2019-04-04 11:48:34 +00:00
|
|
|
} else if (mbstrchr(punct, from) != NULL) {
|
2019-06-08 12:17:41 +00:00
|
|
|
copy_character(&from, &to);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 12:17:41 +00:00
|
|
|
if (*from != '\0' && mbstrchr(brackets, from) != NULL)
|
|
|
|
copy_character(&from, &to);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-04-04 11:48:34 +00:00
|
|
|
if (*from != '\0' && is_blank_mbchar(from)) {
|
2019-06-09 15:36:29 +00:00
|
|
|
from += char_length(from);
|
2019-04-04 12:11:52 +00:00
|
|
|
*(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-06-09 15:36:29 +00:00
|
|
|
from += char_length(from);
|
2019-04-04 12:11:52 +00:00
|
|
|
*(to++) = ' ';
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 11:34:15 +00:00
|
|
|
while (*from != '\0' && is_blank_mbchar(from))
|
2019-06-09 15:36:29 +00:00
|
|
|
from += char_length(from);
|
2019-06-08 12:17:41 +00:00
|
|
|
} else
|
|
|
|
copy_character(&from, &to);
|
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-06-08 14:12:14 +00:00
|
|
|
while (to > start && *(to - 1) == ' ')
|
2019-04-04 11:48:34 +00:00
|
|
|
to--;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-08 11:34:15 +00:00
|
|
|
*to = '\0';
|
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
|
|
|
|
2019-12-17 15:15:51 +00:00
|
|
|
/* If indentation of this and preceding line are equal, this is not a BOP. */
|
|
|
|
if (wideness(line->prev->data, quote_len + prev_dent_len) ==
|
|
|
|
wideness(line->data, quote_len + indent_len))
|
2018-05-21 08:19:40 +00:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* Otherwise, this is a BOP if the preceding line is not. */
|
2018-05-26 10:27:48 +00:00
|
|
|
return !begpar(line->prev, depth + 1);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-19 12:49:35 +00:00
|
|
|
/* Return TRUE when the given line is part of a paragraph: when it
|
|
|
|
* contains something more than quoting and leading whitespace. */
|
2019-03-21 16:08:52 +00:00
|
|
|
bool inpar(const linestruct *const line)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
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
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Find the first occurring paragraph in the forward direction. Return TRUE
|
|
|
|
* when a paragraph was found, and FALSE otherwise. Furthermore, return the
|
|
|
|
* first line and the length (number of lines) 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;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-25 12:04:35 +00:00
|
|
|
/* When not currently in a paragraph, move forward to a line that is. */
|
|
|
|
while (!inpar(line) && line->next != NULL)
|
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-11-22 19:54:22 +00:00
|
|
|
*firstline = line;
|
2018-09-11 06:55:00 +00:00
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Move down to the last line of the paragraph (if any). */
|
2018-11-25 18:24:08 +00:00
|
|
|
do_para_end(&line);
|
2018-11-25 12:04:35 +00:00
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* When not in a paragraph now, there aren't any paragraphs left. */
|
|
|
|
if (!inpar(line))
|
2018-09-11 06:55:00 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* We found a paragraph; determine its number of lines. */
|
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
|
|
|
}
|
|
|
|
|
2019-06-08 11:58:28 +00:00
|
|
|
/* Concatenate into a single line all the lines of the paragraph that starts at
|
|
|
|
* *line and consists of par_len lines, 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. */
|
2019-12-19 10:06:02 +00:00
|
|
|
if (break_pos < 0 || lead_len + break_pos == line_len)
|
2018-08-25 22:53:17 +00:00
|
|
|
break;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* Adjust the breaking position for the leading part and
|
|
|
|
* move it beyond the found whitespace character. */
|
|
|
|
break_pos += lead_len + 1;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Insert a new line after the current one, and copy the leading part
|
|
|
|
* plus the text after the breaking point into it. */
|
2018-12-26 13:56:29 +00:00
|
|
|
splice_node(*line, make_new_node(*line));
|
|
|
|
(*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
|
|
|
|
2019-12-19 11:07:36 +00:00
|
|
|
/* When requested, snip the one or two trailing spaces. */
|
2018-08-25 22:53:17 +00:00
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
2019-12-19 11:07:36 +00:00
|
|
|
while (break_pos > 0 && (*line)->data[break_pos - 1] == ' ')
|
2018-08-25 22:53:17 +00:00
|
|
|
break_pos--;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-12-30 17:00:28 +00:00
|
|
|
/* Now actually break the current line, and go to the next. */
|
2019-04-04 12:44:44 +00:00
|
|
|
(*line)->data[break_pos] = '\0';
|
2018-12-26 13:56:29 +00:00
|
|
|
*line = (*line)->next;
|
2018-08-25 22:53:17 +00:00
|
|
|
}
|
2017-11-22 18:26:47 +00:00
|
|
|
|
2018-12-26 14:03:37 +00:00
|
|
|
/* When possible, go to the line after the rewrapped paragraph. */
|
2018-12-26 13:37:36 +00:00
|
|
|
if ((*line)->next != NULL)
|
|
|
|
*line = (*line)->next;
|
2018-08-25 22:44:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-19 18:56:58 +00:00
|
|
|
/* Justify the lines of the given paragraph (that starts at *line, and consists
|
|
|
|
* 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);
|
2019-10-14 17:51:24 +00:00
|
|
|
lead_string = measured_copy(sampleline->data, lead_len + 1);
|
2018-12-26 19:52:06 +00:00
|
|
|
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);
|
2019-10-14 17:51:24 +00:00
|
|
|
the_lead = measured_copy(first_par_line->data, lead_len + 1);
|
2019-01-08 23:48:40 +00:00
|
|
|
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
|
2019-10-26 15:05:03 +00:00
|
|
|
add_undo(COUPLE_BEGIN, N_("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;
|
|
|
|
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(CUT, NULL);
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
2019-04-11 13:12:04 +00:00
|
|
|
|
|
|
|
/* Do the equivalent of a marked cut into an empty cutbuffer. */
|
|
|
|
cutbuffer = NULL;
|
2020-01-08 15:13:04 +00:00
|
|
|
extract_segment(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);
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(cutbuffer->data + needed_top_extra, cutbuffer->data,
|
2019-01-08 21:58:08 +00:00
|
|
|
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)
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(cutbuffer->data + lead_len,
|
2019-01-08 22:42:04 +00:00
|
|
|
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)
|
2019-09-18 13:20:08 +00:00
|
|
|
memmove(cutbuffer->data, cutbuffer->data + needed_top_extra,
|
2019-01-08 21:58:08 +00:00
|
|
|
strlen(cutbuffer->data) - needed_top_extra + 1);
|
|
|
|
else {
|
|
|
|
cutbuffer->prev = make_new_node(NULL);
|
2019-10-13 10:24:27 +00:00
|
|
|
cutbuffer->prev->data = copy_of("");
|
2019-01-08 21:58:08 +00:00
|
|
|
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);
|
2019-10-13 10:24:27 +00:00
|
|
|
line->next->data = copy_of(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
|
2019-10-09 17:12:07 +00:00
|
|
|
add_undo(PASTE, NULL);
|
2018-09-07 17:01:43 +00:00
|
|
|
#endif
|
2018-11-26 08:25:32 +00:00
|
|
|
/* Do the equivalent of a paste of the justified text. */
|
2018-09-01 00:45:27 +00:00
|
|
|
ingraft_buffer(cutbuffer);
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(PASTE);
|
|
|
|
|
2019-10-27 15:26:03 +00:00
|
|
|
add_undo(COUPLE_END, "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) {
|
2019-05-24 16:56:34 +00:00
|
|
|
openfile->mark = line_from_number(was_top_lineno);
|
2018-09-10 22:34:33 +00:00
|
|
|
openfile->mark_x = was_top_x;
|
|
|
|
} else {
|
2019-05-24 16:56:34 +00:00
|
|
|
openfile->current = line_from_number(was_top_lineno);
|
2018-09-10 22:34:33 +00:00
|
|
|
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)
|
|
|
|
{
|
2019-10-13 10:24:27 +00:00
|
|
|
char *copy_of_command = copy_of(command);
|
2018-04-22 10:00:26 +00:00
|
|
|
char *element = strtok(copy_of_command, " ");
|
2018-04-22 10:05:19 +00:00
|
|
|
int count = 2;
|
2018-04-22 08:29:06 +00:00
|
|
|
|
2018-04-22 10:00:26 +00:00
|
|
|
while (element != NULL) {
|
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;
|
2019-10-13 10:24:27 +00:00
|
|
|
last_search = copy_of(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
|
|
|
}
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Run a spell-check on the given file, using 'spell' to produce a list of all
|
|
|
|
* misspelled words, then feeding those through 'sort' and 'uniq' to obtain an
|
|
|
|
* alphabetical list, which words are then offered one by one to the user for
|
|
|
|
* correction. Return NULL when okay, 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
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run spell in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_spell = fork()) == 0) {
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Child: open the temporary file that holds the text to be checked. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(6);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard input to the temporary file. */
|
2019-10-27 09:25:37 +00:00
|
|
|
if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO)
|
|
|
|
exit(7);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard output to the write end of the first pipe. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(tempfile_fd);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(spell_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-11-25 15:57:04 +00:00
|
|
|
/* Try to run 'hunspell'; if that fails, fall back to 'spell'. */
|
2019-11-24 09:11:32 +00:00
|
|
|
execlp("hunspell", "hunspell", "-l", NULL);
|
2019-11-25 15:57:04 +00:00
|
|
|
execlp("spell", "spell", NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-11-24 09:11:32 +00:00
|
|
|
/* Indicate failure when neither speller was found. */
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Parent: close the unused write end of the first pipe. */
|
2005-07-25 02:33:45 +00:00
|
|
|
close(spell_fd[1]);
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run sort in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_sort = fork()) == 0) {
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard input to the read end of the first pipe. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(7);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Connect standard output to the write end of the second pipe. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(spell_fd[0]);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(sort_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[1]);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Now run the sort program. Use -f to mix upper and lower case. */
|
2017-12-29 18:27:33 +00:00
|
|
|
execlp("sort", "sort", "-f", NULL);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(sort_fd[1]);
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Fork a process to run uniq in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_uniq = fork()) == 0) {
|
|
|
|
if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(7);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
2019-10-27 09:25:37 +00:00
|
|
|
exit(8);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:40:09 +00:00
|
|
|
close(sort_fd[0]);
|
2019-10-27 09:34:12 +00:00
|
|
|
close(uniq_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
execlp("uniq", "uniq", NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
exit(9);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* When some child process was not forked successfully... */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
|
|
|
|
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-10-27 09:10:03 +00:00
|
|
|
/* Block SIGWINCHes while reading misspelled words from the third pipe. */
|
2019-03-30 18:31:42 +00:00
|
|
|
block_sigwinch(TRUE);
|
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
totalread = 0;
|
2019-04-11 07:14:30 +00:00
|
|
|
buffersize = pipesize + 1;
|
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);
|
|
|
|
|
2019-10-27 09:10:03 +00:00
|
|
|
/* Do any replacements case-sensitively, forward, and without regexes. */
|
2018-04-25 10:27:01 +00:00
|
|
|
SET(CASE_SENSITIVE);
|
|
|
|
UNSET(BACKWARDS_SEARCH);
|
|
|
|
UNSET(USE_REGEXP);
|
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer = misspellings;
|
|
|
|
oneword = misspellings;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-04-25 10:27:01 +00:00
|
|
|
/* Process each of the misspelled words. */
|
2019-03-30 18:08:17 +00:00
|
|
|
while (*pointer != '\0') {
|
|
|
|
if ((*pointer == '\r') || (*pointer == '\n')) {
|
|
|
|
*pointer = '\0';
|
|
|
|
if (oneword != pointer) {
|
|
|
|
if (!fix_spello(oneword)) {
|
|
|
|
oneword = pointer;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-03-30 18:08:17 +00:00
|
|
|
oneword = pointer + 1;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2019-03-30 18:08:17 +00:00
|
|
|
pointer++;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Special case: the last word doesn't end with '\r' or '\n'. */
|
2019-03-30 18:08:17 +00:00
|
|
|
if (oneword != pointer)
|
|
|
|
fix_spello(oneword);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-03-30 18:08:17 +00:00
|
|
|
free(misspellings);
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Process the end of the three processes. */
|
|
|
|
waitpid(pid_spell, &spell_status, 0);
|
|
|
|
waitpid(pid_sort, &sort_status, 0);
|
|
|
|
waitpid(pid_uniq, &uniq_status, 0);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-06-05 13:04:18 +00:00
|
|
|
if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
|
|
|
|
return _("Error invoking \"uniq\"");
|
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
|
|
|
|
2019-06-05 13:04:18 +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
|
|
|
/* When all went okay. */
|
2019-10-10 07:17:32 +00:00
|
|
|
statusbar(_("Finished checking spelling"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return NULL;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-09 13:24:21 +00:00
|
|
|
/* Execute the given program, with the given temp file as last argument. */
|
2019-10-09 17:42:22 +00:00
|
|
|
const char *treat(char *tempfile_name, char *theprogram, bool spelling)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2019-10-09 13:10:57 +00:00
|
|
|
ssize_t lineno_save = openfile->current->lineno;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t current_x_save = openfile->current_x;
|
|
|
|
size_t pww_save = openfile->placewewant;
|
|
|
|
bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
|
2019-10-09 13:24:21 +00:00
|
|
|
struct stat fileinfo;
|
2019-10-09 17:42:22 +00:00
|
|
|
long timestamp_sec, timestamp_nsec;
|
2019-10-09 13:24:21 +00:00
|
|
|
static char **arguments = NULL;
|
|
|
|
pid_t thepid;
|
2019-10-09 13:10:57 +00:00
|
|
|
int program_status;
|
2019-10-10 07:17:32 +00:00
|
|
|
bool replaced = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Get the timestamp and the size of the temporary file. */
|
2019-10-09 13:24:21 +00:00
|
|
|
stat(tempfile_name, &fileinfo);
|
2019-10-09 17:42:22 +00:00
|
|
|
timestamp_sec = (long)fileinfo.st_mtim.tv_sec;
|
|
|
|
timestamp_nsec = (long)fileinfo.st_mtim.tv_nsec;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If the number of bytes to check is zero, get out. */
|
2019-10-09 13:24:21 +00:00
|
|
|
if (fileinfo.st_size == 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return NULL;
|
|
|
|
|
2019-10-25 17:02:07 +00:00
|
|
|
/* Exit from curses mode to give the program control of the terminal. */
|
|
|
|
endwin();
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-10-09 13:24:21 +00:00
|
|
|
construct_argument_list(&arguments, theprogram, tempfile_name);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-09 13:24:21 +00:00
|
|
|
/* Fork a child process and run the given program in it. */
|
|
|
|
if ((thepid = fork()) == 0) {
|
|
|
|
execvp(arguments[0], arguments);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-09 13:24:21 +00:00
|
|
|
/* Terminate the child if the program is not found. */
|
2019-10-25 15:29:56 +00:00
|
|
|
exit(9);
|
2019-10-09 13:24:21 +00:00
|
|
|
} else if (thepid < 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return _("Could not fork");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-09 13:24:21 +00:00
|
|
|
/* Block SIGWINCHes while waiting for the program to end,
|
2019-03-27 18:06:23 +00:00
|
|
|
* so nano doesn't get pushed past the wait(). */
|
2019-03-27 18:44:50 +00:00
|
|
|
block_sigwinch(TRUE);
|
2019-10-09 13:10:57 +00:00
|
|
|
wait(&program_status);
|
2019-03-27 18:44:50 +00:00
|
|
|
block_sigwinch(FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-25 17:02:07 +00:00
|
|
|
/* Restore the terminal state and reenter curses mode. */
|
|
|
|
terminal_init();
|
|
|
|
doupdate();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-26 10:01:23 +00:00
|
|
|
if (!WIFEXITED(program_status) || WEXITSTATUS(program_status) > 2) {
|
|
|
|
statusline(ALERT, _("Error invoking '%s'"), arguments[0]);
|
|
|
|
return NULL;
|
|
|
|
} else if (WEXITSTATUS(program_status) != 0)
|
2019-10-26 10:04:10 +00:00
|
|
|
statusline(ALERT, _("Program '%s' complained"), arguments[0]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Stat the temporary file again. */
|
2019-10-09 13:24:21 +00:00
|
|
|
stat(tempfile_name, &fileinfo);
|
2018-08-08 17:53:05 +00:00
|
|
|
|
2019-10-10 07:17:32 +00:00
|
|
|
/* When the temporary file wasn't touched, say so and leave. */
|
|
|
|
if ((long)fileinfo.st_mtim.tv_sec == timestamp_sec &&
|
|
|
|
(long)fileinfo.st_mtim.tv_nsec == timestamp_nsec) {
|
|
|
|
statusbar(_("Nothing changed"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2019-10-10 12:05:49 +00:00
|
|
|
/* Replace the marked text (or entire text) with the corrected text. */
|
2019-10-10 12:10:19 +00:00
|
|
|
if (spelling && openfile->mark) {
|
2019-10-10 12:05:49 +00:00
|
|
|
bool upright = (openfile->mark->lineno < openfile->current->lineno ||
|
|
|
|
(openfile->mark == openfile->current &&
|
|
|
|
openfile->mark_x < openfile->current_x));
|
|
|
|
ssize_t was_mark_lineno = openfile->mark->lineno;
|
|
|
|
|
2019-10-10 12:10:19 +00:00
|
|
|
replaced = replace_buffer(tempfile_name, CUT, TRUE, "spelling correction");
|
2019-10-10 12:05:49 +00:00
|
|
|
|
|
|
|
/* Adjust the end point of the marked region for any change in
|
|
|
|
* length of the region's last line. */
|
|
|
|
if (upright)
|
|
|
|
current_x_save = openfile->current_x;
|
|
|
|
else
|
|
|
|
openfile->mark_x = openfile->current_x;
|
|
|
|
|
|
|
|
/* Restore the mark. */
|
|
|
|
openfile->mark = line_from_number(was_mark_lineno);
|
|
|
|
} else
|
2017-02-10 01:04:14 +00:00
|
|
|
#endif
|
2019-10-10 12:10:19 +00:00
|
|
|
replaced = replace_buffer(tempfile_name, CUT_TO_EOF, FALSE,
|
2019-10-27 15:26:03 +00:00
|
|
|
/* TRANSLATORS: The next two go with Undid/Redid messages. */
|
2019-10-25 17:21:17 +00:00
|
|
|
(spelling ? N_("spelling correction") : N_("formatting")));
|
2018-08-08 17:53:05 +00:00
|
|
|
|
2019-10-10 12:05:49 +00:00
|
|
|
/* 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
|
2019-10-10 12:05:49 +00:00
|
|
|
if (replaced)
|
|
|
|
update_undo(COUPLE_END);
|
2018-08-16 13:59:30 +00:00
|
|
|
#endif
|
2019-10-10 12:05:49 +00:00
|
|
|
openfile->placewewant = pww_save;
|
|
|
|
adjust_viewport(STATIONARY);
|
2019-10-10 07:17:32 +00:00
|
|
|
|
|
|
|
if (spelling)
|
|
|
|
statusbar(_("Finished checking spelling"));
|
|
|
|
else
|
|
|
|
statusbar(_("Buffer has been processed"));
|
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
|
|
|
{
|
2019-10-16 09:46:22 +00:00
|
|
|
FILE *stream;
|
|
|
|
char *temp_name;
|
2018-04-25 09:33:22 +00:00
|
|
|
unsigned stash[sizeof(flags) / sizeof(flags[0])];
|
2019-04-23 09:48:10 +00:00
|
|
|
const char *result_msg;
|
2019-10-16 09:46:22 +00:00
|
|
|
bool okay;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2019-10-09 16:58:30 +00:00
|
|
|
if (in_restricted_mode())
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
2010-11-12 06:22:12 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
temp_name = safe_tempfile(&stream);
|
2016-01-02 16:01:04 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
if (temp_name == NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Save the settings of the global flags. */
|
|
|
|
memcpy(stash, flags, sizeof(flags));
|
|
|
|
|
|
|
|
/* Don't add an extra newline when writing out the (selected) text. */
|
2019-04-07 06:47:29 +00:00
|
|
|
SET(NO_NEWLINES);
|
2018-04-25 09:33:22 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark)
|
2019-10-16 09:46:22 +00:00
|
|
|
okay = write_marked_file(temp_name, stream, TRUE, OVERWRITE);
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2005-07-25 02:33:45 +00:00
|
|
|
#endif
|
2019-10-16 09:46:22 +00:00
|
|
|
okay = write_file(temp_name, stream, TRUE, OVERWRITE, TRUE);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
if (!okay) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
2019-10-16 09:46:22 +00:00
|
|
|
free(temp_name);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-05-12 11:50:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
blank_bottombars();
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2019-10-09 17:42:22 +00:00
|
|
|
if (alt_speller)
|
2019-10-16 09:46:22 +00:00
|
|
|
result_msg = treat(temp_name, alt_speller, TRUE);
|
2019-10-09 17:42:22 +00:00
|
|
|
else
|
2019-10-16 09:46:22 +00:00
|
|
|
result_msg = do_int_speller(temp_name);
|
2018-09-23 12:51:40 +00:00
|
|
|
|
2019-10-16 09:46:22 +00:00
|
|
|
unlink(temp_name);
|
|
|
|
free(temp_name);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Restore the settings of the global flags. */
|
|
|
|
memcpy(flags, stash, sizeof(flags));
|
|
|
|
|
2019-05-24 10:23:42 +00:00
|
|
|
/* Ensure the help lines will be redrawn and a selection is retained. */
|
2019-05-21 17:08:14 +00:00
|
|
|
currmenu = MMOST;
|
2019-05-24 10:23:42 +00:00
|
|
|
shift_held = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-09-23 12:51:40 +00:00
|
|
|
if (result_msg != NULL) {
|
2019-06-05 12:57:24 +00:00
|
|
|
/* Avoid giving a failure reason of "Success". */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (errno == 0)
|
2019-06-05 12:57:24 +00:00
|
|
|
statusline(ALERT, result_msg);
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2019-06-05 12:57:24 +00:00
|
|
|
statusline(ALERT, _("%s: %s"), result_msg, strerror(errno));
|
2019-10-10 07:17:32 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 18:32:42 +00:00
|
|
|
#endif /* ENABLE_SPELLER */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2019-10-26 09:18:38 +00:00
|
|
|
/* Run a linting program on the current buffer. */
|
2014-02-24 10:18:15 +00:00
|
|
|
void do_linter(void)
|
|
|
|
{
|
2019-04-11 07:36:49 +00:00
|
|
|
char *lintings, *pointer, *onelint;
|
2019-04-11 07:14:30 +00:00
|
|
|
long pipesize;
|
2019-04-11 07:36:49 +00:00
|
|
|
size_t buffersize, bytesread, totalread;
|
2019-10-26 09:18:38 +00:00
|
|
|
bool parsesuccess = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
int lint_status, lint_fd[2];
|
|
|
|
pid_t pid_lint;
|
2018-10-21 18:47:37 +00:00
|
|
|
bool helpless = ISSET(NO_HELP);
|
2017-12-29 18:27:33 +00:00
|
|
|
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
|
|
|
|
2019-10-09 16:58:30 +00:00
|
|
|
if (in_restricted_mode())
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->linter) {
|
2019-10-27 15:19:45 +00:00
|
|
|
statusbar(_("No linter is defined for this type of file"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2018-01-09 11:23:41 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
2018-01-09 11:23:41 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
|
|
|
|
|
|
|
if (openfile->modified) {
|
2019-02-10 14:40:08 +00:00
|
|
|
int choice = do_yesno_prompt(FALSE, _("Save modified buffer before linting?"));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-02-10 14:40:08 +00:00
|
|
|
if (choice == -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Cancelled"));
|
|
|
|
return;
|
2019-02-10 14:40:08 +00:00
|
|
|
} else if (choice == 1 && (do_writeout(FALSE, FALSE) != 1))
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a pipe up front. */
|
|
|
|
if (pipe(lint_fd) == -1) {
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("Could not create pipe"));
|
2017-12-29 18:27:33 +00:00
|
|
|
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
|
|
|
|
2019-11-17 07:15:02 +00:00
|
|
|
/* Fork a process to run the linter in. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if ((pid_lint = fork()) == 0) {
|
2019-11-17 07:15:02 +00:00
|
|
|
/* Redirect standard output and standard error into the pipe. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(lint_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
2019-11-17 07:15:02 +00:00
|
|
|
exit(7);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (dup2(lint_fd[1], STDERR_FILENO) != STDERR_FILENO)
|
2019-11-17 07:15:02 +00:00
|
|
|
exit(8);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2019-11-17 07:15:02 +00:00
|
|
|
close(lint_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
close(lint_fd[1]);
|
|
|
|
|
|
|
|
/* 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]);
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("Could not fork"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the system pipe buffer size. */
|
2019-04-11 07:14:30 +00:00
|
|
|
pipesize = fpathconf(lint_fd[0], _PC_PIPE_BUF);
|
2019-04-11 06:51:15 +00:00
|
|
|
|
2019-04-11 07:14:30 +00:00
|
|
|
if (pipesize < 1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
close(lint_fd[0]);
|
2019-05-28 15:36:32 +00:00
|
|
|
statusline(ALERT, _("Could not get size of pipe buffer"));
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* 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-10-13 10:24:27 +00:00
|
|
|
char *message = copy_of(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. */
|
2019-10-26 09:18:38 +00:00
|
|
|
parsesuccess = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = curlint;
|
|
|
|
curlint = nmalloc(sizeof(lintstruct));
|
|
|
|
curlint->next = NULL;
|
|
|
|
curlint->prev = tmplint;
|
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint->prev->next = curlint;
|
2019-10-13 10:24:27 +00:00
|
|
|
curlint->msg = copy_of(message);
|
2017-12-29 18:27:33 +00:00
|
|
|
curlint->lineno = tmplineno;
|
|
|
|
curlint->colno = tmpcolno;
|
2019-10-13 10:24:27 +00:00
|
|
|
curlint->filename = copy_of(filename);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
if (lints == NULL)
|
|
|
|
lints = curlint;
|
|
|
|
}
|
|
|
|
}
|
2019-04-11 12:17:32 +00:00
|
|
|
}
|
|
|
|
free(message);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2019-04-11 07:36:49 +00:00
|
|
|
onelint = pointer + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2019-04-11 07:36:49 +00:00
|
|
|
pointer++;
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
|
|
|
|
2019-04-11 07:55:18 +00:00
|
|
|
free(lintings);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Process the end of the linting process. */
|
|
|
|
waitpid(pid_lint, &lint_status, 0);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!WIFEXITED(lint_status) || WEXITSTATUS(lint_status) > 2) {
|
2019-10-26 09:52:53 +00:00
|
|
|
statusline(ALERT, _("Error invoking '%s'"), openfile->syntax->linter);
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2019-10-26 09:18:38 +00:00
|
|
|
if (!parsesuccess) {
|
2017-12-29 18:27:33 +00:00
|
|
|
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
|
2019-10-13 10:24:27 +00:00
|
|
|
char *dontwantfile = copy_of(curlint->filename);
|
2018-03-25 19:36:13 +00:00
|
|
|
lintstruct *restlint = NULL;
|
|
|
|
|
|
|
|
while (curlint != NULL) {
|
|
|
|
if (strcmp(curlint->filename, dontwantfile) == 0) {
|
|
|
|
if (curlint == lints)
|
|
|
|
lints = curlint->next;
|
|
|
|
else
|
|
|
|
curlint->prev->next = curlint->next;
|
|
|
|
if (curlint->next != NULL)
|
|
|
|
curlint->next->prev = curlint->prev;
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
free(tmplint->msg);
|
|
|
|
free(tmplint->filename);
|
|
|
|
free(tmplint);
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL)
|
|
|
|
restlint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
}
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-07-14 19:02:32 +00:00
|
|
|
free(dontwantfile);
|
|
|
|
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL) {
|
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;
|
2019-12-15 18:47:05 +00:00
|
|
|
} else if (func == do_help) {
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
2019-12-15 18:47:05 +00:00
|
|
|
do_help();
|
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"));
|
2019-12-15 14:34:21 +00:00
|
|
|
beep();
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
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"));
|
2019-12-15 14:34:21 +00:00
|
|
|
beep();
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
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
|
|
|
}
|
2019-10-09 17:42:22 +00:00
|
|
|
|
|
|
|
#ifdef ENABLE_SPELLER
|
|
|
|
/* Run a manipulation program on the contents of the buffer. */
|
2019-10-25 15:17:48 +00:00
|
|
|
void do_formatter(void)
|
2019-10-09 17:42:22 +00:00
|
|
|
{
|
|
|
|
FILE *stream;
|
|
|
|
char *temp_name;
|
|
|
|
bool okay = FALSE;
|
|
|
|
const char *result_msg;
|
|
|
|
|
|
|
|
if (in_restricted_mode())
|
|
|
|
return;
|
|
|
|
|
2019-10-25 15:17:48 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->formatter) {
|
|
|
|
statusbar(_("No formatter is defined for this type of file"));
|
2019-10-09 17:42:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp_name = safe_tempfile(&stream);
|
|
|
|
|
|
|
|
if (temp_name != NULL)
|
|
|
|
okay = write_file(temp_name, stream, TRUE, OVERWRITE, TRUE);
|
|
|
|
|
|
|
|
if (!okay) {
|
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
free(temp_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-25 15:17:48 +00:00
|
|
|
result_msg = treat(temp_name, openfile->syntax->formatter, FALSE);
|
2019-10-09 17:42:22 +00:00
|
|
|
|
|
|
|
if (result_msg != NULL)
|
|
|
|
statusline(ALERT, result_msg);
|
|
|
|
|
|
|
|
unlink(temp_name);
|
|
|
|
free(temp_name);
|
|
|
|
}
|
|
|
|
#endif /* ENABLE_SPELLER */
|
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
|
|
|
{
|
2019-05-31 15:45:51 +00:00
|
|
|
linestruct *was_current = openfile->current;
|
|
|
|
size_t was_x = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t words = 0, chars = 0;
|
2019-05-30 17:34:57 +00:00
|
|
|
ssize_t lines = 0;
|
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;
|
|
|
|
|
|
|
|
/* If the mark is on, partition the buffer so that it
|
|
|
|
* contains only the marked text, and turn the mark off. */
|
2019-05-31 15:34:22 +00:00
|
|
|
if (openfile->mark) {
|
2019-04-28 09:01:51 +00:00
|
|
|
get_region((const linestruct **)&top, &top_x,
|
|
|
|
(const linestruct **)&bot, &bot_x, NULL);
|
2019-05-31 17:00:20 +00:00
|
|
|
partition_buffer(top, top_x, bot, bot_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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++;
|
|
|
|
}
|
|
|
|
|
2019-05-31 15:22:47 +00:00
|
|
|
/* Get the number of lines, similar to what "wc -l" gives. */
|
|
|
|
lines = openfile->filebot->lineno - openfile->filetop->lineno +
|
|
|
|
((openfile->filebot->data[0] == '\0') ? 0 : 1);
|
|
|
|
|
|
|
|
/* Get the number of multibyte characters, similar to "wc -c". */
|
2019-05-31 15:34:22 +00:00
|
|
|
if (openfile->mark) {
|
2019-03-21 16:23:49 +00:00
|
|
|
chars = get_totsize(openfile->filetop, openfile->filebot);
|
2019-05-31 17:00:20 +00:00
|
|
|
unpartition_buffer();
|
2019-05-31 15:34:22 +00:00
|
|
|
} else
|
2017-12-29 18:27:33 +00:00
|
|
|
chars = openfile->totsize;
|
|
|
|
|
|
|
|
/* Restore where we were. */
|
2019-05-31 15:45:51 +00:00
|
|
|
openfile->current = was_current;
|
|
|
|
openfile->current_x = was_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-01-16 15:16:31 +00:00
|
|
|
/* Display the total word, line, and character counts on the status bar. */
|
2019-05-31 15:34:22 +00:00
|
|
|
statusline(HUSH, _("%sWords: %zu Lines: %zd Chars: %zu"), openfile->mark ?
|
2019-05-30 17:34:57 +00:00
|
|
|
_("In Selection: ") : "", words, lines, 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;
|
2020-01-08 10:00:51 +00:00
|
|
|
size_t count;
|
|
|
|
char *keycodes;
|
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. */
|
2020-01-08 10:00:51 +00:00
|
|
|
kbinput = get_verbatim_kbinput(edit, &count);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-01-16 15:16:31 +00:00
|
|
|
/* Unsuppress cursor-position display or blank the status bar. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(CONSTANT_SHOW))
|
|
|
|
suppress_cursorpos = FALSE;
|
|
|
|
else
|
|
|
|
wipe_statusbar();
|
2006-05-27 17:39:19 +00:00
|
|
|
|
2020-01-08 10:00:51 +00:00
|
|
|
keycodes = charalloc(count + 1);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-01-08 10:00:51 +00:00
|
|
|
for (size_t i = 0; i < count; i++)
|
|
|
|
keycodes[i] = (char)kbinput[i];
|
|
|
|
keycodes[count] = '\0';
|
2006-12-02 17:22:21 +00:00
|
|
|
|
2020-01-08 09:49:19 +00:00
|
|
|
/* Insert the keystroke verbatim, without filtering control characters. */
|
2020-01-08 10:09:57 +00:00
|
|
|
inject(keycodes, count, FALSE);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2020-01-08 10:00:51 +00:00
|
|
|
free(keycodes);
|
|
|
|
free(kbinput);
|
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))
|
2019-06-09 17:37:56 +00:00
|
|
|
length = step_right(text, length);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2019-01-02 16:52:11 +00:00
|
|
|
/* Now copy this candidate to a new string. */
|
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) {
|
2019-06-09 17:37:56 +00:00
|
|
|
size_t oneleft = step_left(openfile->current->data, start_of_shard);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2019-06-09 17:30:29 +00:00
|
|
|
if (!is_word_mbchar(&openfile->current->data[oneleft], FALSE))
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2019-06-09 17:30:29 +00:00
|
|
|
start_of_shard = oneleft;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there is no word fragment before the cursor, do nothing. */
|
|
|
|
if (start_of_shard == openfile->current_x) {
|
2019-09-22 15:30:22 +00:00
|
|
|
/* TRANSLATORS: Shown when no text is directly left of the cursor. */
|
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[
|
2019-06-09 17:37:56 +00:00
|
|
|
step_left(pletion_line->data, i)], FALSE))
|
2017-12-29 18:27:33 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If this match is the shard itself, ignore it. */
|
|
|
|
if (pletion_line == openfile->current &&
|
|
|
|
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. */
|
2020-01-08 10:09:57 +00:00
|
|
|
inject(&completion[shard_length],
|
2020-01-08 09:49:19 +00:00
|
|
|
strlen(completion) - shard_length, TRUE);
|
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 */
|