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
|
|
|
* *
|
2018-01-24 09:13:28 +00:00
|
|
|
* Copyright (C) 1999-2011, 2013-2018 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 *
|
|
|
|
* Copyright (C) 2015-2018 Benno Schulenberg *
|
2016-08-29 13:14:18 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano is free software: you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published *
|
|
|
|
* by the Free Software Foundation, either version 3 of the License, *
|
|
|
|
* or (at your option) any later version. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
2016-08-29 15:10:49 +00:00
|
|
|
* GNU nano is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty *
|
|
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
|
|
* See the GNU General Public License for more details. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
2016-08-29 15:10:49 +00:00
|
|
|
* along with this program. If not, see http://www.gnu.org/licenses/. *
|
2005-07-24 19:57:51 +00:00
|
|
|
* *
|
|
|
|
**************************************************************************/
|
|
|
|
|
2005-12-08 02:47:10 +00:00
|
|
|
#include "proto.h"
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <errno.h>
|
2005-07-24 19:57:51 +00:00
|
|
|
#include <fcntl.h>
|
2017-08-06 11:32:44 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2005-07-24 19:57:51 +00:00
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-07-30 18:07:25 +00:00
|
|
|
static pid_t pid_of_command = -1;
|
|
|
|
/* The PID of the forked process -- needed when wanting to abort it. */
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2005-11-25 13:48:09 +00:00
|
|
|
static bool prepend_wrap = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Should we prepend wrapped text to the next line? */
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
2016-12-07 12:10:40 +00:00
|
|
|
#ifdef ENABLE_WORDCOMPLETION
|
2016-12-07 04:13:47 +00:00
|
|
|
static int pletion_x = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The x position in pletion_line of the last found completion. */
|
2016-12-07 04:13:47 +00:00
|
|
|
static completion_word *list_of_completions;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A linked list of the completions that have been attempted. */
|
2016-12-07 04:13:47 +00:00
|
|
|
#endif
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Toggle the mark. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_mark(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->mark) {
|
|
|
|
openfile->mark = openfile->current;
|
|
|
|
openfile->mark_x = openfile->current_x;
|
|
|
|
statusbar(_("Mark Set"));
|
|
|
|
openfile->kind_of_mark = HARDMARK;
|
|
|
|
} else {
|
|
|
|
openfile->mark = NULL;
|
|
|
|
statusbar(_("Mark Unset"));
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#if defined(ENABLE_COLOR) || defined(ENABLE_SPELLER)
|
2015-04-17 09:24:17 +00:00
|
|
|
/* Return an error message containing the given name. */
|
|
|
|
char *invocation_error(const char *name)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *message, *invoke_error = _("Error invoking \"%s\"");
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
message = charalloc(strlen(invoke_error) + strlen(name) + 1);
|
|
|
|
sprintf(message, invoke_error, name);
|
|
|
|
return message;
|
2015-04-17 09:24:17 +00:00
|
|
|
}
|
2015-06-28 14:12:25 +00:00
|
|
|
#endif
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2006-12-10 17:57:09 +00:00
|
|
|
/* Delete the character under the cursor. */
|
2014-06-08 19:02:12 +00:00
|
|
|
void do_deletion(undo_type action)
|
2005-07-25 02:41:59 +00:00
|
|
|
{
|
2014-06-09 20:26:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t old_amount = 0;
|
2014-06-09 20:26:54 +00:00
|
|
|
#endif
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->placewewant = xplustabs();
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current->data[openfile->current_x] != '\0') {
|
|
|
|
/* We're in the middle of a line: delete the current character. */
|
|
|
|
int char_len = parse_mbchar(openfile->current->data +
|
|
|
|
openfile->current_x, NULL, NULL);
|
|
|
|
size_t line_len = strlen(openfile->current->data +
|
|
|
|
openfile->current_x);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2014-05-29 18:50:13 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-09-25 16:01:57 +00:00
|
|
|
/* If the type of action changed or the cursor moved to a different
|
|
|
|
* line, create a new undo item, otherwise update the existing item. */
|
|
|
|
if (action != openfile->last_action ||
|
|
|
|
openfile->current->lineno != openfile->current_undo->lineno)
|
|
|
|
add_undo(action);
|
|
|
|
else
|
|
|
|
update_undo(action);
|
2014-06-09 20:26:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(SOFTWRAP))
|
|
|
|
old_amount = number_of_chunks_in(openfile->current);
|
2014-06-09 20:26:54 +00:00
|
|
|
#endif
|
2009-12-12 22:21:20 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Move the remainder of the line "in", over the current character. */
|
|
|
|
charmove(&openfile->current->data[openfile->current_x],
|
|
|
|
&openfile->current->data[openfile->current_x + char_len],
|
|
|
|
line_len - char_len + 1);
|
2016-06-06 18:29:53 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the mark if it is after the cursor on the current line. */
|
|
|
|
if (openfile->mark == openfile->current &&
|
|
|
|
openfile->mark_x > openfile->current_x)
|
|
|
|
openfile->mark_x -= char_len;
|
|
|
|
#endif
|
|
|
|
/* Adjust the file size. */
|
|
|
|
openfile->totsize--;
|
|
|
|
} else if (openfile->current != openfile->filebot) {
|
|
|
|
/* We're at the end of a line and not at the end of the file: join
|
|
|
|
* this line with the next. */
|
|
|
|
filestruct *joining = openfile->current->next;
|
|
|
|
|
|
|
|
/* If there is a magic line, and we're before it: don't eat it. */
|
|
|
|
if (joining == openfile->filebot && openfile->current_x != 0 &&
|
|
|
|
!ISSET(NO_NEWLINES)) {
|
2015-07-06 19:08:13 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == BACK)
|
|
|
|
add_undo(BACK);
|
2015-07-06 19:08:13 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-07-06 18:48:15 +00:00
|
|
|
|
2014-05-29 18:50:13 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(action);
|
2014-05-29 18:50:13 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Add the contents of the next line to those of the current one. */
|
|
|
|
openfile->current->data = charealloc(openfile->current->data,
|
|
|
|
strlen(openfile->current->data) + strlen(joining->data) + 1);
|
|
|
|
strcat(openfile->current->data, joining->data);
|
2015-12-08 19:09:14 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the file size. */
|
|
|
|
openfile->totsize--;
|
2016-06-04 09:35:11 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Remember the new file size for a possible redo. */
|
|
|
|
openfile->current_undo->newsize = openfile->totsize;
|
2016-06-06 17:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the mark if it was on the line that was "eaten". */
|
|
|
|
if (openfile->mark == joining) {
|
|
|
|
openfile->mark = openfile->current;
|
|
|
|
openfile->mark_x += openfile->current_x;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
unlink_node(joining);
|
|
|
|
renumber(openfile->current);
|
2015-06-20 09:42:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Two lines were joined, so we need to refresh the screen. */
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
} else
|
|
|
|
/* We're at the end-of-file: nothing to do. */
|
|
|
|
return;
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2014-06-09 20:26:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the number of screen rows that a softwrapped line occupies
|
|
|
|
* has changed, we need a full refresh. */
|
|
|
|
if (ISSET(SOFTWRAP) && refresh_needed == FALSE &&
|
|
|
|
number_of_chunks_in(openfile->current) != old_amount)
|
|
|
|
refresh_needed = TRUE;
|
2014-06-09 20:26:54 +00:00
|
|
|
#endif
|
2009-12-12 22:21:20 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2015-06-28 14:12:25 +00:00
|
|
|
/* Delete the character under the cursor. */
|
2014-06-08 19:02:12 +00:00
|
|
|
void do_delete(void)
|
|
|
|
{
|
2018-10-24 09:02:08 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->mark && ISSET(LET_THEM_ZAP))
|
|
|
|
zap_text();
|
|
|
|
else
|
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
do_deletion(DEL);
|
2014-06-08 19:02:12 +00:00
|
|
|
}
|
|
|
|
|
2005-12-31 21:22:54 +00:00
|
|
|
/* Backspace over one character. That is, move the cursor left one
|
2006-11-08 13:05:50 +00:00
|
|
|
* character, and then delete the character under the cursor. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_backspace(void)
|
|
|
|
{
|
2018-10-24 09:02:08 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
if (openfile->mark && ISSET(LET_THEM_ZAP))
|
|
|
|
zap_text();
|
|
|
|
else
|
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current != openfile->fileage || openfile->current_x > 0) {
|
|
|
|
do_left();
|
|
|
|
do_deletion(BACK);
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 11:52:26 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
/* Delete text from the cursor until the first start of a word to
|
|
|
|
* the right, or to the left when backward is true. */
|
|
|
|
void do_cutword(bool backward)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Remember the current cursor position. */
|
|
|
|
filestruct *is_current = openfile->current;
|
|
|
|
size_t is_current_x = openfile->current_x;
|
|
|
|
|
|
|
|
/* Remember where the cutbuffer is and then make it seem blank. */
|
|
|
|
filestruct *is_cutbuffer = cutbuffer;
|
|
|
|
filestruct *is_cutbottom = cutbottom;
|
|
|
|
cutbuffer = NULL;
|
|
|
|
cutbottom = NULL;
|
|
|
|
|
2018-09-02 11:26:12 +00:00
|
|
|
/* Move the cursor to a word start, to the left or to the right.
|
|
|
|
* If that word is on another line and the cursor was not already
|
|
|
|
* on the edge of the original line, then put the cursor on that
|
|
|
|
* edge instead, so that lines will not be joined unexpectedly. */
|
|
|
|
if (backward) {
|
2018-09-09 19:01:09 +00:00
|
|
|
do_prev_word(ISSET(WORD_BOUNDS));
|
2018-09-02 11:26:12 +00:00
|
|
|
if (openfile->current != is_current) {
|
|
|
|
if (is_current_x > 0) {
|
|
|
|
openfile->current = is_current;
|
|
|
|
openfile->current_x = 0;
|
|
|
|
} else
|
|
|
|
openfile->current_x = strlen(openfile->current->data);
|
|
|
|
}
|
|
|
|
} else {
|
2018-09-09 19:01:09 +00:00
|
|
|
do_next_word(FALSE, ISSET(WORD_BOUNDS));
|
2018-09-02 11:26:12 +00:00
|
|
|
if (openfile->current != is_current &&
|
|
|
|
is_current->data[is_current_x] != '\0') {
|
|
|
|
openfile->current = is_current;
|
|
|
|
openfile->current_x = strlen(is_current->data);
|
|
|
|
}
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Set the mark at the start of that word. */
|
|
|
|
openfile->mark = openfile->current;
|
|
|
|
openfile->mark_x = openfile->current_x;
|
|
|
|
|
|
|
|
/* Put the cursor back where it was, so an undo will put it there too. */
|
|
|
|
openfile->current = is_current;
|
|
|
|
openfile->current_x = is_current_x;
|
|
|
|
|
|
|
|
/* Now kill the marked region and a word is gone. */
|
|
|
|
do_cut_text_void();
|
|
|
|
|
|
|
|
/* Discard the cut word and restore the cutbuffer. */
|
|
|
|
free_filestruct(cutbuffer);
|
|
|
|
cutbuffer = is_cutbuffer;
|
|
|
|
cutbottom = is_cutbottom;
|
2015-07-31 11:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Delete a word leftward. */
|
|
|
|
void do_cut_prev_word(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_cutword(TRUE);
|
2015-07-31 11:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Delete a word rightward. */
|
|
|
|
void do_cut_next_word(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_cutword(FALSE);
|
2015-07-31 11:52:26 +00:00
|
|
|
}
|
|
|
|
#endif /* !NANO_TINY */
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Insert a tab. If the TABS_TO_SPACES flag is set, insert the number
|
|
|
|
* of spaces that a tab would normally take up. */
|
2005-07-25 02:41:59 +00:00
|
|
|
void do_tab(void)
|
|
|
|
{
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
|
|
|
char *spaces = charalloc(tabsize + 1);
|
|
|
|
size_t length = tabsize - (xplustabs() % tabsize);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
charset(spaces, ' ', length);
|
|
|
|
spaces[length] = '\0';
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output(spaces, length, TRUE);
|
2005-07-25 02:41:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(spaces);
|
|
|
|
} else
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output((char *)"\t", 1, TRUE);
|
2005-07-25 02:41:59 +00:00
|
|
|
}
|
|
|
|
|
2006-04-28 13:19:56 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-09-10 11:25:20 +00:00
|
|
|
/* Add an indent to the given line. */
|
|
|
|
void indent_a_line(filestruct *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
|
|
|
|
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:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Add the fabricated indentation to the beginning of the line. */
|
|
|
|
line->data = charealloc(line->data, length + indent_len + 1);
|
|
|
|
charmove(line->data + indent_len, line->data, length + 1);
|
|
|
|
strncpy(line->data, indentation, indent_len);
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize += indent_len;
|
2017-07-13 22:22:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Compensate for the change in the current line. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
2017-12-29 20:35:14 +00:00
|
|
|
openfile->mark_x += indent_len;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += indent_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2017-07-13 22:22:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Indent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* This inserts either a tab character or a tab's worth of spaces,
|
|
|
|
* depending on whether --tabstospaces is in effect. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_indent(void)
|
2006-04-28 13:19:56 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *indentation;
|
|
|
|
filestruct *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. */
|
|
|
|
get_range((const filestruct **)&top, (const filestruct **)&bot);
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any leading empty lines. */
|
|
|
|
while (top != bot->next && top->data[0] == '\0')
|
|
|
|
top = top->next;
|
2017-08-16 15:16:18 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If all lines are empty, there is nothing to do. */
|
|
|
|
if (top == bot->next)
|
|
|
|
return;
|
2017-12-24 09:56:16 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation = charalloc(tabsize + 1);
|
2017-08-16 15:16:18 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Set the indentation to either a bunch of spaces or a single tab. */
|
|
|
|
if (ISSET(TABS_TO_SPACES)) {
|
|
|
|
charset(indentation, ' ', tabsize);
|
|
|
|
indentation[tabsize] = '\0';
|
|
|
|
} else {
|
|
|
|
indentation[0] = '\t';
|
|
|
|
indentation[1] = '\0';
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(INDENT);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, adding an indent to the non-empty ones,
|
|
|
|
* and recording whatever was added in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
char *real_indent = (line->data[0] == '\0') ? "" : indentation;
|
2017-07-07 16:06:55 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indent_a_line(line, real_indent);
|
|
|
|
update_multiline_undo(line->lineno, real_indent);
|
|
|
|
}
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(indentation);
|
2006-04-28 13:19:56 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
2006-04-28 13:19:56 +00:00
|
|
|
}
|
|
|
|
|
2017-12-07 20:09:11 +00:00
|
|
|
/* Return the number of bytes of whitespace at the start of the given text,
|
|
|
|
* but at most a tab's worth. */
|
2017-07-07 18:53:00 +00:00
|
|
|
size_t length_of_white(const char *text)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t bytes_of_white = 0;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
if (*text == '\t')
|
|
|
|
return ++bytes_of_white;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*text != ' ')
|
|
|
|
return bytes_of_white;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 20:35:14 +00:00
|
|
|
if (++bytes_of_white == tabsize)
|
2017-12-29 18:27:33 +00:00
|
|
|
return tabsize;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
text++;
|
|
|
|
}
|
2017-07-07 18:53:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-24 10:25:10 +00:00
|
|
|
/* Adjust the positions of mark and cursor when they are on the given line. */
|
|
|
|
void compensate_leftward(filestruct *line, size_t leftshift)
|
|
|
|
{
|
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. */
|
|
|
|
void unindent_a_line(filestruct *line, size_t indent_len)
|
2017-07-13 22:38:26 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t length = strlen(line->data);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the indent is empty, don't change the line. */
|
|
|
|
if (indent_len == 0)
|
|
|
|
return;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Remove the first tab's worth of whitespace from this line. */
|
|
|
|
charmove(line->data, line->data + indent_len, length - indent_len + 1);
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= indent_len;
|
2017-07-13 22:38:26 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when they are affected. */
|
|
|
|
compensate_leftward(line, indent_len);
|
2017-07-13 22:38:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 21:40:57 +00:00
|
|
|
/* Unindent the current line (or the marked lines) by tabsize columns.
|
|
|
|
* The removed indent can be a mixture of spaces plus at most one tab. */
|
2017-07-10 21:34:27 +00:00
|
|
|
void do_unindent(void)
|
2017-07-10 20:48:50 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
filestruct *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. */
|
|
|
|
get_range((const filestruct **)&top, (const filestruct **)&bot);
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any leading lines that cannot be unindented. */
|
|
|
|
while (top != bot->next && length_of_white(top->data) == 0)
|
|
|
|
top = top->next;
|
2017-07-07 18:53:00 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If none of the lines can be unindented, there is nothing to do. */
|
|
|
|
if (top == bot->next)
|
|
|
|
return;
|
2017-12-07 20:09:11 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(UNINDENT);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Go through each of the lines, removing their leading indent where
|
|
|
|
* possible, and saving the removed whitespace in the undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
size_t indent_len = length_of_white(line->data);
|
|
|
|
char *indentation = mallocstrncpy(NULL, line->data, indent_len + 1);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
indentation[indent_len] = '\0';
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
unindent_a_line(line, indent_len);
|
|
|
|
update_multiline_undo(line->lineno, indentation);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(indentation);
|
|
|
|
}
|
2017-07-10 20:48:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
set_modified();
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
2017-07-10 20:48:50 +00:00
|
|
|
}
|
2017-07-13 23:15:58 +00:00
|
|
|
|
|
|
|
/* Perform an undo or redo for an indent or unindent action. */
|
|
|
|
void handle_indent_action(undo *u, bool undoing, bool add_indent)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_group *group = u->grouping;
|
2018-04-01 09:36:27 +00:00
|
|
|
filestruct *line = fsfromline(group->top_line);
|
|
|
|
|
|
|
|
if (group->next != NULL)
|
|
|
|
statusline(ALERT, "Multiple groups -- please report a bug");
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the indenter adjust it. */
|
|
|
|
if (!undoing)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
/* For each line in the group, add or remove the individual indent. */
|
|
|
|
while (line && line->lineno <= group->bottom_line) {
|
|
|
|
char *blanks = group->indentations[line->lineno - group->top_line];
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
if (undoing ^ add_indent)
|
|
|
|
indent_a_line(line, blanks);
|
|
|
|
else
|
|
|
|
unindent_a_line(line, strlen(blanks));
|
2017-09-12 18:46:14 +00:00
|
|
|
|
2018-04-01 09:36:27 +00:00
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When undoing, reposition the cursor to the recorded location. */
|
|
|
|
if (undoing)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2017-07-13 23:15:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2017-07-13 23:15:58 +00:00
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif /* !NANO_TINY */
|
|
|
|
|
|
|
|
/* Test whether the string is empty or consists of only blanks. */
|
|
|
|
bool white_string(const char *s)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*s != '\0' && (is_blank_mbchar(s) || *s == '\r'))
|
|
|
|
s += move_mbright(s, 0);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return !*s;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2016-09-08 19:00:51 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2016-05-25 20:13:50 +00:00
|
|
|
/* Test whether the given line can be uncommented, or add or remove a comment,
|
|
|
|
* depending on action. Return TRUE if the line is uncommentable, or when
|
|
|
|
* anything was added or removed; FALSE otherwise. */
|
2017-09-10 11:25:20 +00:00
|
|
|
bool comment_line(undo_type action, filestruct *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);
|
|
|
|
|
|
|
|
if (!ISSET(NO_NEWLINES) && line == openfile->filebot)
|
|
|
|
return FALSE;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == COMMENT) {
|
|
|
|
/* Make room for the comment sequence(s), move the text right and
|
|
|
|
* copy them in. */
|
|
|
|
line->data = charealloc(line->data, line_len + pre_len + post_len + 1);
|
|
|
|
charmove(line->data + pre_len, line->data, line_len + 1);
|
|
|
|
charmove(line->data, comment_seq, pre_len);
|
|
|
|
if (post_len > 0)
|
|
|
|
charmove(line->data + pre_len + line_len, post_seq, post_len + 1);
|
|
|
|
|
|
|
|
openfile->totsize += pre_len + post_len;
|
|
|
|
|
|
|
|
/* If needed, adjust the position of the mark and of the cursor. */
|
|
|
|
if (line == openfile->mark && openfile->mark_x > 0)
|
|
|
|
openfile->mark_x += pre_len;
|
|
|
|
if (line == openfile->current && openfile->current_x > 0) {
|
|
|
|
openfile->current_x += pre_len;
|
|
|
|
openfile->placewewant = xplustabs();
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the line is commented, report it as uncommentable, or uncomment it. */
|
|
|
|
if (strncmp(line->data, comment_seq, pre_len) == 0 && (post_len == 0 ||
|
|
|
|
strcmp(line->data + line_len - post_len, post_seq) == 0)) {
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (action == PREFLIGHT)
|
|
|
|
return TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Erase the comment prefix by moving the non-comment part. */
|
|
|
|
charmove(line->data, line->data + pre_len, line_len - pre_len);
|
|
|
|
/* Truncate the postfix if there was one. */
|
|
|
|
line->data[line_len - pre_len - post_len] = '\0';
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->totsize -= pre_len + post_len;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the positions of mark and cursor, when needed. */
|
|
|
|
compensate_leftward(line, pre_len);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-04-01 08:53:15 +00:00
|
|
|
/* Comment or uncomment the current line or the marked lines. */
|
|
|
|
void do_comment(void)
|
|
|
|
{
|
|
|
|
const char *comment_seq = GENERAL_COMMENT_CHARACTER;
|
|
|
|
undo_type action = UNCOMMENT;
|
|
|
|
filestruct *top, *bot, *line;
|
|
|
|
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. */
|
|
|
|
get_range((const filestruct **)&top, (const filestruct **)&bot);
|
|
|
|
|
|
|
|
/* If only the magic line is selected, don't do anything. */
|
|
|
|
if (top == bot && bot == openfile->filebot && !ISSET(NO_NEWLINES)) {
|
|
|
|
statusbar(_("Cannot comment past end of file"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Figure out whether to comment or uncomment the selected line or lines. */
|
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
empty = white_string(line->data);
|
|
|
|
|
|
|
|
/* If this line is not blank and not commented, we comment all. */
|
|
|
|
if (!empty && !comment_line(PREFLIGHT, line, comment_seq)) {
|
|
|
|
action = COMMENT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
all_empty = all_empty && empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If all selected lines are blank, we comment them. */
|
|
|
|
action = all_empty ? COMMENT : action;
|
|
|
|
|
|
|
|
add_undo(action);
|
|
|
|
|
|
|
|
/* Store the comment sequence used for the operation, because it could
|
|
|
|
* change when the file name changes; we need to know what it was. */
|
|
|
|
openfile->current_undo->strdata = mallocstrcpy(NULL, comment_seq);
|
|
|
|
|
2018-04-01 09:24:44 +00:00
|
|
|
/* Comment/uncomment each of the selected lines when possible, and
|
|
|
|
* store undo data when a line changed. */
|
2018-04-01 08:53:15 +00:00
|
|
|
for (line = top; line != bot->next; line = line->next) {
|
|
|
|
if (comment_line(action, line, comment_seq))
|
|
|
|
update_multiline_undo(line->lineno, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
set_modified();
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
shift_held = TRUE;
|
|
|
|
}
|
|
|
|
|
2016-05-25 20:13:50 +00:00
|
|
|
/* Perform an undo or redo for a comment or uncomment action. */
|
|
|
|
void handle_comment_action(undo *u, bool undoing, bool add_comment)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo_group *group = u->grouping;
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When redoing, reposition the cursor and let the commenter adjust it. */
|
|
|
|
if (!undoing)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (group) {
|
|
|
|
filestruct *f = fsfromline(group->top_line);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (f && f->lineno <= group->bottom_line) {
|
|
|
|
comment_line(undoing ^ add_comment ?
|
|
|
|
COMMENT : UNCOMMENT, f, u->strdata);
|
|
|
|
f = f->next;
|
|
|
|
}
|
2017-09-12 18:46:14 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
group = group->next;
|
|
|
|
}
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When undoing, reposition the cursor to the recorded location. */
|
|
|
|
if (undoing)
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2016-05-25 20:13:50 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
#endif /* ENABLE_COMMENT */
|
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
2014-05-15 20:00:46 +00:00
|
|
|
#define redo_paste undo_cut
|
|
|
|
#define undo_paste redo_cut
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Undo a cut, or redo an uncut. */
|
2008-09-16 21:35:19 +00:00
|
|
|
void undo_cut(undo *u)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get to where we need to uncut from. */
|
2018-12-27 21:56:30 +00:00
|
|
|
if (u->xflags & WAS_WHOLE_LINE)
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, 0);
|
|
|
|
else
|
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
|
2018-07-08 15:36:17 +00:00
|
|
|
/* If nothing was actually cut, positioning the cursor was enough. */
|
2018-06-09 21:50:30 +00:00
|
|
|
if (!u->cutbuffer)
|
|
|
|
return;
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
copy_from_buffer(u->cutbuffer);
|
2014-05-25 19:41:49 +00:00
|
|
|
|
2018-12-27 22:09:19 +00:00
|
|
|
/* If the final line was originally cut, remove the extra magicline. */
|
|
|
|
if ((u->xflags & WAS_FINAL_LINE) && !ISSET(NO_NEWLINES))
|
|
|
|
remove_magicline();
|
|
|
|
|
2018-12-27 21:56:30 +00:00
|
|
|
if (!(u->xflags & WAS_MARKED_FORWARD) && u->type != PASTE)
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
2008-09-16 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Redo a cut, or undo an uncut. */
|
2014-04-08 18:38:45 +00:00
|
|
|
void redo_cut(undo *u)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
filestruct *oldcutbuffer = cutbuffer, *oldcutbottom = cutbottom;
|
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;
|
|
|
|
cutbottom = NULL;
|
2014-07-02 20:52:27 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = fsfromline(u->mark_begin_lineno);
|
2018-12-27 21:56:30 +00:00
|
|
|
openfile->mark_x = (u->xflags & WAS_WHOLE_LINE) ? 0 : u->mark_begin_x;
|
2014-06-22 11:03:49 +00:00
|
|
|
|
2018-10-24 02:25:22 +00:00
|
|
|
do_cut_text(FALSE, TRUE, FALSE, u->type == ZAP);
|
2014-06-22 11:03:49 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free_filestruct(cutbuffer);
|
|
|
|
cutbuffer = oldcutbuffer;
|
|
|
|
cutbottom = oldcutbottom;
|
2008-09-16 21:35:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-04 20:45:28 +00:00
|
|
|
/* Undo the last thing(s) we did. */
|
2008-07-10 20:13:04 +00:00
|
|
|
void do_undo(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
2018-03-03 15:54:55 +00:00
|
|
|
filestruct *f = NULL, *t = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
filestruct *oldcutbuffer, *oldcutbottom;
|
|
|
|
char *data, *undidmsg = NULL;
|
|
|
|
size_t from_x, to_x;
|
|
|
|
|
|
|
|
if (!u) {
|
|
|
|
statusbar(_("Nothing in undo buffer!"));
|
|
|
|
return;
|
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2018-03-03 15:54:55 +00:00
|
|
|
if (u->type <= REPLACE) {
|
|
|
|
f = fsfromline(u->mark_begin_lineno);
|
|
|
|
if (f == NULL)
|
|
|
|
return;
|
|
|
|
}
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current_x = u->begin;
|
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
2018-05-27 15:53:36 +00:00
|
|
|
/* TRANSLATORS: The next thirteen strings describe actions
|
2018-05-27 16:35:52 +00:00
|
|
|
* that are undone or redone. They are all nouns, not verbs. */
|
2017-12-29 18:27:33 +00:00
|
|
|
undidmsg = _("text add");
|
2018-12-27 21:56:30 +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:
|
|
|
|
if (f->next == NULL) {
|
|
|
|
statusline(ALERT, "Missing break line -- please report a bug");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
renumber(f);
|
|
|
|
goto_line_posx(u->lineno, to_x);
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
|
|
|
case DEL:
|
|
|
|
undidmsg = _("text delete");
|
|
|
|
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. */
|
2018-12-27 21:56:30 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_BACKSPACE) && !ISSET(NO_NEWLINES)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(openfile->filebot->lineno, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
t = make_new_node(f);
|
|
|
|
t->data = mallocstrcpy(NULL, u->strdata);
|
|
|
|
data = mallocstrncpy(NULL, f->data, u->mark_begin_x + 1);
|
|
|
|
data[u->mark_begin_x] = '\0';
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
splice_node(f, t);
|
2018-03-03 20:23:02 +00:00
|
|
|
renumber(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:
|
|
|
|
undidmsg = _("text replace");
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
data = u->strdata;
|
|
|
|
u->strdata = f->data;
|
|
|
|
f->data = data;
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_END:
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
while (openfile->current_undo->type != SPLIT_BEGIN)
|
|
|
|
do_undo();
|
|
|
|
u = openfile->current_undo;
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
undidmsg = _("text add");
|
|
|
|
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:
|
|
|
|
undidmsg = _("text cut");
|
|
|
|
undo_cut(u);
|
|
|
|
break;
|
|
|
|
case PASTE:
|
|
|
|
undidmsg = _("text uncut");
|
|
|
|
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:
|
|
|
|
undidmsg = _("text insert");
|
|
|
|
oldcutbuffer = cutbuffer;
|
|
|
|
oldcutbottom = cutbottom;
|
|
|
|
cutbuffer = NULL;
|
|
|
|
cutbottom = NULL;
|
|
|
|
openfile->mark = fsfromline(u->mark_begin_lineno);
|
|
|
|
openfile->mark_x = u->mark_begin_x;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
cut_marked(NULL);
|
|
|
|
free_filestruct(u->cutbuffer);
|
|
|
|
u->cutbuffer = cutbuffer;
|
|
|
|
u->cutbottom = cutbottom;
|
|
|
|
cutbuffer = oldcutbuffer;
|
|
|
|
cutbottom = oldcutbottom;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2018-08-04 07:51:32 +00:00
|
|
|
undidmsg = u->strdata;
|
2018-08-16 13:59:30 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
|
|
|
case COUPLE_END:
|
|
|
|
openfile->current_undo = openfile->current_undo->next;
|
|
|
|
do_undo();
|
|
|
|
do_undo();
|
|
|
|
do_undo();
|
|
|
|
return;
|
2017-12-29 18:27:33 +00:00
|
|
|
case INDENT:
|
|
|
|
handle_indent_action(u, TRUE, TRUE);
|
|
|
|
undidmsg = _("indent");
|
|
|
|
break;
|
|
|
|
case UNINDENT:
|
|
|
|
handle_indent_action(u, TRUE, FALSE);
|
|
|
|
undidmsg = _("unindent");
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_COMMENT
|
|
|
|
case COMMENT:
|
|
|
|
handle_comment_action(u, TRUE, TRUE);
|
|
|
|
undidmsg = _("comment");
|
|
|
|
break;
|
|
|
|
case UNCOMMENT:
|
|
|
|
handle_comment_action(u, TRUE, FALSE);
|
|
|
|
undidmsg = _("uncomment");
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
statusline(ALERT, "Wrong undo type -- please report a bug");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (undidmsg && !pletion_line)
|
|
|
|
statusline(HUSH, _("Undid action (%s)"), undidmsg);
|
|
|
|
|
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)
|
|
|
|
{
|
2018-03-03 15:54:55 +00:00
|
|
|
filestruct *f = NULL, *shoveline;
|
2017-12-29 18:27:33 +00:00
|
|
|
char *data, *redidmsg = NULL;
|
|
|
|
undo *u = openfile->undotop;
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u == NULL || u == openfile->current_undo) {
|
|
|
|
statusbar(_("Nothing to re-do!"));
|
|
|
|
return;
|
|
|
|
}
|
2015-10-29 17:02:13 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the previous undo item. */
|
|
|
|
while (u != NULL && u->next != openfile->current_undo)
|
|
|
|
u = u->next;
|
2015-10-28 20:49:16 +00:00
|
|
|
|
2018-07-14 18:02:03 +00:00
|
|
|
if (u == NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusline(ALERT, "Bad undo stack -- please report a bug");
|
|
|
|
return;
|
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2018-03-03 15:54:55 +00:00
|
|
|
if (u->type <= REPLACE) {
|
|
|
|
f = fsfromline(u->mark_begin_lineno);
|
|
|
|
if (f == NULL)
|
|
|
|
return;
|
|
|
|
}
|
2016-05-08 08:51:40 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
|
|
|
redidmsg = _("text add");
|
2018-12-27 21:56:30 +00:00
|
|
|
if ((u->xflags & WAS_FINAL_LINE) && !ISSET(NO_NEWLINES))
|
2017-12-29 18:27:33 +00:00
|
|
|
new_magicline();
|
|
|
|
data = charalloc(strlen(f->data) + strlen(u->strdata) + 1);
|
|
|
|
strncpy(data, f->data, u->begin);
|
|
|
|
strcpy(&data[u->begin], u->strdata);
|
|
|
|
strcpy(&data[u->begin + strlen(u->strdata)], &f->data[u->begin]);
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->mark_begin_lineno, u->mark_begin_x);
|
|
|
|
break;
|
|
|
|
case ENTER:
|
|
|
|
redidmsg = _("line break");
|
|
|
|
shoveline = make_new_node(f);
|
|
|
|
shoveline->data = mallocstrcpy(NULL, u->strdata);
|
|
|
|
data = mallocstrncpy(NULL, f->data, u->begin + 1);
|
|
|
|
data[u->begin] = '\0';
|
|
|
|
free(f->data);
|
|
|
|
f->data = data;
|
|
|
|
splice_node(f, shoveline);
|
|
|
|
renumber(shoveline);
|
|
|
|
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:
|
|
|
|
redidmsg = _("text delete");
|
|
|
|
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:
|
|
|
|
if (f->next == NULL) {
|
|
|
|
statusline(ALERT, "Missing join line -- please report a bug");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
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. */
|
2018-12-27 21:56:30 +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);
|
|
|
|
renumber(f);
|
|
|
|
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:
|
|
|
|
redidmsg = _("text replace");
|
|
|
|
data = u->strdata;
|
|
|
|
u->strdata = f->data;
|
|
|
|
f->data = data;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
openfile->current_undo = u;
|
|
|
|
while (openfile->current_undo->type != SPLIT_END)
|
|
|
|
do_redo();
|
|
|
|
u = openfile->current_undo;
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
case SPLIT_END:
|
|
|
|
redidmsg = _("text add");
|
|
|
|
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:
|
|
|
|
redidmsg = _("text cut");
|
|
|
|
redo_cut(u);
|
|
|
|
break;
|
|
|
|
case PASTE:
|
|
|
|
redidmsg = _("text uncut");
|
|
|
|
redo_paste(u);
|
|
|
|
break;
|
|
|
|
case INSERT:
|
|
|
|
redidmsg = _("text insert");
|
|
|
|
goto_line_posx(u->lineno, u->begin);
|
|
|
|
copy_from_buffer(u->cutbuffer);
|
|
|
|
free_filestruct(u->cutbuffer);
|
|
|
|
u->cutbuffer = NULL;
|
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
|
|
|
openfile->current_undo = u;
|
|
|
|
do_redo();
|
|
|
|
do_redo();
|
|
|
|
do_redo();
|
|
|
|
return;
|
|
|
|
case COUPLE_END:
|
2018-08-04 07:51:32 +00:00
|
|
|
redidmsg = u->strdata;
|
2018-08-16 13:59:30 +00:00
|
|
|
goto_line_posx(u->lineno, u->begin);
|
2018-05-17 10:10:06 +00:00
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case INDENT:
|
|
|
|
handle_indent_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("indent");
|
|
|
|
break;
|
|
|
|
case UNINDENT:
|
|
|
|
handle_indent_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("unindent");
|
|
|
|
break;
|
|
|
|
#ifdef ENABLE_COMMENT
|
|
|
|
case COMMENT:
|
|
|
|
handle_comment_action(u, FALSE, TRUE);
|
|
|
|
redidmsg = _("comment");
|
|
|
|
break;
|
|
|
|
case UNCOMMENT:
|
|
|
|
handle_comment_action(u, FALSE, FALSE);
|
|
|
|
redidmsg = _("uncomment");
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
statusline(ALERT, "Wrong redo type -- please report a bug");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (redidmsg)
|
|
|
|
statusline(HUSH, _("Redid action (%s)"), redidmsg);
|
|
|
|
|
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
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
filestruct *newnode = make_new_node(openfile->current);
|
|
|
|
size_t extra = 0;
|
2017-04-19 12:29:30 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-05-28 14:35:27 +00:00
|
|
|
filestruct *sampleline = openfile->current;
|
2017-12-29 18:27:33 +00:00
|
|
|
bool allblanks = FALSE;
|
|
|
|
|
|
|
|
if (ISSET(AUTOINDENT)) {
|
2018-05-28 07:57:24 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When doing automatic long-line wrapping and the next line is
|
|
|
|
* in this same paragraph, use its indentation as the model. */
|
2018-05-30 18:45:06 +00:00
|
|
|
if (!ISSET(NO_WRAP) && sampleline->next != NULL &&
|
|
|
|
inpar(sampleline->next) && !begpar(sampleline->next, 0))
|
2018-05-28 08:39:47 +00:00
|
|
|
sampleline = sampleline->next;
|
2018-05-28 07:57:24 +00:00
|
|
|
#endif
|
2018-05-22 19:10:10 +00:00
|
|
|
extra = indent_length(sampleline->data);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When breaking in the indentation, limit the automatic one. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (extra > openfile->current_x)
|
|
|
|
extra = openfile->current_x;
|
|
|
|
else if (extra == openfile->current_x)
|
|
|
|
allblanks = TRUE;
|
|
|
|
}
|
2018-05-28 07:57:24 +00:00
|
|
|
#endif /* NANO_TINY */
|
2017-12-29 18:27:33 +00:00
|
|
|
newnode->data = charalloc(strlen(openfile->current->data +
|
|
|
|
openfile->current_x) + extra + 1);
|
|
|
|
strcpy(&newnode->data[extra], openfile->current->data +
|
|
|
|
openfile->current_x);
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(AUTOINDENT)) {
|
2018-05-22 19:10:10 +00:00
|
|
|
/* Copy the whitespace from the sample line to the new one. */
|
|
|
|
strncpy(newnode->data, sampleline->data, extra);
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there were only blanks before the cursor, trim them. */
|
|
|
|
if (allblanks)
|
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
null_at(&openfile->current->data, openfile->current_x);
|
2016-12-13 18:27:33 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(ENTER);
|
2017-04-06 18:34:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Adjust the mark if it was on the current line after the cursor. */
|
|
|
|
if (openfile->mark == openfile->current &&
|
|
|
|
openfile->mark_x > openfile->current_x) {
|
|
|
|
openfile->mark = newnode;
|
|
|
|
openfile->mark_x += extra - openfile->current_x;
|
|
|
|
}
|
2005-07-25 02:41:59 +00:00
|
|
|
#endif
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Insert the newly created line after the current one and renumber. */
|
2017-12-29 18:27:33 +00:00
|
|
|
splice_node(openfile->current, newnode);
|
|
|
|
renumber(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. */
|
|
|
|
void send_data(const filestruct *line, int fd)
|
|
|
|
{
|
|
|
|
FILE *tube = fdopen(fd, "w");
|
|
|
|
|
|
|
|
if (tube == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Send each line, except a final empty line. */
|
|
|
|
while (line != NULL && (line->next != NULL || line->data[0] != '\0')) {
|
|
|
|
fprintf(tube, "%s%s", line->data, line->next == NULL ? "" : "\n");
|
|
|
|
line = line->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(tube);
|
|
|
|
}
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Execute the given command in a shell. Return TRUE on success. */
|
2005-07-24 19:57:51 +00:00
|
|
|
bool execute_command(const char *command)
|
|
|
|
{
|
2018-05-16 18:42:44 +00:00
|
|
|
int from_fd[2], to_fd[2];
|
2018-05-16 01:20:11 +00:00
|
|
|
/* The pipes through which text will written and read. */
|
|
|
|
const bool should_pipe = (command[0] == '|');
|
2018-04-23 11:04:31 +00:00
|
|
|
FILE *stream;
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *shellenv;
|
|
|
|
struct sigaction oldaction, newaction;
|
|
|
|
/* Original and temporary handlers for SIGINT. */
|
2018-04-23 10:30:14 +00:00
|
|
|
bool setup_failed = FALSE;
|
|
|
|
/* Whether setting up the temporary SIGINT handler failed. */
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* Create a pipe to read the command's output from, and, if needed,
|
|
|
|
* a pipe to feed the command's input through. */
|
2018-05-16 18:42:44 +00:00
|
|
|
if (pipe(from_fd) == -1 || (should_pipe && pipe(to_fd) == -1)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Could not create pipe"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Check which shell to use. If none is specified, use /bin/sh. */
|
2017-12-29 18:27:33 +00:00
|
|
|
shellenv = getenv("SHELL");
|
|
|
|
if (shellenv == NULL)
|
|
|
|
shellenv = (char *) "/bin/sh";
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Fork a child process to run the command in. */
|
2018-07-30 18:07:25 +00:00
|
|
|
if ((pid_of_command = fork()) == 0) {
|
2018-05-16 18:42:44 +00:00
|
|
|
/* Child: close the unused read end of the output pipe. */
|
|
|
|
close(from_fd[0]);
|
2018-04-23 12:01:55 +00:00
|
|
|
|
2018-05-16 18:42:44 +00:00
|
|
|
/* Connect the write end of the output pipe to the process' output streams. */
|
|
|
|
dup2(from_fd[1], fileno(stdout));
|
|
|
|
dup2(from_fd[1], fileno(stderr));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* If the parent sends text, connect the read end of the
|
|
|
|
* feeding pipe to the child's input stream. */
|
|
|
|
if (should_pipe) {
|
|
|
|
dup2(to_fd[0], fileno(stdin));
|
|
|
|
close(to_fd[1]);
|
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Run the given command inside the preferred shell. */
|
2018-05-16 01:20:11 +00:00
|
|
|
execl(shellenv, tail(shellenv), "-c", should_pipe ? &command[1] : command, NULL);
|
2018-04-23 12:01:55 +00:00
|
|
|
|
|
|
|
/* If the exec call returns, there was an error. */
|
|
|
|
exit(1);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 12:01:55 +00:00
|
|
|
/* Parent: close the unused write end of the pipe. */
|
2018-05-16 18:42:44 +00:00
|
|
|
close(from_fd[1]);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-07-30 18:07:25 +00:00
|
|
|
if (pid_of_command == -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Could not fork"));
|
2018-05-16 18:42:44 +00:00
|
|
|
close(from_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-16 01:20:11 +00:00
|
|
|
/* If the command starts with "|", pipe buffer or region to the command. */
|
|
|
|
if (should_pipe) {
|
|
|
|
filestruct *was_cutbuffer = cutbuffer;
|
|
|
|
cutbuffer = NULL;
|
|
|
|
|
2018-05-24 19:09:14 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-17 10:10:06 +00:00
|
|
|
if (ISSET(MULTIBUFFER)) {
|
2018-05-16 01:20:11 +00:00
|
|
|
switch_to_prev_buffer();
|
2018-05-24 19:09:14 +00:00
|
|
|
if (openfile->mark)
|
2018-10-24 02:25:22 +00:00
|
|
|
do_cut_text(TRUE, TRUE, FALSE, FALSE);
|
2018-05-24 19:09:14 +00:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
2018-05-17 10:10:06 +00:00
|
|
|
add_undo(COUPLE_BEGIN);
|
2018-08-04 07:51:32 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("filtering"));
|
2018-05-24 19:09:14 +00:00
|
|
|
if (openfile->mark == NULL) {
|
2018-05-16 01:20:11 +00:00
|
|
|
openfile->current = openfile->fileage;
|
|
|
|
openfile->current_x = 0;
|
|
|
|
}
|
|
|
|
add_undo(CUT);
|
2018-10-24 02:25:22 +00:00
|
|
|
do_cut_text(FALSE, openfile->mark, openfile->mark == NULL, FALSE);
|
2018-05-16 01:20:11 +00:00
|
|
|
update_undo(CUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fork() == 0) {
|
|
|
|
close(to_fd[0]);
|
|
|
|
send_data(cutbuffer != NULL ? cutbuffer : openfile->fileage, to_fd[1]);
|
|
|
|
close(to_fd[1]);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(to_fd[0]);
|
|
|
|
close(to_fd[1]);
|
|
|
|
|
2018-05-24 19:09:14 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-16 01:20:11 +00:00
|
|
|
if (ISSET(MULTIBUFFER))
|
|
|
|
switch_to_next_buffer();
|
2018-05-24 19:09:14 +00:00
|
|
|
#endif
|
2018-05-16 01:20:11 +00:00
|
|
|
free_filestruct(cutbuffer);
|
|
|
|
cutbuffer = was_cutbuffer;
|
|
|
|
}
|
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Re-enable interpretation of the special control keys so that we get
|
2017-12-29 18:27:33 +00:00
|
|
|
* SIGINT when Ctrl-C is pressed. */
|
|
|
|
enable_signals();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Set things up so that Ctrl-C will terminate the forked process. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (sigaction(SIGINT, NULL, &newaction) == -1) {
|
2018-04-23 10:30:14 +00:00
|
|
|
setup_failed = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
|
|
|
} else {
|
2018-07-30 18:07:25 +00:00
|
|
|
newaction.sa_handler = cancel_the_command;
|
2017-12-29 18:27:33 +00:00
|
|
|
if (sigaction(SIGINT, &newaction, &oldaction) == -1) {
|
2018-04-23 10:30:14 +00:00
|
|
|
setup_failed = TRUE;
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 18:42:44 +00:00
|
|
|
stream = fdopen(from_fd[0], "rb");
|
2018-04-23 11:04:31 +00:00
|
|
|
if (stream == NULL)
|
2018-04-23 15:56:58 +00:00
|
|
|
statusline(ALERT, _("Failed to open pipe: %s"), strerror(errno));
|
|
|
|
else
|
|
|
|
read_file(stream, 0, "pipe", TRUE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-04 07:51:32 +00:00
|
|
|
if (should_pipe && !ISSET(MULTIBUFFER)) {
|
2018-05-17 10:10:06 +00:00
|
|
|
add_undo(COUPLE_END);
|
2018-08-04 07:51:32 +00:00
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("filtering"));
|
|
|
|
}
|
2018-05-17 10:10:06 +00:00
|
|
|
|
2018-08-19 07:54:16 +00:00
|
|
|
/* Wait for the external command (and possibly data sender) to terminate. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (wait(NULL) == -1)
|
|
|
|
nperror("wait");
|
2018-08-19 07:54:16 +00:00
|
|
|
if (should_pipe && (wait(NULL) == -1))
|
|
|
|
nperror("wait");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* If it was changed, restore the handler for SIGINT. */
|
2018-04-23 10:30:14 +00:00
|
|
|
if (!setup_failed && sigaction(SIGINT, &oldaction, NULL) == -1)
|
2017-12-29 18:27:33 +00:00
|
|
|
nperror("sigaction");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-23 10:19:25 +00:00
|
|
|
/* Restore the terminal to its desired state, and disable
|
|
|
|
* interpretation of the special control keys again. */
|
2017-12-29 18:27:33 +00:00
|
|
|
terminal_init();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2017-11-27 15:11:54 +00:00
|
|
|
/* Discard undo items that are newer than the given one, or all if NULL.
|
2017-12-18 19:08:06 +00:00
|
|
|
* When keep is TRUE, do not touch the last_saved pointer. */
|
2017-11-27 15:11:54 +00:00
|
|
|
void discard_until(const undo *thisitem, openfilestruct *thefile, bool keep)
|
2015-12-03 08:50:34 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *dropit = thefile->undotop;
|
|
|
|
undo_group *group;
|
|
|
|
|
|
|
|
while (dropit != NULL && dropit != thisitem) {
|
|
|
|
thefile->undotop = dropit->next;
|
|
|
|
free(dropit->strdata);
|
|
|
|
free_filestruct(dropit->cutbuffer);
|
|
|
|
group = dropit->grouping;
|
|
|
|
while (group != NULL) {
|
|
|
|
undo_group *next = group->next;
|
|
|
|
free_chararray(group->indentations,
|
|
|
|
group->bottom_line - group->top_line);
|
|
|
|
free(group);
|
|
|
|
group = next;
|
|
|
|
}
|
2018-03-11 19:06:24 +00:00
|
|
|
free(dropit);
|
2017-12-29 18:27:33 +00:00
|
|
|
dropit = thefile->undotop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust the pointer to the top of the undo stack. */
|
|
|
|
thefile->current_undo = (undo *)thisitem;
|
|
|
|
|
|
|
|
/* Prevent a chain of editing actions from continuing. */
|
|
|
|
thefile->last_action = OTHER;
|
|
|
|
|
|
|
|
/* When requested, record that the undo stack was chopped, and
|
|
|
|
* that thus there is no point at which the file was last saved. */
|
|
|
|
if (!keep)
|
|
|
|
thefile->last_saved = (undo *)0xbeeb;
|
2015-12-03 08:50:34 +00:00
|
|
|
}
|
|
|
|
|
2018-08-20 17:31:23 +00:00
|
|
|
/* Add a new undo item of the given type to the top of the current pile. */
|
2015-06-17 11:18:20 +00:00
|
|
|
void add_undo(undo_type action)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
|
|
|
/* The thing we did previously. */
|
|
|
|
|
|
|
|
/* Blow away newer undo items if we add somewhere in the middle. */
|
|
|
|
discard_until(u, openfile, TRUE);
|
2008-07-10 20:13:04 +00:00
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Allocate and initialize a new undo item. */
|
2017-12-29 18:27:33 +00:00
|
|
|
u = (undo *) nmalloc(sizeof(undo));
|
|
|
|
u->type = action;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->strdata = NULL;
|
|
|
|
u->cutbuffer = NULL;
|
|
|
|
u->cutbottom = NULL;
|
|
|
|
u->lineno = openfile->current->lineno;
|
|
|
|
u->begin = openfile->current_x;
|
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
u->wassize = openfile->totsize;
|
2018-08-09 03:38:16 +00:00
|
|
|
u->newsize = openfile->totsize;
|
2018-06-01 14:45:37 +00:00
|
|
|
u->xflags = 0;
|
|
|
|
u->grouping = NULL;
|
|
|
|
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2018-06-01 14:45:37 +00:00
|
|
|
/* If some action caused automatic long-line wrapping, insert the
|
|
|
|
* SPLIT_BEGIN item underneath that action's undo item. Otherwise,
|
|
|
|
* just add the new item to the top of the undo stack. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (u->type == SPLIT_BEGIN) {
|
|
|
|
u->next = openfile->undotop->next;
|
|
|
|
openfile->undotop->next = u;
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
u->next = openfile->undotop;
|
|
|
|
openfile->undotop = u;
|
|
|
|
openfile->current_undo = u;
|
|
|
|
}
|
|
|
|
|
2018-06-01 14:45:37 +00:00
|
|
|
/* Record the info needed to be able to undo each possible action. */
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
|
|
|
case ADD:
|
|
|
|
/* If a new magic line will be added, an undo should remove it. */
|
|
|
|
if (openfile->current == openfile->filebot)
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_FINAL_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
u->wassize--;
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case ENTER:
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
|
|
|
/* If the next line is the magic line, don't ever undo this
|
|
|
|
* backspace, as it won't actually have deleted anything. */
|
|
|
|
if (openfile->current->next == openfile->filebot &&
|
|
|
|
openfile->current->data[0] != '\0')
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_FINAL_BACKSPACE;
|
2017-12-29 18:27:33 +00:00
|
|
|
case DEL:
|
2018-06-01 14:45:37 +00:00
|
|
|
/* When not at the end of a line, store the deleted character,
|
|
|
|
* else purposely fall into the line-joining code. */
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current->data[openfile->current_x] != '\0') {
|
|
|
|
char *char_buf = charalloc(MAXCHARLEN + 1);
|
|
|
|
int char_len = parse_mbchar(&openfile->current->data[u->begin],
|
|
|
|
char_buf, NULL);
|
|
|
|
char_buf[char_len] = '\0';
|
|
|
|
u->strdata = char_buf;
|
|
|
|
if (u->type == BACK)
|
|
|
|
u->mark_begin_x += char_len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case JOIN:
|
|
|
|
if (openfile->current->next) {
|
|
|
|
if (u->type == BACK) {
|
|
|
|
u->lineno = openfile->current->next->lineno;
|
|
|
|
u->begin = 0;
|
|
|
|
}
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->next->data);
|
|
|
|
}
|
|
|
|
action = u->type = JOIN;
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case REPLACE:
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->data);
|
|
|
|
break;
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
case SPLIT_BEGIN:
|
|
|
|
action = openfile->undotop->type;
|
|
|
|
break;
|
|
|
|
case SPLIT_END:
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
case CUT_TO_EOF:
|
2018-12-27 22:09:19 +00:00
|
|
|
u->xflags |= WAS_FINAL_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT:
|
|
|
|
if (openfile->mark) {
|
|
|
|
u->mark_begin_lineno = openfile->mark->lineno;
|
|
|
|
u->mark_begin_x = openfile->mark_x;
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= MARK_WAS_SET;
|
2018-12-27 22:09:19 +00:00
|
|
|
if (openfile->current == openfile->filebot ||
|
|
|
|
openfile->mark == openfile->filebot)
|
|
|
|
u->xflags |= WAS_FINAL_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else if (!ISSET(CUT_FROM_CURSOR)) {
|
|
|
|
/* The entire line is being cut regardless of the cursor position. */
|
|
|
|
u->begin = 0;
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_WHOLE_LINE;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PASTE:
|
|
|
|
u->cutbuffer = copy_filestruct(cutbuffer);
|
|
|
|
u->lineno += cutbottom->lineno - cutbuffer->lineno;
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case INSERT:
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
|
|
|
case COUPLE_END:
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
case INDENT:
|
|
|
|
case UNINDENT:
|
|
|
|
break;
|
2016-05-25 20:13:50 +00:00
|
|
|
#ifdef ENABLE_COMMENT
|
2017-12-29 18:27:33 +00:00
|
|
|
case COMMENT:
|
|
|
|
case UNCOMMENT:
|
|
|
|
break;
|
2016-05-25 20:13:50 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
default:
|
|
|
|
statusline(ALERT, "Wrong undo adding type -- please report a bug");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
openfile->last_action = action;
|
2008-07-10 20:13:04 +00:00
|
|
|
}
|
|
|
|
|
2017-07-09 01:54:59 +00:00
|
|
|
/* Update a multiline undo item. This should be called once for each line
|
2018-04-01 09:24:44 +00:00
|
|
|
* affected by a multiple-line-altering feature. The indentation that is
|
|
|
|
* added or removed is saved separately for each line in the undo item. */
|
2017-07-09 01:54:59 +00:00
|
|
|
void update_multiline_undo(ssize_t lineno, char *indentation)
|
2016-05-25 20:13:50 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
undo *u = openfile->current_undo;
|
|
|
|
|
|
|
|
/* If there already is a group and the current line is contiguous with it,
|
|
|
|
* extend the group; otherwise, create a new group. */
|
|
|
|
if (u->grouping && u->grouping->bottom_line + 1 == lineno) {
|
|
|
|
size_t number_of_lines;
|
|
|
|
|
|
|
|
u->grouping->bottom_line++;
|
|
|
|
|
|
|
|
number_of_lines = u->grouping->bottom_line - u->grouping->top_line + 1;
|
|
|
|
u->grouping->indentations = (char **)nrealloc(u->grouping->indentations,
|
|
|
|
number_of_lines * sizeof(char *));
|
|
|
|
u->grouping->indentations[number_of_lines - 1] = mallocstrcpy(NULL,
|
|
|
|
indentation);
|
|
|
|
} else {
|
|
|
|
undo_group *born = (undo_group *)nmalloc(sizeof(undo_group));
|
|
|
|
|
|
|
|
born->next = u->grouping;
|
|
|
|
u->grouping = born;
|
|
|
|
born->top_line = lineno;
|
|
|
|
born->bottom_line = lineno;
|
|
|
|
|
|
|
|
u->grouping->indentations = (char **)nmalloc(sizeof(char *));
|
|
|
|
u->grouping->indentations[0] = mallocstrcpy(NULL, indentation);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Store the file size after the change, to be used when redoing. */
|
|
|
|
u->newsize = openfile->totsize;
|
2016-05-25 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2018-09-25 18:25:21 +00:00
|
|
|
/* Update an undo item with (among other things) the file size and
|
|
|
|
* cursor position after the given action. */
|
2008-08-03 04:48:05 +00:00
|
|
|
void update_undo(undo_type action)
|
2008-07-10 20:13:04 +00:00
|
|
|
{
|
2018-03-05 09:18:11 +00:00
|
|
|
undo *u = openfile->undotop;
|
|
|
|
char *char_buf;
|
|
|
|
int char_len;
|
2008-08-01 03:50:20 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
u->newsize = openfile->totsize;
|
2015-11-30 16:21:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
switch (u->type) {
|
2018-03-05 09:18:11 +00:00
|
|
|
case ADD:
|
|
|
|
char_buf = charalloc(MAXCHARLEN);
|
|
|
|
char_len = parse_mbchar(&openfile->current->data[u->mark_begin_x],
|
|
|
|
char_buf, NULL);
|
|
|
|
u->strdata = addstrings(u->strdata, u->strdata ? strlen(u->strdata) : 0,
|
|
|
|
char_buf, char_len);
|
2017-12-29 18:27:33 +00:00
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case ENTER:
|
|
|
|
u->strdata = mallocstrcpy(NULL, openfile->current->data);
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
break;
|
2017-12-29 18:27:33 +00:00
|
|
|
case BACK:
|
2018-03-05 09:18:11 +00:00
|
|
|
case DEL:
|
|
|
|
char_buf = charalloc(MAXCHARLEN);
|
|
|
|
char_len = parse_mbchar(&openfile->current->data[openfile->current_x],
|
|
|
|
char_buf, NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->current_x == u->begin) {
|
|
|
|
/* They deleted more: add removed character after earlier stuff. */
|
|
|
|
u->strdata = addstrings(u->strdata, strlen(u->strdata), char_buf, char_len);
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
|
|
|
} else if (openfile->current_x == u->begin - char_len) {
|
|
|
|
/* They backspaced further: add removed character before earlier. */
|
|
|
|
u->strdata = addstrings(char_buf, char_len, u->strdata, strlen(u->strdata));
|
|
|
|
u->begin = openfile->current_x;
|
|
|
|
} else {
|
|
|
|
/* They deleted *elsewhere* on the line: start a new undo item. */
|
|
|
|
free(char_buf);
|
|
|
|
add_undo(u->type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
case JOIN:
|
|
|
|
break;
|
|
|
|
case REPLACE:
|
|
|
|
case PASTE:
|
|
|
|
u->lineno = openfile->current->lineno;
|
2018-09-25 18:25:21 +00:00
|
|
|
u->begin = openfile->current_x;
|
tweaks: reshuffle the undo types into mostly the same order everywhere
First the two that add something (ADD, ENTER), then the three that
delete something (BACK, DEL, JOIN), and then the one that changes
something (REPLACE). Then the SPLITs, CUT, PASTE, and INSERT, and
then the INDENTs and COMMENTs, when they exist.
2018-03-05 09:05:07 +00:00
|
|
|
break;
|
|
|
|
#ifdef ENABLE_WRAPPING
|
|
|
|
case SPLIT_BEGIN:
|
|
|
|
case SPLIT_END:
|
|
|
|
break;
|
|
|
|
#endif
|
2018-10-24 02:25:22 +00:00
|
|
|
case ZAP:
|
2017-12-29 18:27:33 +00:00
|
|
|
case CUT_TO_EOF:
|
|
|
|
case CUT:
|
|
|
|
if (!cutbuffer)
|
|
|
|
break;
|
2018-10-24 02:25:22 +00:00
|
|
|
if (u->type == ZAP)
|
|
|
|
u->cutbuffer = cutbuffer;
|
|
|
|
else {
|
|
|
|
free_filestruct(u->cutbuffer);
|
|
|
|
u->cutbuffer = copy_filestruct(cutbuffer);
|
|
|
|
}
|
2018-12-27 21:56:30 +00:00
|
|
|
if (u->xflags & MARK_WAS_SET) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the "marking" operation was from right-->left or
|
|
|
|
* bottom-->top, then swap the mark points. */
|
|
|
|
if ((u->lineno == u->mark_begin_lineno && u->begin < u->mark_begin_x)
|
|
|
|
|| u->lineno < u->mark_begin_lineno) {
|
|
|
|
ssize_t line = u->lineno;
|
|
|
|
size_t x_loc = u->begin;
|
|
|
|
|
|
|
|
u->begin = u->mark_begin_x;
|
|
|
|
u->mark_begin_x = x_loc;
|
|
|
|
|
|
|
|
u->lineno = u->mark_begin_lineno;
|
|
|
|
u->mark_begin_lineno = line;
|
|
|
|
} else
|
2018-12-27 21:56:30 +00:00
|
|
|
u->xflags |= WAS_MARKED_FORWARD;
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
|
|
|
/* Compute the end of the cut for the undo, using our copy. */
|
|
|
|
u->cutbottom = u->cutbuffer;
|
|
|
|
while (u->cutbottom->next != NULL)
|
|
|
|
u->cutbottom = u->cutbottom->next;
|
|
|
|
u->lineno = u->mark_begin_lineno + u->cutbottom->lineno -
|
|
|
|
u->cutbuffer->lineno;
|
|
|
|
if (ISSET(CUT_FROM_CURSOR) || u->type == CUT_TO_EOF) {
|
|
|
|
u->begin = strlen(u->cutbottom->data);
|
|
|
|
if (u->lineno == u->mark_begin_lineno)
|
|
|
|
u->begin += u->mark_begin_x;
|
|
|
|
} else if (openfile->current == openfile->filebot &&
|
|
|
|
ISSET(NO_NEWLINES))
|
|
|
|
u->begin = strlen(u->cutbottom->data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case INSERT:
|
|
|
|
u->mark_begin_lineno = openfile->current->lineno;
|
|
|
|
u->mark_begin_x = openfile->current_x;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_BEGIN:
|
2018-08-16 13:59:30 +00:00
|
|
|
break;
|
2018-05-17 10:10:06 +00:00
|
|
|
case COUPLE_END:
|
2018-08-16 13:59:30 +00:00
|
|
|
u->lineno = openfile->current->lineno;
|
|
|
|
u->begin = openfile->current_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
statusline(ALERT, "Wrong undo update type -- please report a bug");
|
|
|
|
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
|
2006-05-22 02:08:49 +00:00
|
|
|
/* Unset the prepend_wrap flag. We need to do this as soon as we do
|
2005-12-08 07:09:08 +00:00
|
|
|
* something other than type text. */
|
2005-07-24 19:57:51 +00:00
|
|
|
void wrap_reset(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
prepend_wrap = FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2014-06-09 10:01:54 +00:00
|
|
|
/* Try wrapping the given line. Return TRUE if wrapped, FALSE otherwise. */
|
|
|
|
bool do_wrap(filestruct *line)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t line_len = strlen(line->data);
|
|
|
|
/* The length of the line we wrap. */
|
|
|
|
ssize_t wrap_loc;
|
|
|
|
/* The index of line->data where we wrap. */
|
|
|
|
const char *remainder;
|
|
|
|
/* The text after the wrap point. */
|
|
|
|
size_t rest_length;
|
|
|
|
/* The length of the remainder. */
|
|
|
|
|
|
|
|
size_t old_x = openfile->current_x;
|
2018-03-17 14:52:27 +00:00
|
|
|
filestruct *old_line = openfile->current;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* There are three steps. First, we decide where to wrap. Then, we
|
|
|
|
* create the new wrap line. Finally, we clean up. */
|
|
|
|
|
|
|
|
/* Step 1, finding where to wrap. We are going to add a new line
|
|
|
|
* after a blank character. In this step, we call break_line() to
|
|
|
|
* get the location of the last blank we can break the line at, and
|
|
|
|
* set wrap_loc to the location of the character after it, so that
|
|
|
|
* the blank is preserved at the end of the line.
|
|
|
|
*
|
|
|
|
* If there is no legal wrap point, or we reach the last character
|
|
|
|
* of the line while trying to find one, we should return without
|
|
|
|
* wrapping. Note that if autoindent is turned on, we don't break
|
|
|
|
* at the end of it! */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Find the last blank where we can break the line. */
|
2018-10-22 23:01:23 +00:00
|
|
|
wrap_loc = break_line(line->data, wrap_at, FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we couldn't break the line, or we've reached the end of it, we
|
|
|
|
* don't wrap. */
|
|
|
|
if (wrap_loc == -1 || line->data[wrap_loc] == '\0')
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* Otherwise, move forward to the character just after the blank. */
|
|
|
|
wrap_loc += move_mbright(line->data + wrap_loc, 0);
|
|
|
|
|
|
|
|
/* If we've reached the end of the line, we don't wrap. */
|
|
|
|
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
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If autoindent is turned on, and we're on the character just after
|
|
|
|
* the indentation, we don't wrap. */
|
|
|
|
if (ISSET(AUTOINDENT) && wrap_loc == indent_length(line->data))
|
|
|
|
return FALSE;
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(SPLIT_BEGIN);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->current = line;
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Step 2, making the new wrap line. It will consist of indentation
|
|
|
|
* followed by the text after the wrap point, optionally followed by
|
|
|
|
* a space (if the text after the wrap point doesn't end in a blank)
|
|
|
|
* and the text of the next line, if they can fit without wrapping,
|
|
|
|
* the next line exists, and the prepend_wrap flag is set. */
|
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
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* We prepend the wrapped text to the next line, if the prepend_wrap
|
|
|
|
* flag is set, there is a next line, and prepending would not make
|
|
|
|
* the line too long. */
|
|
|
|
if (prepend_wrap && line != openfile->filebot) {
|
|
|
|
const char *tail = remainder + move_mbleft(remainder, rest_length);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Go to the end of the line. */
|
|
|
|
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. */
|
|
|
|
if (!is_blank_mbchar(tail)) {
|
2014-06-20 16:13:54 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(ADD);
|
|
|
|
#endif
|
|
|
|
line->data = charealloc(line->data, line_len + 2);
|
|
|
|
line->data[line_len] = ' ';
|
|
|
|
line->data[line_len + 1] = '\0';
|
|
|
|
remainder = line->data + wrap_loc;
|
|
|
|
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
|
|
|
|
2018-10-22 23:01:23 +00:00
|
|
|
if (rest_length + strlen(line->next->data) <= wrap_at) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Delete the LF to join the two lines. */
|
|
|
|
do_delete();
|
|
|
|
/* Delete any leading blanks from the joined-on line. */
|
|
|
|
while (is_blank_mbchar(&line->data[openfile->current_x]))
|
|
|
|
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)) {
|
2018-01-23 12:09:12 +00:00
|
|
|
size_t tail_x = move_mbleft(line->data, wrap_loc);
|
2018-01-22 10:33:19 +00:00
|
|
|
size_t typed_x = move_mbleft(line->data, old_x);
|
2017-12-02 09:06:12 +00:00
|
|
|
|
2018-01-23 12:09:12 +00:00
|
|
|
while (tail_x != typed_x && is_blank_mbchar(line->data + tail_x)) {
|
|
|
|
openfile->current_x = tail_x;
|
2017-12-29 18:27:33 +00:00
|
|
|
do_delete();
|
2018-01-23 12:09:12 +00:00
|
|
|
tail_x = move_mbleft(line->data, tail_x);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-12-02 09:06:12 +00:00
|
|
|
}
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Now split the line. */
|
|
|
|
do_enter();
|
2005-11-25 13:48:09 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (old_x < wrap_loc) {
|
|
|
|
openfile->current_x = old_x;
|
|
|
|
openfile->current = old_line;
|
|
|
|
prepend_wrap = TRUE;
|
|
|
|
} else {
|
|
|
|
openfile->current_x += (old_x - wrap_loc);
|
|
|
|
prepend_wrap = FALSE;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->placewewant = xplustabs();
|
2014-06-09 10:01:54 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
add_undo(SPLIT_END);
|
2005-07-24 19:57:51 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-29 20:00:09 +00:00
|
|
|
#endif /* ENABLE_WRAPPING */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:39:30 +00:00
|
|
|
#if defined(ENABLE_HELP) || defined(ENABLED_WRAPORJUSTIFY)
|
2005-07-25 02:33:45 +00:00
|
|
|
/* We are trying to break a chunk off line. We find the last blank such
|
2005-09-20 06:12:54 +00:00
|
|
|
* that the display length to there is at most (goal + 1). If there is
|
|
|
|
* no such blank, then we find the first blank. We then take the last
|
2005-07-25 02:33:45 +00:00
|
|
|
* blank in that group of blanks. The terminating '\0' counts as a
|
2017-02-14 19:53:25 +00:00
|
|
|
* blank, as does a '\n' if snap_at_nl is TRUE. */
|
|
|
|
ssize_t break_line(const char *line, ssize_t goal, bool snap_at_nl)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
ssize_t lastblank = -1;
|
|
|
|
/* The index of the last blank we found. */
|
|
|
|
ssize_t index = 0;
|
|
|
|
/* The index of the character we are looking at. */
|
|
|
|
size_t column = 0;
|
|
|
|
/* The column position that corresponds with index. */
|
|
|
|
int char_len = 0;
|
|
|
|
/* The length of the current character, in bytes. */
|
|
|
|
|
|
|
|
/* Find the last blank that does not overshoot the target column. */
|
2018-05-27 08:24:02 +00:00
|
|
|
while (*line != '\0' && ((ssize_t)column <= goal)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (is_blank_mbchar(line) || (snap_at_nl && *line == '\n')) {
|
|
|
|
lastblank = index;
|
|
|
|
|
|
|
|
if (*line == '\n')
|
|
|
|
break;
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
char_len = parse_mbchar(line, NULL, &column);
|
|
|
|
line += char_len;
|
|
|
|
index += char_len;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the whole line displays shorter than goal, we're done. */
|
2018-05-27 08:24:02 +00:00
|
|
|
if ((ssize_t)column <= goal)
|
2017-12-29 18:27:33 +00:00
|
|
|
return index;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-04-25 15:51:45 +00:00
|
|
|
#ifdef ENABLE_HELP
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If we're wrapping a help text and no blank was found, or was
|
|
|
|
* found only as the first character, force a line break. */
|
|
|
|
if (snap_at_nl && lastblank < 1)
|
|
|
|
return (index - char_len);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* If no blank was found within the goal width, seek one after it. */
|
|
|
|
if (lastblank < 0) {
|
|
|
|
while (*line != '\0') {
|
|
|
|
if (is_blank_mbchar(line))
|
|
|
|
lastblank = index;
|
|
|
|
else if (lastblank > 0)
|
|
|
|
return lastblank;
|
|
|
|
|
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
|
|
|
index += char_len;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return -1;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Move the pointer back to the last blank, and then step beyond it. */
|
|
|
|
line = line - index + lastblank;
|
2014-04-14 13:02:43 +00:00
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Skip any consecutive blanks after the last blank. */
|
|
|
|
while (*line != '\0' && is_blank_mbchar(line)) {
|
|
|
|
lastblank += char_len;
|
|
|
|
char_len = parse_mbchar(line, NULL, NULL);
|
|
|
|
line += char_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastblank;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2017-10-31 16:39:30 +00:00
|
|
|
#endif /* ENABLE_HELP || ENABLED_WRAPORJUSTIFY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:34:07 +00:00
|
|
|
#if !defined(NANO_TINY) || defined(ENABLE_JUSTIFY)
|
2005-07-25 02:33:45 +00:00
|
|
|
/* The "indentation" of a line is the whitespace between the quote part
|
|
|
|
* and the non-whitespace of the line. */
|
|
|
|
size_t indent_length(const char *line)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t len = 0;
|
|
|
|
char onechar[MAXCHARLEN];
|
|
|
|
int charlen;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*line != '\0') {
|
|
|
|
charlen = parse_mbchar(line, onechar, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!is_blank_mbchar(onechar))
|
|
|
|
break;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
line += charlen;
|
|
|
|
len += charlen;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return len;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 16:34:07 +00:00
|
|
|
#endif /* !NANO_TINY || ENABLE_JUSTIFY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-10-31 16:34:07 +00:00
|
|
|
#ifdef ENABLE_JUSTIFY
|
2005-07-25 02:33:45 +00:00
|
|
|
/* justify_format() replaces blanks with spaces and multiple spaces by 1
|
|
|
|
* (except it maintains up to 2 after a character in punct optionally
|
|
|
|
* followed by a character in brackets, and removes all from the end).
|
|
|
|
*
|
|
|
|
* justify_format() might make paragraph->data shorter, and change the
|
|
|
|
* actual pointer with null_at().
|
|
|
|
*
|
|
|
|
* justify_format() will not look at the first skip characters of
|
|
|
|
* paragraph. skip should be at most strlen(paragraph->data). The
|
|
|
|
* character at paragraph[skip + 1] must not be blank. */
|
|
|
|
void justify_format(filestruct *paragraph, size_t skip)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *end, *new_end, *new_paragraph_data;
|
|
|
|
size_t shift = 0;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
end = paragraph->data + skip;
|
|
|
|
new_paragraph_data = charalloc(strlen(paragraph->data) + 1);
|
|
|
|
strncpy(new_paragraph_data, paragraph->data, skip);
|
|
|
|
new_end = new_paragraph_data + skip;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*end != '\0') {
|
|
|
|
int end_len;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If this character is blank, change it to a space if
|
|
|
|
* necessary, and skip over all blanks after it. */
|
|
|
|
if (is_blank_mbchar(end)) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
2005-12-31 21:08:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*new_end = ' ';
|
|
|
|
new_end++;
|
|
|
|
end += end_len;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*end != '\0' && is_blank_mbchar(end)) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
end += end_len;
|
|
|
|
shift += end_len;
|
|
|
|
}
|
|
|
|
/* If this character is punctuation optionally followed by a
|
|
|
|
* bracket and then followed by blanks, change no more than two
|
|
|
|
* of the blanks to spaces if necessary, and skip over all
|
|
|
|
* blanks after them. */
|
|
|
|
} else if (mbstrchr(punct, end) != NULL) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
|
|
|
|
|
|
|
while (end_len > 0) {
|
|
|
|
*new_end = *end;
|
|
|
|
new_end++;
|
|
|
|
end++;
|
|
|
|
end_len--;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*end != '\0' && mbstrchr(brackets, end) != NULL) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (end_len > 0) {
|
|
|
|
*new_end = *end;
|
|
|
|
new_end++;
|
|
|
|
end++;
|
|
|
|
end_len--;
|
|
|
|
}
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*end != '\0' && is_blank_mbchar(end)) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*new_end = ' ';
|
|
|
|
new_end++;
|
|
|
|
end += end_len;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (*end != '\0' && is_blank_mbchar(end)) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*new_end = ' ';
|
|
|
|
new_end++;
|
|
|
|
end += end_len;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*end != '\0' && is_blank_mbchar(end)) {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
|
|
|
|
|
|
|
end += end_len;
|
|
|
|
shift += end_len;
|
|
|
|
}
|
|
|
|
/* If this character is neither blank nor punctuation, leave it
|
|
|
|
* unchanged. */
|
|
|
|
} else {
|
|
|
|
end_len = parse_mbchar(end, NULL, NULL);
|
|
|
|
|
|
|
|
while (end_len > 0) {
|
|
|
|
*new_end = *end;
|
|
|
|
new_end++;
|
|
|
|
end++;
|
|
|
|
end_len--;
|
|
|
|
}
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*new_end = *end;
|
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. */
|
|
|
|
while (new_end > new_paragraph_data + skip && *(new_end - 1) == ' ') {
|
|
|
|
new_end--;
|
|
|
|
shift++;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (shift > 0) {
|
|
|
|
null_at(&new_paragraph_data, new_end - new_paragraph_data);
|
|
|
|
free(paragraph->data);
|
|
|
|
paragraph->data = new_paragraph_data;
|
|
|
|
} else
|
|
|
|
free(new_paragraph_data);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-07-25 02:33:45 +00:00
|
|
|
/* The "quote part" of a line is the largest initial substring matching
|
|
|
|
* the quote string. This function returns the length of the quote part
|
2017-02-21 22:04:39 +00:00
|
|
|
* of the given line. */
|
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;
|
|
|
|
/* matches.rm_so should be 0, since the quote string should start
|
|
|
|
* with the caret ^. */
|
|
|
|
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). */
|
2018-05-26 10:27:48 +00:00
|
|
|
bool begpar(const filestruct *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. */
|
2018-05-18 10:48:45 +00:00
|
|
|
if (line == openfile->fileage)
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-26 10:27:48 +00:00
|
|
|
/* If recursion is going too deep, just say it's not a BOP. */
|
|
|
|
if (depth > RECURSION_LIMIT)
|
|
|
|
return FALSE;
|
|
|
|
|
2018-05-18 10:48:45 +00:00
|
|
|
quote_len = quote_length(line->data);
|
|
|
|
indent_len = indent_length(line->data + quote_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If this line contains no text, it is not a BOP. */
|
2018-05-18 10:48:45 +00:00
|
|
|
if (line->data[quote_len + indent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the quote part of the preceding line differs, this is a BOP. */
|
2018-05-21 19:29:21 +00:00
|
|
|
if (quote_len != quote_length(line->prev->data) ||
|
|
|
|
strncmp(line->data, line->prev->data, quote_len) != 0)
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 19:10:51 +00:00
|
|
|
prev_dent_len = indent_length(line->prev->data + quote_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the preceding line contains no text, this is a BOP. */
|
2018-05-21 19:10:51 +00:00
|
|
|
if (line->prev->data[quote_len + prev_dent_len] == '\0')
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-05-21 08:19:40 +00:00
|
|
|
/* If the indentation of the preceding line equals the indentation
|
|
|
|
* of this line, this is not a BOP. */
|
2018-05-21 19:19:47 +00:00
|
|
|
if (prev_dent_len == indent_len && strncmp(line->prev->data + quote_len,
|
|
|
|
line->data + quote_len, indent_len) == 0)
|
2018-05-21 08:19:40 +00:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* Otherwise, this is a BOP if the preceding line is not. */
|
2018-05-26 10:27:48 +00:00
|
|
|
return !begpar(line->prev, depth + 1);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-19 12:49:35 +00:00
|
|
|
/* Return TRUE when the given line is part of a paragraph: when it
|
|
|
|
* contains something more than quoting and leading whitespace. */
|
2018-05-18 10:48:45 +00:00
|
|
|
bool inpar(const filestruct *const line)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2018-05-20 18:56:36 +00:00
|
|
|
size_t quote_len = quote_length(line->data);
|
2018-11-19 12:49:35 +00:00
|
|
|
size_t indent_len = indent_length(line->data + quote_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-19 12:49:35 +00:00
|
|
|
return (line->data[quote_len + indent_len] != '\0');
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-26 09:24:01 +00:00
|
|
|
/* Determine the beginning, length, and quoting of the first found paragraph.
|
2018-11-22 19:00:18 +00:00
|
|
|
* Return TRUE if we found a paragraph, and FALSE otherwise. Furthermore,
|
2018-11-26 09:24:01 +00:00
|
|
|
* return in firstline the first line of the paragraph, in *quotelen the
|
|
|
|
* length of the quoting, and in *parlen the length of the paragraph. */
|
|
|
|
bool find_paragraph(filestruct **firstline,
|
2018-11-22 19:54:22 +00:00
|
|
|
size_t *const quotelen, size_t *const parlen)
|
2005-07-25 02:33:45 +00:00
|
|
|
{
|
2018-11-22 19:54:22 +00:00
|
|
|
filestruct *line = *firstline;
|
2018-08-31 17:42:41 +00:00
|
|
|
/* The line of the current paragraph we're searching in. */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-11-25 12:04:35 +00:00
|
|
|
/* When not currently in a paragraph, move forward to a line that is. */
|
|
|
|
while (!inpar(line) && line->next != NULL)
|
|
|
|
line = line->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-11-22 19:54:22 +00:00
|
|
|
*firstline = line;
|
2018-09-11 06:55:00 +00:00
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* Move down to the last line of the paragraph. */
|
|
|
|
do_para_end(&line);
|
2018-11-25 12:04:35 +00:00
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* When not in a paragraph now, there aren't any paragraphs left. */
|
|
|
|
if (!inpar(line))
|
2018-09-11 06:55:00 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2018-11-25 18:24:08 +00:00
|
|
|
/* We found a paragraph; determine length of quoting and number of lines. */
|
2018-11-22 19:54:22 +00:00
|
|
|
*quotelen = quote_length((*firstline)->data);
|
2018-11-25 18:24:08 +00:00
|
|
|
*parlen = line->lineno - (*firstline)->lineno + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
return TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2018-12-26 14:03:37 +00:00
|
|
|
/* Wrap all lines of the paragraph (that starts at *line, consists of
|
|
|
|
* par_len lines, and has quote_len bytes of quoting) so they all fit
|
2018-12-26 13:11:46 +00:00
|
|
|
* within the wrap_at target width. */
|
2018-12-26 14:03:37 +00:00
|
|
|
void justify_paragraph(filestruct **line, size_t quote_len, size_t par_len)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2018-08-25 22:53:17 +00:00
|
|
|
filestruct *sampleline;
|
|
|
|
/* The line from which the indentation is copied -- either
|
|
|
|
* the first and only or the second line of the paragraph. */
|
|
|
|
size_t lead_len;
|
|
|
|
/* Length of the quote part plus the indentation part. */
|
|
|
|
ssize_t break_pos;
|
2018-12-26 13:11:46 +00:00
|
|
|
/* The x-coordinate where the current line is to be broken. */
|
2018-08-25 22:53:17 +00:00
|
|
|
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. */
|
2018-12-26 13:37:36 +00:00
|
|
|
sampleline = (par_len == 1 ? *line : (*line)->next);
|
2018-08-25 22:53:17 +00:00
|
|
|
|
|
|
|
/* Copy the leading part (quoting + indentation) of the sample line. */
|
|
|
|
lead_len = quote_len + indent_length(sampleline->data + quote_len);
|
|
|
|
lead_string = mallocstrncpy(NULL, sampleline->data, lead_len + 1);
|
|
|
|
lead_string[lead_len] = '\0';
|
|
|
|
|
2018-12-26 14:03:37 +00:00
|
|
|
/* Now first tack all the lines of the paragraph together, skipping
|
2018-08-25 22:53:17 +00:00
|
|
|
* the quoting and indentation on all lines after the first. */
|
|
|
|
while (par_len > 1) {
|
2018-12-26 13:56:29 +00:00
|
|
|
filestruct *next_line = (*line)->next;
|
|
|
|
size_t line_len = strlen((*line)->data);
|
2018-08-25 22:53:17 +00:00
|
|
|
size_t next_line_len = strlen(next_line->data);
|
|
|
|
|
|
|
|
lead_len = quote_len + indent_length(next_line->data + quote_len);
|
|
|
|
|
|
|
|
/* 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-05-27 15:45:51 +00:00
|
|
|
line_len + next_line_len - lead_len + 1);
|
2018-12-26 13:56:29 +00:00
|
|
|
strcat((*line)->data, next_line->data + lead_len);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
unlink_node(next_line);
|
|
|
|
par_len--;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Change all blank characters to spaces and remove excess spaces. */
|
2018-12-26 13:56:29 +00:00
|
|
|
justify_format(*line, quote_len + indent_length((*line)->data + quote_len));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Now break this long line into pieces that each fit with wrap_at columns. */
|
2018-12-26 13:56:29 +00:00
|
|
|
while (strlenpt((*line)->data) > wrap_at) {
|
|
|
|
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,
|
|
|
|
wrap_at - strnlenpt((*line)->data, lead_len), FALSE);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* If we can't break the line, or don't need to, we're done. */
|
|
|
|
if (break_pos == -1 || break_pos + lead_len == line_len)
|
|
|
|
break;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* Adjust the breaking position for the leading part and
|
|
|
|
* move it beyond the found whitespace character. */
|
|
|
|
break_pos += lead_len + 1;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-12-26 13:11:46 +00:00
|
|
|
/* Insert a new line after the current one, and copy the leading part
|
|
|
|
* plus the text after the breaking point into it. */
|
2018-12-26 13:56:29 +00:00
|
|
|
splice_node(*line, make_new_node(*line));
|
|
|
|
(*line)->next->data = charalloc(lead_len + line_len - break_pos + 1);
|
|
|
|
strncpy((*line)->next->data, lead_string, lead_len);
|
|
|
|
strcpy((*line)->next->data + lead_len, (*line)->data + break_pos);
|
2018-05-27 15:11:18 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
/* When requested, snip all trailing blanks. */
|
|
|
|
if (ISSET(TRIM_BLANKS)) {
|
|
|
|
while (break_pos > 0 &&
|
2018-12-26 13:56:29 +00:00
|
|
|
is_blank_mbchar(&(*line)->data[break_pos - 1]))
|
2018-08-25 22:53:17 +00:00
|
|
|
break_pos--;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-12-30 17:00:28 +00:00
|
|
|
/* Now actually break the current line, and go to the next. */
|
2018-12-26 13:56:29 +00:00
|
|
|
null_at(&(*line)->data, break_pos);
|
|
|
|
*line = (*line)->next;
|
2018-08-25 22:53:17 +00:00
|
|
|
}
|
2017-11-22 18:26:47 +00:00
|
|
|
|
2018-08-25 22:53:17 +00:00
|
|
|
free(lead_string);
|
2005-07-24 19:57:51 +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
|
|
|
}
|
|
|
|
|
|
|
|
/* Justify the current paragraph, and justify the entire file when
|
|
|
|
* full_justify is TRUE. */
|
|
|
|
void do_justify(bool full_justify)
|
|
|
|
{
|
2018-08-25 22:53:17 +00:00
|
|
|
size_t quote_len;
|
|
|
|
/* Length of the quote part of the current paragraph. */
|
|
|
|
size_t par_len;
|
|
|
|
/* Number of lines in the current paragraph. */
|
2018-08-25 22:44:20 +00:00
|
|
|
filestruct *first_par_line = NULL;
|
2018-09-10 20:12:07 +00:00
|
|
|
/* Will be the first line of the justified paragraph(s), if any. */
|
2018-08-25 22:44:20 +00:00
|
|
|
filestruct *last_par_line = NULL;
|
|
|
|
/* Will be the line after the last line of the justified
|
2018-09-10 20:12:07 +00:00
|
|
|
* paragraph(s), if any. */
|
2018-11-26 09:24:01 +00:00
|
|
|
size_t x_for_last;
|
|
|
|
/* The x position until where to extract the last paragraph line. */
|
2018-08-25 22:44:20 +00:00
|
|
|
|
2018-09-01 00:45:27 +00:00
|
|
|
filestruct *was_cutbuffer = cutbuffer;
|
|
|
|
/* The old cutbuffer, so we can justify in the current cutbuffer. */
|
|
|
|
filestruct *was_cutbottom = cutbottom;
|
|
|
|
/* The old cutbottom, so we can justify in the current cutbuffer. */
|
|
|
|
size_t jus_len;
|
|
|
|
/* The number of lines we're storing in the current cutbuffer. */
|
|
|
|
filestruct *jusline;
|
|
|
|
/* 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;
|
|
|
|
#endif
|
|
|
|
|
2018-11-25 14:15:02 +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. */
|
2018-11-25 14:23:24 +00:00
|
|
|
if (full_justify)
|
2018-08-25 22:44:20 +00:00
|
|
|
openfile->current = openfile->fileage;
|
2018-11-25 14:23:24 +00:00
|
|
|
else if (inpar(openfile->current) && !begpar(openfile->current, 0))
|
2018-11-25 14:15:02 +00:00
|
|
|
do_para_begin(&openfile->current);
|
2018-08-25 22:44:20 +00:00
|
|
|
|
2018-11-26 08:25:32 +00:00
|
|
|
/* 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. */
|
2018-11-26 09:24:01 +00:00
|
|
|
if (!find_paragraph(&openfile->current, "e_len, &par_len)) {
|
2018-08-31 17:42:41 +00:00
|
|
|
openfile->current_x = strlen(openfile->filebot->data);
|
2018-08-25 22:53:17 +00:00
|
|
|
refresh_needed = TRUE;
|
|
|
|
return;
|
|
|
|
}
|
2018-08-25 22:44:20 +00:00
|
|
|
|
2018-11-25 18:55:20 +00:00
|
|
|
/* We cannot (yet) justify a marked region, so turn the mark off. */
|
|
|
|
openfile->mark = NULL;
|
|
|
|
|
2018-09-01 00:45:27 +00:00
|
|
|
/* Prepare to put the text we want to justify in the cutbuffer. */
|
|
|
|
cutbuffer = NULL;
|
|
|
|
cutbottom = NULL;
|
|
|
|
|
2018-11-26 08:25:32 +00:00
|
|
|
/* Start out at the first line of the paragraph. */
|
2018-08-25 22:53:17 +00:00
|
|
|
first_par_line = openfile->current;
|
2018-09-01 00:45:27 +00:00
|
|
|
last_par_line = openfile->current;
|
|
|
|
|
2018-11-26 08:25:32 +00:00
|
|
|
/* Set the number of lines to be pulled into the cutbuffer. */
|
2018-09-01 00:45:27 +00:00
|
|
|
if (full_justify) {
|
2018-11-26 09:24:01 +00:00
|
|
|
jus_len = openfile->filebot->lineno;
|
2018-09-01 00:45:27 +00:00
|
|
|
} else
|
|
|
|
jus_len = par_len;
|
2018-08-25 22:44:20 +00:00
|
|
|
|
2018-11-26 09:24:01 +00:00
|
|
|
/* Move down to the last line to be extracted. */
|
|
|
|
for (; jus_len > 1; jus_len--)
|
2018-09-01 00:45:27 +00:00
|
|
|
last_par_line = last_par_line->next;
|
2018-11-26 09:24:01 +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;
|
|
|
|
x_for_last = 0;
|
|
|
|
} else
|
|
|
|
x_for_last = strlen(last_par_line->data);
|
2018-08-27 02:46:44 +00:00
|
|
|
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
add_undo(COUPLE_BEGIN);
|
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("justify"));
|
|
|
|
|
|
|
|
/* Store the original cursor position, in case we unjustify. */
|
|
|
|
openfile->undotop->lineno = was_lineno;
|
|
|
|
openfile->undotop->begin = was_current_x;
|
|
|
|
|
|
|
|
add_undo(CUT);
|
|
|
|
#endif
|
|
|
|
/* Do the equivalent of a marked cut. */
|
2018-09-01 00:45:27 +00:00
|
|
|
extract_buffer(&cutbuffer, &cutbottom, first_par_line, 0, last_par_line,
|
2018-11-26 09:24:01 +00:00
|
|
|
x_for_last);
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(CUT);
|
|
|
|
#endif
|
2018-08-27 02:46:44 +00:00
|
|
|
|
2018-09-01 00:45:27 +00:00
|
|
|
/* Prepare to justify the text we just put in the cutbuffer. */
|
|
|
|
jusline = cutbuffer;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-09-01 00:45:27 +00:00
|
|
|
/* Justify the current paragraph. */
|
|
|
|
justify_paragraph(&jusline, quote_len, par_len);
|
2018-08-28 15:56:52 +00:00
|
|
|
|
2018-11-26 08:25:32 +00:00
|
|
|
/* When justifying the entire buffer, find and justify all paragraphs. */
|
2018-09-01 00:45:27 +00:00
|
|
|
if (full_justify) {
|
2018-11-26 09:24:01 +00:00
|
|
|
while (find_paragraph(&jusline, "e_len, &par_len)) {
|
2018-09-01 00:45:27 +00:00
|
|
|
justify_paragraph(&jusline, quote_len, par_len);
|
2018-11-26 09:24:01 +00:00
|
|
|
|
|
|
|
if (jusline->next == NULL)
|
|
|
|
break;
|
|
|
|
}
|
2018-09-01 00:45:27 +00:00
|
|
|
}
|
2018-08-31 17:42:41 +00:00
|
|
|
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
add_undo(PASTE);
|
|
|
|
#endif
|
2018-11-26 08:25:32 +00:00
|
|
|
/* Do the equivalent of a paste of the justified text. */
|
2018-09-01 00:45:27 +00:00
|
|
|
ingraft_buffer(cutbuffer);
|
2018-09-07 17:01:43 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(PASTE);
|
|
|
|
|
|
|
|
add_undo(COUPLE_END);
|
|
|
|
openfile->undotop->strdata = mallocstrcpy(NULL, _("justify"));
|
|
|
|
#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;
|
|
|
|
cutbottom = was_cutbottom;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-10 20:12:07 +00:00
|
|
|
/* Show what we justified on the status bar. */
|
|
|
|
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;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Justify the current paragraph. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_justify_void(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_justify(FALSE);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Justify the entire file. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_full_justify(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
do_justify(TRUE);
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 16:34:07 +00:00
|
|
|
#endif /* ENABLE_JUSTIFY */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
#if defined(ENABLE_SPELLER) || defined (ENABLE_COLOR)
|
|
|
|
/* Set up an argument list for executing the given command. */
|
|
|
|
void construct_argument_list(char ***arguments, char *command, char *filename)
|
|
|
|
{
|
|
|
|
char *copy_of_command = mallocstrcpy(NULL, command);
|
2018-04-22 10:00:26 +00:00
|
|
|
char *element = strtok(copy_of_command, " ");
|
2018-04-22 10:05:19 +00:00
|
|
|
int count = 2;
|
2018-04-22 08:29:06 +00:00
|
|
|
|
2018-04-22 10:00:26 +00:00
|
|
|
while (element != NULL) {
|
2018-04-22 10:05:19 +00:00
|
|
|
*arguments = (char **)nrealloc(*arguments, ++count * sizeof(char *));
|
|
|
|
(*arguments)[count - 3] = element;
|
2018-04-22 10:00:26 +00:00
|
|
|
element = strtok(NULL, " ");
|
2018-04-22 08:29:06 +00:00
|
|
|
}
|
|
|
|
|
2018-04-22 10:05:19 +00:00
|
|
|
(*arguments)[count - 2] = filename;
|
|
|
|
(*arguments)[count - 1] = NULL;
|
2018-04-22 08:29:06 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-10-31 18:32:42 +00:00
|
|
|
#ifdef ENABLE_SPELLER
|
2018-04-25 09:49:25 +00:00
|
|
|
/* Let the user edit the misspelled word. Return FALSE if the user cancels. */
|
|
|
|
bool fix_spello(const char *word)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *save_search;
|
|
|
|
size_t firstcolumn_save = openfile->firstcolumn;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
|
|
|
filestruct *edittop_save = openfile->edittop;
|
|
|
|
filestruct *current_save = openfile->current;
|
|
|
|
/* 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. */
|
|
|
|
filestruct *top, *bot;
|
|
|
|
size_t top_x, bot_x;
|
2005-07-25 02:33:45 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Save the current search string, then set it to the misspelled word. */
|
|
|
|
save_search = last_search;
|
|
|
|
last_search = mallocstrcpy(NULL, word);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the mark is on, start at the beginning of the marked region. */
|
|
|
|
if (openfile->mark) {
|
|
|
|
mark_order((const filestruct **)&top, &top_x,
|
|
|
|
(const filestruct **)&bot, &bot_x, &right_side_up);
|
|
|
|
/* 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. */
|
|
|
|
{
|
|
|
|
openfile->current = openfile->fileage;
|
|
|
|
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();
|
|
|
|
light_to_col = light_from_col + strlenpt(word);
|
2017-05-12 16:07:32 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
filestruct *saved_mark = openfile->mark;
|
|
|
|
openfile->mark = NULL;
|
2017-05-12 16:07:32 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
2018-09-25 00:11:10 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Let the user supply a correctly spelled alternative. */
|
|
|
|
proceed = (do_prompt(FALSE, FALSE, MSPELL, word, NULL,
|
|
|
|
edit_refresh, _("Edit a replacement")) != -1);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-29 16:14:43 +00:00
|
|
|
spotlighted = FALSE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-09-25 00:11:10 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
openfile->mark = saved_mark;
|
|
|
|
#endif
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If a replacement was given, go through all occurrences. */
|
|
|
|
if (proceed && strcmp(word, answer) != 0) {
|
|
|
|
do_replace_loop(word, TRUE, current_save, ¤t_x_save);
|
2016-05-04 17:31:59 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* TRANSLATORS: Shown after fixing misspellings in one word. */
|
|
|
|
statusbar(_("Next word..."));
|
|
|
|
napms(400);
|
|
|
|
}
|
2016-03-31 11:27:16 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark) {
|
|
|
|
/* Restore the (compensated) end points of the marked region. */
|
|
|
|
if (right_side_up) {
|
|
|
|
openfile->current = openfile->mark;
|
|
|
|
openfile->current_x = openfile->mark_x;
|
|
|
|
openfile->mark = top;
|
|
|
|
openfile->mark_x = top_x;
|
|
|
|
} else {
|
|
|
|
openfile->current = top;
|
|
|
|
openfile->current_x = top_x;
|
|
|
|
}
|
|
|
|
} else
|
2016-05-04 15:49:37 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
{
|
|
|
|
/* Restore the (compensated) cursor position. */
|
|
|
|
openfile->current = current_save;
|
|
|
|
openfile->current_x = current_x_save;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the string that was last searched for. */
|
|
|
|
free(last_search);
|
|
|
|
last_search = save_search;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the viewport to where it was. */
|
|
|
|
openfile->edittop = edittop_save;
|
|
|
|
openfile->firstcolumn = firstcolumn_save;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return proceed;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Internal (integrated) spell checking using the spell program,
|
|
|
|
* filtered through the sort and uniq programs. Return NULL for normal
|
|
|
|
* termination, and the error string otherwise. */
|
2005-07-25 02:33:45 +00:00
|
|
|
const char *do_int_speller(const char *tempfile_name)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *read_buff, *read_buff_ptr, *read_buff_word;
|
|
|
|
size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
|
|
|
|
int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1;
|
|
|
|
pid_t pid_spell, pid_sort, pid_uniq;
|
|
|
|
int spell_status, sort_status, uniq_status;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Create all three pipes up front. */
|
|
|
|
if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1)
|
|
|
|
return _("Could not create pipe");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Creating misspelled word list, please wait..."));
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run spell in. */
|
|
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future spell process). */
|
|
|
|
close(spell_fd[0]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Replace the standard input with the temp file. */
|
|
|
|
if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-07-14 19:27:50 +00:00
|
|
|
if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) {
|
|
|
|
close(tempfile_fd);
|
2017-12-29 18:27:33 +00:00
|
|
|
goto close_pipes_and_exit;
|
2018-07-14 19:27:50 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(tempfile_fd);
|
|
|
|
|
|
|
|
/* Send spell's standard output to the pipe. */
|
|
|
|
if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the spell program; we are using $PATH. */
|
|
|
|
execlp("spell", "spell", NULL);
|
|
|
|
|
|
|
|
/* This should not be reached if spell is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent continues here. */
|
2005-07-25 02:33:45 +00:00
|
|
|
close(spell_fd[1]);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run sort in. */
|
|
|
|
if ((pid_sort = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future sort process). Replace the
|
|
|
|
* standard input with the standard output of the old pipe. */
|
|
|
|
if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Send sort's standard output to the new pipe. */
|
|
|
|
if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[1]);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the sort program. Use -f to ignore case. */
|
|
|
|
execlp("sort", "sort", "-f", NULL);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* This should not be reached if sort is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(spell_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(sort_fd[1]);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* A new process to run uniq in. */
|
|
|
|
if ((pid_uniq = fork()) == 0) {
|
|
|
|
/* Child continues (i.e. future uniq process). Replace the
|
|
|
|
* standard input with the standard output of the old pipe. */
|
|
|
|
if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[0]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Send uniq's standard output to the new pipe. */
|
|
|
|
if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
goto close_pipes_and_exit;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Start the uniq program; we are using PATH. */
|
|
|
|
execlp("uniq", "uniq", NULL);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* This should not be reached if uniq is found. */
|
|
|
|
exit(1);
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
close(sort_fd[0]);
|
2005-07-25 02:33:45 +00:00
|
|
|
close(uniq_fd[1]);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The child process was not forked successfully. */
|
|
|
|
if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) {
|
|
|
|
close(uniq_fd[0]);
|
|
|
|
return _("Could not fork");
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the system pipe buffer size. */
|
|
|
|
if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) {
|
|
|
|
close(uniq_fd[0]);
|
|
|
|
return _("Could not get size of pipe buffer");
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Read in the returned spelling errors. */
|
|
|
|
read_buff_read = 0;
|
|
|
|
read_buff_size = pipe_buff_size + 1;
|
|
|
|
read_buff = read_buff_ptr = charalloc(read_buff_size);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
|
|
|
|
read_buff_read += bytesread;
|
|
|
|
read_buff_size += pipe_buff_size;
|
|
|
|
read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
|
|
|
|
read_buff_ptr += read_buff_read;
|
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*read_buff_ptr = '\0';
|
2005-07-25 02:33:45 +00:00
|
|
|
close(uniq_fd[0]);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-04-25 10:27:01 +00:00
|
|
|
/* Do any replacements case sensitive, forward, and without regexes. */
|
|
|
|
SET(CASE_SENSITIVE);
|
|
|
|
UNSET(BACKWARDS_SEARCH);
|
|
|
|
UNSET(USE_REGEXP);
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
read_buff_word = read_buff_ptr = read_buff;
|
|
|
|
|
2018-04-25 10:27:01 +00:00
|
|
|
/* Process each of the misspelled words. */
|
2017-12-29 18:27:33 +00:00
|
|
|
while (*read_buff_ptr != '\0') {
|
|
|
|
if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
|
|
|
|
*read_buff_ptr = '\0';
|
|
|
|
if (read_buff_word != read_buff_ptr) {
|
2018-04-25 09:49:25 +00:00
|
|
|
if (!fix_spello(read_buff_word)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
read_buff_word = read_buff_ptr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
read_buff_word = read_buff_ptr + 1;
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
read_buff_ptr++;
|
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'. */
|
|
|
|
if (read_buff_word != read_buff_ptr)
|
2018-04-25 09:49:25 +00:00
|
|
|
fix_spello(read_buff_word);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(read_buff);
|
2018-04-13 08:31:18 +00:00
|
|
|
tidy_up_after_search();
|
2017-12-29 18:27:33 +00:00
|
|
|
refresh_needed = TRUE;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Process the end of the three processes. */
|
|
|
|
waitpid(pid_spell, &spell_status, 0);
|
|
|
|
waitpid(pid_sort, &sort_status, 0);
|
|
|
|
waitpid(pid_uniq, &uniq_status, 0);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status))
|
|
|
|
return _("Error invoking \"spell\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status))
|
|
|
|
return _("Error invoking \"sort -f\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status))
|
|
|
|
return _("Error invoking \"uniq\"");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* When all went okay. */
|
|
|
|
return NULL;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-07-25 02:33:45 +00:00
|
|
|
close_pipes_and_exit:
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Don't leak any handles. */
|
|
|
|
close(spell_fd[0]);
|
|
|
|
close(spell_fd[1]);
|
|
|
|
close(sort_fd[0]);
|
|
|
|
close(sort_fd[1]);
|
|
|
|
close(uniq_fd[0]);
|
|
|
|
close(uniq_fd[1]);
|
|
|
|
exit(1);
|
2005-07-25 02:33:45 +00:00
|
|
|
}
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* External (alternate) spell checking. Return NULL for normal
|
|
|
|
* termination, and the error string otherwise. */
|
2005-07-25 02:33:45 +00:00
|
|
|
const char *do_alt_speller(char *tempfile_name)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
int alt_spell_status;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
|
|
|
size_t pww_save = openfile->placewewant;
|
|
|
|
ssize_t lineno_save = openfile->current->lineno;
|
|
|
|
bool was_at_eol = (openfile->current->data[openfile->current_x] == '\0');
|
|
|
|
struct stat spellfileinfo;
|
|
|
|
time_t timestamp;
|
|
|
|
pid_t pid_spell;
|
|
|
|
static char **spellargs = NULL;
|
|
|
|
|
|
|
|
/* Get the timestamp and the size of the temporary file. */
|
|
|
|
stat(tempfile_name, &spellfileinfo);
|
|
|
|
timestamp = spellfileinfo.st_mtime;
|
|
|
|
|
|
|
|
/* If the number of bytes to check is zero, get out. */
|
|
|
|
if (spellfileinfo.st_size == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Exit from curses mode. */
|
|
|
|
endwin();
|
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
construct_argument_list(&spellargs, alt_speller, tempfile_name);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Fork a child process and run the alternate spell program in it. */
|
|
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
execvp(spellargs[0], spellargs);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Terminate the child process if no alternate speller is found. */
|
|
|
|
exit(1);
|
|
|
|
} else if (pid_spell < 0)
|
|
|
|
return _("Could not fork");
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Wait for the alternate spell checker to finish. */
|
|
|
|
wait(&alt_spell_status);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Reenter curses mode. */
|
|
|
|
doupdate();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Restore the terminal to its previous state. */
|
|
|
|
terminal_init();
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0)
|
|
|
|
return invocation_error(alt_speller);
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Stat the temporary file again. */
|
2018-08-08 17:44:40 +00:00
|
|
|
stat(tempfile_name, &spellfileinfo);
|
2018-08-08 17:53:05 +00:00
|
|
|
|
|
|
|
/* Use the spell-checked file only when it changed. */
|
2018-08-08 17:44:40 +00:00
|
|
|
if (spellfileinfo.st_mtime != timestamp) {
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Replace the marked text (or entire text) with the corrected text. */
|
|
|
|
if (openfile->mark) {
|
|
|
|
filestruct *top, *bot;
|
|
|
|
size_t top_x, bot_x;
|
|
|
|
bool right_side_up;
|
|
|
|
ssize_t was_mark_lineno = openfile->mark->lineno;
|
2017-02-28 20:57:53 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
mark_order((const filestruct **)&top, &top_x,
|
|
|
|
(const filestruct **)&bot, &bot_x, &right_side_up);
|
2017-05-12 16:07:32 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
replace_marked_buffer(tempfile_name);
|
2016-12-10 12:20:06 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Adjust the end point of the marked region for any change in
|
|
|
|
* length of the region's last line. */
|
|
|
|
if (right_side_up)
|
|
|
|
current_x_save = openfile->current_x;
|
|
|
|
else
|
|
|
|
openfile->mark_x = openfile->current_x;
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-08-08 17:53:05 +00:00
|
|
|
/* Restore the mark. */
|
|
|
|
openfile->mark = fsfromline(was_mark_lineno);
|
|
|
|
} else
|
2017-02-10 01:04:14 +00:00
|
|
|
#endif
|
2018-08-08 17:53:05 +00:00
|
|
|
replace_buffer(tempfile_name);
|
|
|
|
|
|
|
|
/* Go back to the old position. */
|
|
|
|
goto_line_posx(lineno_save, current_x_save);
|
|
|
|
if (was_at_eol || openfile->current_x > strlen(openfile->current->data))
|
|
|
|
openfile->current_x = strlen(openfile->current->data);
|
2018-08-16 13:59:30 +00:00
|
|
|
#ifndef NANO_TINY
|
|
|
|
update_undo(COUPLE_END);
|
|
|
|
#endif
|
2018-08-08 17:53:05 +00:00
|
|
|
openfile->placewewant = pww_save;
|
|
|
|
adjust_viewport(STATIONARY);
|
2018-08-08 17:44:40 +00:00
|
|
|
}
|
2017-03-01 08:56:38 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return NULL;
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Spell check the current file. If an alternate spell checker is
|
|
|
|
* specified, use it. Otherwise, use the internal spell checker. */
|
2005-07-25 02:33:45 +00:00
|
|
|
void do_spell(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
bool status;
|
|
|
|
FILE *temp_file;
|
|
|
|
char *temp;
|
2018-04-25 09:33:22 +00:00
|
|
|
unsigned stash[sizeof(flags) / sizeof(flags[0])];
|
|
|
|
/* A storage place for the current flag settings. */
|
2018-09-23 12:51:40 +00:00
|
|
|
const char *result_msg;
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (ISSET(RESTRICTED)) {
|
|
|
|
show_restricted_warning();
|
|
|
|
return;
|
|
|
|
}
|
2010-11-12 06:22:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
temp = safe_tempfile(&temp_file);
|
2016-01-02 16:01:04 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (temp == NULL) {
|
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Save the settings of the global flags. */
|
|
|
|
memcpy(stash, flags, sizeof(flags));
|
|
|
|
|
|
|
|
/* Don't add an extra newline when writing out the (selected) text. */
|
|
|
|
SET(NO_NEWLINES);
|
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (openfile->mark)
|
|
|
|
status = write_marked_file(temp, temp_file, TRUE, OVERWRITE);
|
|
|
|
else
|
2005-07-25 02:33:45 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
status = write_file(temp, temp_file, TRUE, OVERWRITE, TRUE);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!status) {
|
|
|
|
statusline(ALERT, _("Error writing temp file: %s"), strerror(errno));
|
|
|
|
free(temp);
|
|
|
|
return;
|
|
|
|
}
|
2014-05-12 11:50:58 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
blank_bottombars();
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-09-23 12:51:40 +00:00
|
|
|
result_msg = (alt_speller ? do_alt_speller(temp) : do_int_speller(temp));
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
unlink(temp);
|
|
|
|
free(temp);
|
2005-07-25 02:33:45 +00:00
|
|
|
|
2018-04-25 09:33:22 +00:00
|
|
|
/* Restore the settings of the global flags. */
|
|
|
|
memcpy(flags, stash, sizeof(flags));
|
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the spell-checker printed any error messages onscreen, make
|
|
|
|
* sure that they're cleared off. */
|
|
|
|
total_refresh();
|
|
|
|
|
2018-09-23 12:51:40 +00:00
|
|
|
if (result_msg != NULL) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (errno == 0)
|
|
|
|
/* Don't display an error message of "Success". */
|
2018-09-23 12:51:40 +00:00
|
|
|
statusline(ALERT, _("Spell checking failed: %s"), result_msg);
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2018-09-23 12:51:40 +00:00
|
|
|
statusline(ALERT, _("Spell checking failed: %s: %s"), result_msg,
|
2017-12-29 18:27:33 +00:00
|
|
|
strerror(errno));
|
|
|
|
} else
|
|
|
|
statusbar(_("Finished checking spelling"));
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2017-10-31 18:32:42 +00:00
|
|
|
#endif /* ENABLE_SPELLER */
|
2005-07-24 19:57:51 +00:00
|
|
|
|
2017-11-01 18:45:33 +00:00
|
|
|
#ifdef ENABLE_COLOR
|
2016-01-04 10:05:52 +00:00
|
|
|
/* Run a linting program on the current buffer. Return NULL for normal
|
2014-02-24 10:18:15 +00:00
|
|
|
* termination, and the error string otherwise. */
|
|
|
|
void do_linter(void)
|
|
|
|
{
|
2018-04-22 08:29:06 +00:00
|
|
|
char *read_buff, *read_buff_ptr, *read_buff_word;
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
|
|
|
|
size_t parsesuccess = 0;
|
|
|
|
int lint_status, lint_fd[2];
|
|
|
|
pid_t pid_lint;
|
2018-10-21 18:47:37 +00:00
|
|
|
bool helpless = ISSET(NO_HELP);
|
2017-12-29 18:27:33 +00:00
|
|
|
static char **lintargs = NULL;
|
|
|
|
lintstruct *lints = NULL, *tmplint = NULL, *curlint = NULL;
|
2018-10-22 01:14:38 +00:00
|
|
|
time_t last_wait = 0;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
if (ISSET(RESTRICTED)) {
|
|
|
|
show_restricted_warning();
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!openfile->syntax || !openfile->syntax->linter) {
|
|
|
|
statusbar(_("No linter defined for this type of file!"));
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2018-01-09 11:23:41 +00:00
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
openfile->mark = NULL;
|
2018-01-09 11:23:41 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
|
|
|
|
|
|
|
if (openfile->modified) {
|
|
|
|
int i = do_yesno_prompt(FALSE, _("Save modified buffer before linting?"));
|
|
|
|
|
|
|
|
if (i == -1) {
|
|
|
|
statusbar(_("Cancelled"));
|
|
|
|
return;
|
|
|
|
} else if (i == 1 && (do_writeout(FALSE, FALSE) != 1))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create a pipe up front. */
|
|
|
|
if (pipe(lint_fd) == -1) {
|
|
|
|
statusbar(_("Could not create pipe"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
blank_bottombars();
|
2018-05-24 09:34:22 +00:00
|
|
|
currmenu = MLINTER;
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("Invoking linter, please wait"));
|
|
|
|
|
2018-04-22 08:29:06 +00:00
|
|
|
construct_argument_list(&lintargs, openfile->syntax->linter, openfile->filename);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Start a new process to run the linter in. */
|
|
|
|
if ((pid_lint = fork()) == 0) {
|
|
|
|
|
|
|
|
/* Child continues here (i.e. the future linting process). */
|
|
|
|
close(lint_fd[0]);
|
|
|
|
|
|
|
|
/* Send the linter's standard output + err to the pipe. */
|
|
|
|
if (dup2(lint_fd[1], STDOUT_FILENO) != STDOUT_FILENO)
|
|
|
|
exit(9);
|
|
|
|
if (dup2(lint_fd[1], STDERR_FILENO) != STDERR_FILENO)
|
|
|
|
exit(9);
|
|
|
|
|
|
|
|
close(lint_fd[1]);
|
|
|
|
|
|
|
|
/* Start the linter program; we are using $PATH. */
|
|
|
|
execvp(lintargs[0], lintargs);
|
|
|
|
|
|
|
|
/* This is only reached when the linter is not found. */
|
|
|
|
exit(9);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parent continues here. */
|
2014-07-16 08:53:16 +00:00
|
|
|
close(lint_fd[1]);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the child process was not forked successfully... */
|
|
|
|
if (pid_lint < 0) {
|
|
|
|
close(lint_fd[0]);
|
|
|
|
statusbar(_("Could not fork"));
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Get the system pipe buffer size. */
|
|
|
|
if ((pipe_buff_size = fpathconf(lint_fd[0], _PC_PIPE_BUF)) < 1) {
|
|
|
|
close(lint_fd[0]);
|
|
|
|
statusbar(_("Could not get size of pipe buffer"));
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Read in the returned syntax errors. */
|
|
|
|
read_buff_read = 0;
|
|
|
|
read_buff_size = pipe_buff_size + 1;
|
|
|
|
read_buff = read_buff_ptr = charalloc(read_buff_size);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while ((bytesread = read(lint_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
|
|
|
|
read_buff_read += bytesread;
|
|
|
|
read_buff_size += pipe_buff_size;
|
|
|
|
read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size);
|
|
|
|
read_buff_ptr += read_buff_read;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
*read_buff_ptr = '\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. */
|
|
|
|
read_buff_word = read_buff_ptr = read_buff;
|
|
|
|
|
|
|
|
while (*read_buff_ptr != '\0') {
|
|
|
|
if ((*read_buff_ptr == '\r') || (*read_buff_ptr == '\n')) {
|
|
|
|
*read_buff_ptr = '\0';
|
|
|
|
if (read_buff_word != read_buff_ptr) {
|
|
|
|
char *filename = NULL, *linestr = NULL, *maybecol = NULL;
|
|
|
|
char *message = mallocstrcpy(NULL, read_buff_word);
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
filename = strtok(read_buff_word, ":");
|
|
|
|
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) {
|
|
|
|
read_buff_ptr++;
|
|
|
|
free(message);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpcolno = strtol(maybecol, NULL, 10);
|
|
|
|
/* Check if the middle field is in comma format. */
|
|
|
|
if (tmpcolno <= 0) {
|
|
|
|
strtok(linestr, ",");
|
|
|
|
if ((tmplinecol = strtok(NULL, ",")) != NULL)
|
|
|
|
tmpcolno = strtol(tmplinecol, NULL, 10);
|
|
|
|
else
|
|
|
|
tmpcolno = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Nice. We have a lint message we can use. */
|
|
|
|
parsesuccess++;
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = nmalloc(sizeof(lintstruct));
|
|
|
|
curlint->next = NULL;
|
|
|
|
curlint->prev = tmplint;
|
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint->prev->next = curlint;
|
|
|
|
curlint->msg = mallocstrcpy(NULL, message);
|
|
|
|
curlint->lineno = tmplineno;
|
|
|
|
curlint->colno = tmpcolno;
|
|
|
|
curlint->filename = mallocstrcpy(NULL, filename);
|
|
|
|
|
|
|
|
if (lints == NULL)
|
|
|
|
lints = curlint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
free(message);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
read_buff_word = read_buff_ptr + 1;
|
|
|
|
}
|
|
|
|
read_buff_ptr++;
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
statusbar(invocation_error(openfile->syntax->linter));
|
|
|
|
return;
|
|
|
|
}
|
2015-04-17 09:24:17 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(read_buff);
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (parsesuccess == 0) {
|
|
|
|
statusline(HUSH, _("Got 0 parsable lines from command: %s"),
|
|
|
|
openfile->syntax->linter);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2018-10-21 18:47:37 +00:00
|
|
|
if (helpless && LINES > 4) {
|
|
|
|
UNSET(NO_HELP);
|
|
|
|
window_init();
|
|
|
|
}
|
|
|
|
|
2018-10-17 18:17:03 +00:00
|
|
|
/* Show that we are in the linter now. */
|
|
|
|
titlebar(NULL);
|
2017-12-29 18:27:33 +00:00
|
|
|
bottombars(MLINTER);
|
2018-10-17 18:17:03 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
|
|
|
curlint = lints;
|
2016-02-11 17:25:37 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
while (TRUE) {
|
|
|
|
int kbinput;
|
|
|
|
functionptrtype func;
|
2018-03-25 19:36:13 +00:00
|
|
|
struct stat lintfileinfo;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-03-25 19:36:13 +00:00
|
|
|
if (stat(curlint->filename, &lintfileinfo) != -1 &&
|
2018-05-25 17:25:11 +00:00
|
|
|
(openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->st_ino != lintfileinfo.st_ino)) {
|
2018-03-25 19:41:46 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-24 16:28:59 +00:00
|
|
|
const openfilestruct *started_at = openfile;
|
2018-05-24 16:17:58 +00:00
|
|
|
|
2018-05-24 16:28:59 +00:00
|
|
|
openfile = openfile->next;
|
2018-05-25 17:25:11 +00:00
|
|
|
while (openfile != started_at && (openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->st_ino != lintfileinfo.st_ino))
|
2018-05-24 16:28:59 +00:00
|
|
|
openfile = openfile->next;
|
2018-05-24 16:14:35 +00:00
|
|
|
|
2018-05-25 17:25:11 +00:00
|
|
|
if (openfile->current_stat == NULL ||
|
|
|
|
openfile->current_stat->st_ino != lintfileinfo.st_ino) {
|
2018-03-25 19:36:13 +00:00
|
|
|
char *msg = charalloc(1024 + strlen(curlint->filename));
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sprintf(msg, _("This message is for unopened file %s,"
|
|
|
|
" open it in a new buffer?"), curlint->filename);
|
|
|
|
i = 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);
|
|
|
|
|
|
|
|
if (i == -1) {
|
|
|
|
statusbar(_("Cancelled"));
|
2018-10-24 08:23:39 +00:00
|
|
|
break;
|
2018-03-25 19:36:13 +00:00
|
|
|
} else if (i == 1) {
|
|
|
|
open_buffer(curlint->filename, TRUE);
|
|
|
|
} else {
|
2018-03-25 19:41:46 +00:00
|
|
|
#endif
|
2018-03-25 19:36:13 +00:00
|
|
|
char *dontwantfile = mallocstrcpy(NULL, curlint->filename);
|
|
|
|
lintstruct *restlint = NULL;
|
|
|
|
|
|
|
|
while (curlint != NULL) {
|
|
|
|
if (strcmp(curlint->filename, dontwantfile) == 0) {
|
|
|
|
if (curlint == lints)
|
|
|
|
lints = curlint->next;
|
|
|
|
else
|
|
|
|
curlint->prev->next = curlint->next;
|
|
|
|
if (curlint->next != NULL)
|
|
|
|
curlint->next->prev = curlint->prev;
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
free(tmplint->msg);
|
|
|
|
free(tmplint->filename);
|
|
|
|
free(tmplint);
|
2017-12-29 18:27:33 +00:00
|
|
|
} else {
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL)
|
|
|
|
restlint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
}
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2018-07-14 19:02:32 +00:00
|
|
|
free(dontwantfile);
|
|
|
|
|
2018-03-25 19:36:13 +00:00
|
|
|
if (restlint == NULL) {
|
2018-10-24 08:23:39 +00:00
|
|
|
statusbar(_("No messages for this file"));
|
2018-03-25 19:36:13 +00:00
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
curlint = restlint;
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-25 19:41:46 +00:00
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
2018-05-24 16:14:35 +00:00
|
|
|
}
|
2018-05-24 16:28:59 +00:00
|
|
|
}
|
2018-03-25 19:41:46 +00:00
|
|
|
#endif
|
2018-03-25 19:36:13 +00:00
|
|
|
}
|
2018-03-25 17:46:44 +00:00
|
|
|
|
|
|
|
if (tmplint != curlint) {
|
2017-12-29 18:27:33 +00:00
|
|
|
goto_line_posx(curlint->lineno, curlint->colno - 1);
|
|
|
|
titlebar(NULL);
|
|
|
|
adjust_viewport(CENTERING);
|
2018-05-25 15:57:04 +00:00
|
|
|
#ifdef ENABLE_LINENUMBERS
|
2018-05-25 18:09:24 +00:00
|
|
|
confirm_margin();
|
2018-05-25 15:57:04 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
edit_refresh();
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2017-12-29 18:27:33 +00:00
|
|
|
bottombars(MLINTER);
|
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Place the cursor to indicate the affected line. */
|
|
|
|
place_the_cursor();
|
|
|
|
wnoutrefresh(edit);
|
2016-02-13 19:41:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
kbinput = get_kbinput(bottomwin, VISIBLE);
|
2015-05-28 13:02:29 +00:00
|
|
|
|
|
|
|
#ifndef NANO_TINY
|
2017-12-29 18:27:33 +00:00
|
|
|
if (kbinput == KEY_WINCH)
|
|
|
|
continue;
|
2015-05-28 13:02:29 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
func = func_from_key(&kbinput);
|
|
|
|
tmplint = curlint;
|
|
|
|
|
2018-10-17 18:17:03 +00:00
|
|
|
if (func == do_cancel || func == do_enter) {
|
2018-10-24 08:23:39 +00:00
|
|
|
wipe_statusbar();
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2018-10-17 18:17:03 +00:00
|
|
|
} else if (func == do_help_void) {
|
2017-12-29 18:27:33 +00:00
|
|
|
tmplint = NULL;
|
|
|
|
do_help_void();
|
2018-09-29 07:40:53 +00:00
|
|
|
} else if (func == do_page_up || func == do_prev_block) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (curlint->prev != NULL)
|
|
|
|
curlint = curlint->prev;
|
2018-10-22 01:14:38 +00:00
|
|
|
else if (last_wait != time(NULL)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
statusbar(_("At first message"));
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
2018-09-29 07:46:05 +00:00
|
|
|
} else if (func == do_page_down || func == do_next_block) {
|
|
|
|
if (curlint->next != NULL)
|
|
|
|
curlint = curlint->next;
|
2018-10-22 01:14:38 +00:00
|
|
|
else if (last_wait != time(NULL)) {
|
2018-09-29 07:46:05 +00:00
|
|
|
statusbar(_("At last message"));
|
2018-10-21 11:02:07 +00:00
|
|
|
napms(600);
|
2018-10-22 01:14:38 +00:00
|
|
|
last_wait = time(NULL);
|
2018-10-17 18:30:25 +00:00
|
|
|
statusline(NOTICE, curlint->msg);
|
2018-10-21 11:02:07 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2016-02-11 17:25:37 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (curlint = lints; curlint != NULL;) {
|
|
|
|
tmplint = curlint;
|
|
|
|
curlint = curlint->next;
|
|
|
|
free(tmplint->msg);
|
|
|
|
free(tmplint->filename);
|
|
|
|
free(tmplint);
|
|
|
|
}
|
2018-10-21 18:47:37 +00:00
|
|
|
|
|
|
|
if (helpless) {
|
|
|
|
SET(NO_HELP);
|
|
|
|
window_init();
|
|
|
|
refresh_needed = TRUE;
|
|
|
|
}
|
2018-10-24 08:23:39 +00:00
|
|
|
|
|
|
|
currmenu = MMOST;
|
|
|
|
titlebar(NULL);
|
2014-02-24 10:18:15 +00:00
|
|
|
}
|
2017-11-01 18:45:33 +00:00
|
|
|
#endif /* ENABLE_COLOR */
|
2014-02-24 10:18:15 +00:00
|
|
|
|
2005-11-15 03:17:35 +00:00
|
|
|
#ifndef NANO_TINY
|
2005-08-10 22:51:49 +00:00
|
|
|
/* Our own version of "wc". Note that its character counts are in
|
|
|
|
* multibyte characters instead of single-byte characters. */
|
2005-07-25 04:21:46 +00:00
|
|
|
void do_wordlinechar_count(void)
|
2005-07-24 19:57:51 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
size_t words = 0, chars = 0;
|
|
|
|
ssize_t nlines = 0;
|
|
|
|
size_t current_x_save = openfile->current_x;
|
|
|
|
size_t pww_save = openfile->placewewant;
|
|
|
|
filestruct *current_save = openfile->current;
|
|
|
|
filestruct *was_mark = openfile->mark;
|
|
|
|
filestruct *top, *bot;
|
|
|
|
size_t top_x, bot_x;
|
|
|
|
|
|
|
|
/* If the mark is on, partition the buffer so that it
|
|
|
|
* contains only the marked text, and turn the mark off. */
|
|
|
|
if (was_mark) {
|
|
|
|
mark_order((const filestruct **)&top, &top_x,
|
|
|
|
(const filestruct **)&bot, &bot_x, NULL);
|
|
|
|
filepart = partition_filestruct(top, top_x, bot, bot_x);
|
|
|
|
openfile->mark = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start at the top of the file. */
|
|
|
|
openfile->current = openfile->fileage;
|
|
|
|
openfile->current_x = 0;
|
|
|
|
openfile->placewewant = 0;
|
|
|
|
|
|
|
|
/* Keep moving to the next word (counting punctuation characters as
|
|
|
|
* part of a word, as "wc -w" does), without updating the screen,
|
|
|
|
* until we reach the end of the file, incrementing the total word
|
|
|
|
* count whenever we're on a word just before moving. */
|
|
|
|
while (openfile->current != openfile->filebot ||
|
|
|
|
openfile->current->data[openfile->current_x] != '\0') {
|
2018-09-09 19:01:09 +00:00
|
|
|
if (do_next_word(FALSE, TRUE))
|
2017-12-29 18:27:33 +00:00
|
|
|
words++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the total line and character counts, as "wc -l" and "wc -c"
|
|
|
|
* do, but get the latter in multibyte characters. */
|
|
|
|
if (was_mark) {
|
|
|
|
nlines = openfile->filebot->lineno - openfile->fileage->lineno + 1;
|
|
|
|
chars = get_totsize(openfile->fileage, openfile->filebot);
|
|
|
|
|
|
|
|
/* Unpartition the buffer so that it contains all the text
|
|
|
|
* again, and turn the mark back on. */
|
|
|
|
unpartition_filestruct(&filepart);
|
|
|
|
openfile->mark = was_mark;
|
|
|
|
} else {
|
|
|
|
nlines = openfile->filebot->lineno;
|
|
|
|
chars = openfile->totsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore where we were. */
|
|
|
|
openfile->current = current_save;
|
|
|
|
openfile->current_x = current_x_save;
|
|
|
|
openfile->placewewant = pww_save;
|
|
|
|
|
|
|
|
/* Display the total word, line, and character counts on the statusbar. */
|
|
|
|
statusline(HUSH, _("%sWords: %zu Lines: %zd Chars: %zu"), was_mark ?
|
|
|
|
_("In Selection: ") : "", words, nlines, chars);
|
2005-07-24 19:57:51 +00:00
|
|
|
}
|
2005-11-15 03:17:35 +00:00
|
|
|
#endif /* !NANO_TINY */
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2005-12-08 07:09:08 +00:00
|
|
|
/* Get verbatim input. */
|
2005-11-07 06:06:05 +00:00
|
|
|
void do_verbatim_input(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
int *kbinput;
|
|
|
|
size_t kbinput_len, i;
|
|
|
|
char *output;
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* TRANSLATORS: This is displayed when the next keystroke will be
|
|
|
|
* inserted verbatim. */
|
|
|
|
statusbar(_("Verbatim Input"));
|
|
|
|
place_the_cursor();
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Read in all the verbatim characters. */
|
|
|
|
kbinput = get_verbatim_kbinput(edit, &kbinput_len);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Unsuppress cursor-position display or blank the statusbar. */
|
|
|
|
if (ISSET(CONSTANT_SHOW))
|
|
|
|
suppress_cursorpos = FALSE;
|
|
|
|
else
|
|
|
|
wipe_statusbar();
|
2006-05-27 17:39:19 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Display all the verbatim characters at once, not filtering out
|
|
|
|
* control characters. */
|
|
|
|
output = charalloc(kbinput_len + 1);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (i = 0; i < kbinput_len; i++)
|
|
|
|
output[i] = (char)kbinput[i];
|
|
|
|
output[i] = '\0';
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(kbinput);
|
2006-12-02 17:22:21 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
do_output(output, kbinput_len, TRUE);
|
2005-11-07 06:06:05 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(output);
|
2005-11-07 06:06:05 +00:00
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2016-12-07 12:10:40 +00:00
|
|
|
#ifdef ENABLE_WORDCOMPLETION
|
2016-12-07 04:13:47 +00:00
|
|
|
/* Copy the found completion candidate. */
|
|
|
|
char *copy_completion(char *check_line, int start)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *word;
|
|
|
|
int position = start, len_of_word = 0, index = 0;
|
|
|
|
|
|
|
|
/* Find the length of the word by travelling to its end. */
|
|
|
|
while (is_word_mbchar(&check_line[position], FALSE)) {
|
|
|
|
int next = move_mbright(check_line, position);
|
|
|
|
len_of_word += next - position;
|
|
|
|
position = next;
|
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
word = charalloc(len_of_word + 1);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Simply copy the word. */
|
|
|
|
while (index < len_of_word)
|
|
|
|
word[index++] = check_line[start++];
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
word[index] = '\0';
|
|
|
|
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
|
2017-12-29 18:27:33 +00:00
|
|
|
bool was_set_wrapping = !ISSET(NO_WRAP);
|
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. */
|
|
|
|
pletion_line = openfile->fileage;
|
|
|
|
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) {
|
|
|
|
int step_left = move_mbleft(openfile->current->data, start_of_shard);
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (!is_word_mbchar(&openfile->current->data[step_left], FALSE))
|
|
|
|
break;
|
|
|
|
start_of_shard = step_left;
|
|
|
|
}
|
2016-12-07 04:13:47 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If there is no word fragment before the cursor, do nothing. */
|
|
|
|
if (start_of_shard == openfile->current_x) {
|
2018-07-13 11:37:33 +00:00
|
|
|
statusbar(_("No word fragment"));
|
2017-12-29 18:27:33 +00:00
|
|
|
pletion_line = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
shard = charalloc(openfile->current_x - start_of_shard + 1);
|
|
|
|
|
|
|
|
/* Copy the fragment that has to be searched for. */
|
|
|
|
while (start_of_shard < openfile->current_x)
|
|
|
|
shard[shard_length++] = openfile->current->data[start_of_shard++];
|
|
|
|
shard[shard_length] = '\0';
|
|
|
|
|
|
|
|
/* Run through all of the lines in the buffer, looking for shard. */
|
|
|
|
while (pletion_line != NULL) {
|
|
|
|
int threshold = strlen(pletion_line->data) - shard_length - 1;
|
|
|
|
/* The point where we can stop searching for shard. */
|
|
|
|
|
|
|
|
/* Traverse the whole line, looking for shard. */
|
2018-04-23 14:37:55 +00:00
|
|
|
for (i = pletion_x; (int)i < threshold; i++) {
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If the first byte doesn't match, run on. */
|
|
|
|
if (pletion_line->data[i] != shard[0])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Compare the rest of the bytes in shard. */
|
|
|
|
for (j = 1; j < shard_length; j++)
|
|
|
|
if (pletion_line->data[i + j] != shard[j])
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* If not all of the bytes matched, continue searching. */
|
|
|
|
if (j < shard_length)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If the found match is not /longer/ than shard, skip it. */
|
|
|
|
if (!is_word_mbchar(&pletion_line->data[i + j], FALSE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If the match is not a separate word, skip it. */
|
|
|
|
if (i > 0 && is_word_mbchar(&pletion_line->data[
|
|
|
|
move_mbleft(pletion_line->data, i)], FALSE))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* If this match is the shard itself, ignore it. */
|
|
|
|
if (pletion_line == openfile->current &&
|
|
|
|
i == openfile->current_x - shard_length)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
completion = copy_completion(pletion_line->data, i);
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
SET(NO_WRAP);
|
2017-01-01 15:33:40 +00:00
|
|
|
#endif
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Inject the completion into the buffer. */
|
|
|
|
do_output(&completion[shard_length],
|
|
|
|
strlen(completion) - shard_length, FALSE);
|
2017-10-29 20:00:09 +00:00
|
|
|
#ifdef ENABLE_WRAPPING
|
2017-12-29 18:27:33 +00:00
|
|
|
/* If needed, reenable wrapping and wrap the current line. */
|
|
|
|
if (was_set_wrapping) {
|
|
|
|
UNSET(NO_WRAP);
|
|
|
|
do_wrap(openfile->current);
|
|
|
|
}
|
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 */
|