From 691698a954520d5c03dca181a60cc3802ef4f5af Mon Sep 17 00:00:00 2001 From: David Lawrence Ramsey Date: Sun, 24 Jul 2005 19:57:51 +0000 Subject: [PATCH] move advanced text operations (command execution in a buffer, wrapping, spell checking, justifying, and word counting) to their own source file, text.c git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@2919 35c25a1d-7b9e-4130-9fde-d3aeb78583b8 --- ChangeLog | 17 + po/ChangeLog | 4 + po/POTFILES.in | 1 + src/Makefile.am | 1 + src/chars.c | 2 - src/color.c | 5 - src/cut.c | 2 - src/files.c | 2 - src/global.c | 19 +- src/move.c | 2 - src/nano.c | 1883 ---------------------------------------------- src/nano.h | 6 + src/proto.h | 102 ++- src/rcfile.c | 2 - src/search.c | 26 - src/text.c | 1915 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils.c | 69 +- src/winio.c | 2 - 18 files changed, 2051 insertions(+), 2009 deletions(-) create mode 100644 src/text.c diff --git a/ChangeLog b/ChangeLog index 31b52306..471f3c42 100644 --- a/ChangeLog +++ b/ChangeLog @@ -88,6 +88,19 @@ CVS code - mbwidth(), make_mbchar(), parse_mbchar(), mbstrncasecmp(), mbstrcasestr(), mbrevstrcasestr(), mbstrchr(), and display_string(). (DLR) + - Move advanced text operations (command execution in a buffer, + wrapping, spell checking, justifying, and word counting) to + their own source file, and adjust related variables + accordingly. New file text.c; changes to cancel_command(), + execute_command(), wrap_reset(), do_wrap(), + do_int_spell_fix(), do_int_speller(), do_alt_speller(), + do_spell(), break_line(), indent_length(), justify_format(), + quote_length(), quotes_match(), indents_match(), begpar(), + inpar(), backup_lines(), find_paragraph(), do_justify(), + do_justify_void(), do_full_justify(), and do_word_count() (all + moved to text.c). (DLR) +- color.c: + - Remove unneeded string.h and fcntl.h includes. (DLR) - chars.c: mbstrchr() - Don't count matches between valid and invalid multibyte @@ -157,6 +170,8 @@ CVS code - HAVE_SNPRINTF. (DLR) - Remove TOP from the topmidnone enum, and rename it centernone. (DLR) + - Move stdlib.h, dirent.h, regex.h, and assert.h includes here, + as every source file needs them. (DLR) proto.h: - Add declarations for bad_mbchar and bad_mbchar_len, so that we can use them in display_string() as well as chars.c. (DLR) @@ -219,6 +234,8 @@ CVS code - tweaks by DLR) - doc/man/fr/nano.1, doc/man/fr/nanorc.1: - Updated translation by Jean-Philippe Gérard. +- src/Makefile.am: + - Add text.c to nano_SOURCES. (DLR) GNU nano 1.3.8 - 2005.06.30 - General: diff --git a/po/ChangeLog b/po/ChangeLog index 7b866efe..49d853ac 100644 --- a/po/ChangeLog +++ b/po/ChangeLog @@ -1,3 +1,7 @@ +2005-07-24 David Lawrence Ramsey + + * POTFILES.in: Add text.c. + 2005-07-19 Jordi Mallach * ms.po: Updated Malay translation by diff --git a/po/POTFILES.in b/po/POTFILES.in index d7a9162c..5659c7f5 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,5 +6,6 @@ src/global.c src/nano.c src/rcfile.c src/search.c +src/text.c src/utils.c src/winio.c diff --git a/src/Makefile.am b/src/Makefile.am index 3aa1ff50..f9621c26 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,6 +15,7 @@ nano_SOURCES = chars.c \ proto.h \ rcfile.c \ search.c \ + text.c \ utils.c \ winio.c diff --git a/src/chars.c b/src/chars.c index 1e1dce57..c62d7b42 100644 --- a/src/chars.c +++ b/src/chars.c @@ -24,10 +24,8 @@ #include #endif -#include #include #include -#include #include "proto.h" #ifdef ENABLE_UTF8 diff --git a/src/color.c b/src/color.c index 34fcab94..811f64a9 100644 --- a/src/color.c +++ b/src/color.c @@ -24,12 +24,7 @@ #include #endif -#include -#include #include -#include -#include -#include #include "proto.h" #ifdef ENABLE_COLOR diff --git a/src/cut.c b/src/cut.c index 13043281..d8cb01be 100644 --- a/src/cut.c +++ b/src/cut.c @@ -24,10 +24,8 @@ #include #endif -#include #include #include -#include #include "proto.h" static bool keep_cutbuffer = FALSE; diff --git a/src/files.c b/src/files.c index da39f6fe..51c3251c 100644 --- a/src/files.c +++ b/src/files.c @@ -24,7 +24,6 @@ #include #endif -#include #include #include #include @@ -34,7 +33,6 @@ #include #include #include -#include #include "proto.h" /* Add an entry to the openfile openfilestruct. This should only be diff --git a/src/global.c b/src/global.c index 26c9e1e9..1e7a1ec6 100644 --- a/src/global.c +++ b/src/global.c @@ -24,25 +24,28 @@ #include #endif -#include -#include #include "proto.h" /* Global variables */ - #ifndef DISABLE_WRAPJUSTIFY -/* wrap_at might be set in rcfile.c or nano.c. */ -ssize_t wrap_at = -CHARS_FROM_EOL; /* Right justified fill value, - allows resize */ +ssize_t fill = 0; /* Where we will wrap lines. */ +ssize_t wrap_at = -CHARS_FROM_EOL; + /* The position that corresponds to + * fill. If it's greater than zero, + * fill is equal to it. Otherwise, fill + * is equal to the number of screen + * columns less it. This allows + * dynamic wrapping based on the current + * screen width. */ #endif char *last_search = NULL; /* Last string we searched for */ char *last_replace = NULL; /* Last replacement string */ long flags = 0; /* Our flag containing many options */ -WINDOW *topwin; /* Top buffer */ +WINDOW *topwin; /* Top subwindow */ WINDOW *edit; /* The file portion of the editor */ -WINDOW *bottomwin; /* Bottom buffer */ +WINDOW *bottomwin; /* Bottom subwindow */ int editwinrows = 0; /* How many rows long is the edit window? */ diff --git a/src/move.c b/src/move.c index 38dedc3a..21a8175d 100644 --- a/src/move.c +++ b/src/move.c @@ -24,10 +24,8 @@ #include #endif -#include #include #include -#include #include "proto.h" void do_first_line(void) diff --git a/src/nano.c b/src/nano.c index 1a8f9d61..45750e70 100644 --- a/src/nano.c +++ b/src/nano.c @@ -25,7 +25,6 @@ #endif #include -#include #include #include #include @@ -33,11 +32,9 @@ #include #include #include -#include #include #include #include -#include #include "proto.h" #ifdef HAVE_TERMIOS_H @@ -55,11 +52,6 @@ #ifndef DISABLE_WRAPJUSTIFY static ssize_t fill = 0; /* Fill - where to wrap lines */ #endif -#ifndef DISABLE_WRAPPING -static bool same_line_wrap = FALSE; /* Whether wrapped text should - be prepended to the next - line */ -#endif static struct termios oldterm; /* The user's original term settings */ static struct sigaction act; /* For all our fun signal handlers */ @@ -67,15 +59,6 @@ static struct sigaction act; /* For all our fun signal handlers */ #ifndef NANO_SMALL static sigjmp_buf jmpbuf; /* Used to return to main() after a SIGWINCH. */ -static pid_t pid; /* The PID of the newly forked process - * in execute_command(). It must be - * global because the signal handler - * needs it. */ -#endif - -#ifndef DISABLE_JUSTIFY -static filestruct *jusbottom = NULL; - /* Pointer to end of justify buffer. */ #endif /* Create a new filestruct node. Note that we specifically do not set @@ -1239,94 +1222,6 @@ void nano_disabled_msg(void) statusbar(_("Sorry, support for this function has been disabled")); } -#ifndef NANO_SMALL -void cancel_command(int signal) -{ - if (kill(pid, SIGKILL) == -1) - nperror("kill"); -} - -/* Return TRUE on success. */ -bool execute_command(const char *command) -{ - int fd[2]; - FILE *f; - struct sigaction oldaction, newaction; - /* Original and temporary handlers for SIGINT. */ - bool sig_failed = FALSE; - /* Did sigaction() fail without changing the signal handlers? */ - - /* Make our pipes. */ - if (pipe(fd) == -1) { - statusbar(_("Could not pipe")); - return FALSE; - } - - /* Fork a child. */ - if ((pid = fork()) == 0) { - close(fd[0]); - dup2(fd[1], fileno(stdout)); - dup2(fd[1], fileno(stderr)); - - /* If execl() returns at all, there was an error. */ - execl("/bin/sh", "sh", "-c", command, NULL); - exit(0); - } - - /* Else continue as parent. */ - close(fd[1]); - - if (pid == -1) { - close(fd[0]); - statusbar(_("Could not fork")); - return FALSE; - } - - /* Before we start reading the forked command's output, we set - * things up so that Ctrl-C will cancel the new process. */ - - /* Enable interpretation of the special control keys so that we get - * SIGINT when Ctrl-C is pressed. */ - enable_signals(); - - if (sigaction(SIGINT, NULL, &newaction) == -1) { - sig_failed = TRUE; - nperror("sigaction"); - } else { - newaction.sa_handler = cancel_command; - if (sigaction(SIGINT, &newaction, &oldaction) == -1) { - sig_failed = TRUE; - nperror("sigaction"); - } - } - /* Note that now oldaction is the previous SIGINT signal handler, - * to be restored later. */ - - f = fdopen(fd[0], "rb"); - if (f == NULL) - nperror("fdopen"); - - read_file(f, "stdin"); - - /* If multibuffer mode is on, we could be here in view mode. If so, - * don't set the modification flag. */ - if (!ISSET(VIEW_MODE)) - set_modified(); - - if (wait(NULL) == -1) - nperror("wait"); - - if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1) - nperror("sigaction"); - - /* Disable interpretation of the special control keys so that we can - * use Ctrl-C for other things. */ - disable_signals(); - - return TRUE; -} -#endif /* !NANO_SMALL */ - void do_verbatim_input(void) { int *kbinput; @@ -1536,68 +1431,6 @@ void do_enter(void) } #ifndef NANO_SMALL -void do_word_count(void) -{ - size_t words = 0, current_x_save = openfile->current_x; - size_t pww_save = openfile->placewewant; - filestruct *current_save = openfile->current; - bool old_mark_set = openfile->mark_set; - bool added_magicline = FALSE; - /* Whether we added a magicline after filebot. */ - filestruct *top, *bot; - size_t top_x, bot_x; - - if (old_mark_set) { - /* If the mark is on, partition the filestruct so that it - * contains only the marked text, keep track of whether the text - * will need a magicline added while we're counting words, add - * the magicline if necessary, and turn the mark off. */ - mark_order((const filestruct **)&top, &top_x, - (const filestruct **)&bot, &bot_x, NULL); - filepart = partition_filestruct(top, top_x, bot, bot_x); - if ((added_magicline = (openfile->filebot->data[0] != '\0'))) - new_magicline(); - openfile->mark_set = FALSE; - } - - /* 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 so that we match the output of "wc -w"), 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_x != 0) { - if (do_next_word(TRUE, FALSE)) - words++; - } - - if (old_mark_set) { - /* If the mark was on and we added a magicline, remove it - * now. */ - if (added_magicline) - remove_magicline(); - - /* Unpartition the filestruct so that it contains all the text - * again, and turn the mark back on. */ - unpartition_filestruct(&filepart); - openfile->mark_set = TRUE; - } - - /* Restore where we were. */ - openfile->current = current_save; - openfile->current_x = current_x_save; - openfile->placewewant = pww_save; - - /* Display the total word count on the statusbar. */ - statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") : - _("Word Count"), (unsigned long)words); -} - void do_mark(void) { openfile->mark_set = !openfile->mark_set; @@ -1614,1722 +1447,6 @@ void do_mark(void) } #endif /* !NANO_SMALL */ -#ifndef DISABLE_WRAPPING -void wrap_reset(void) -{ - same_line_wrap = FALSE; -} -#endif - -#ifndef DISABLE_WRAPPING -/* We wrap the given line. Precondition: we assume the cursor has been - * moved forward since the last typed character. Return value: whether - * we wrapped. */ -bool do_wrap(filestruct *line) -{ - size_t line_len; - /* Length of the line we wrap. */ - ssize_t wrap_loc; - /* Index of line->data where we wrap. */ -#ifndef NANO_SMALL - const char *indent_string = NULL; - /* Indentation to prepend to the new line. */ - size_t indent_len = 0; /* The length of indent_string. */ -#endif - const char *after_break; /* The text after the wrap point. */ - size_t after_break_len; /* The length of after_break. */ - bool wrapping = FALSE; /* Do we prepend to the next line? */ - const char *next_line = NULL; - /* The next line, minus indentation. */ - size_t next_line_len = 0; /* The length of next_line. */ - char *new_line = NULL; /* The line we create. */ - size_t new_line_len = 0; /* The eventual length of new_line. */ - - /* 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 - * 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! */ - - assert(line != NULL && line->data != NULL); - - /* Save the length of the line. */ - line_len = strlen(line->data); - - /* Find the last blank where we can break the line. */ - wrap_loc = break_line(line->data, fill, FALSE); - - /* 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; - -#ifndef NANO_SMALL - /* If autoindent is turned on, and we're on the character just after - * the indentation, we don't wrap. */ - if (ISSET(AUTOINDENT)) { - /* Get the indentation of this line. */ - indent_string = line->data; - indent_len = indent_length(indent_string); - - if (wrap_loc == indent_len) - return FALSE; - } -#endif - - /* 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 same_line_wrap flag is - * set. */ - - /* after_break is the text that will be wrapped to the next line. */ - after_break = line->data + wrap_loc; - after_break_len = line_len - wrap_loc; - - assert(strlen(after_break) == after_break_len); - - /* We prepend the wrapped text to the next line, if the - * same_line_wrap flag is set, there is a next line, and prepending - * would not make the line too long. */ - if (same_line_wrap && line->next != NULL) { - const char *end = after_break + move_mbleft(after_break, - after_break_len); - - /* If after_break doesn't end in a blank, make sure it ends in a - * space. */ - if (!is_blank_mbchar(end)) { - line_len++; - line->data = charealloc(line->data, line_len + 1); - line->data[line_len - 1] = ' '; - line->data[line_len] = '\0'; - after_break = line->data + wrap_loc; - after_break_len++; - openfile->totsize++; - } - - next_line = line->next->data; - next_line_len = strlen(next_line); - - if (after_break_len + next_line_len <= fill) { - wrapping = TRUE; - new_line_len += next_line_len; - } - } - - /* new_line_len is now the length of the text that will be wrapped - * to the next line, plus (if we're prepending to it) the length of - * the text of the next line. */ - new_line_len += after_break_len; - -#ifndef NANO_SMALL - if (ISSET(AUTOINDENT)) { - if (wrapping) { - /* If we're wrapping, the indentation will come from the - * next line. */ - indent_string = next_line; - indent_len = indent_length(indent_string); - next_line += indent_len; - } else { - /* Otherwise, it will come from this line, in which case - * we should increase new_line_len to make room for it. */ - new_line_len += indent_len; - openfile->totsize += mbstrnlen(indent_string, indent_len); - } - } -#endif - - /* Now we allocate the new line and copy the text into it. */ - new_line = charalloc(new_line_len + 1); - new_line[0] = '\0'; - -#ifndef NANO_SMALL - if (ISSET(AUTOINDENT)) { - /* Copy the indentation. */ - strncpy(new_line, indent_string, indent_len); - new_line[indent_len] = '\0'; - new_line_len += indent_len; - } -#endif - - /* Copy all the text after the wrap point of the current line. */ - strcat(new_line, after_break); - - /* Break the current line at the wrap point. */ - null_at(&line->data, wrap_loc); - - if (wrapping) { - /* If we're wrapping, copy the text from the next line, minus - * the indentation that we already copied above. */ - strcat(new_line, next_line); - - free(line->next->data); - line->next->data = new_line; - } else { - /* Otherwise, make a new line and copy the text after where we - * broke this line to the beginning of the new line. */ - splice_node(openfile->current, make_new_node(openfile->current), - openfile->current->next); - - openfile->current->next->data = new_line; - - openfile->totlines++; - openfile->totsize++; - } - - /* Step 3, clean up. Reposition the cursor and mark, and do some - * other sundry things. */ - - /* Set the same_line_wrap flag, so that later wraps of this line - * will be prepended to the next line. */ - same_line_wrap = TRUE; - - /* Each line knows its line number. We recalculate these if we - * inserted a new line. */ - if (!wrapping) - renumber(line); - - /* If the cursor was after the break point, we must move it. We - * also clear the same_line_wrap flag in this case. */ - if (openfile->current_x > wrap_loc) { - same_line_wrap = FALSE; - openfile->current = openfile->current->next; - openfile->current_x -= wrap_loc -#ifndef NANO_SMALL - - indent_len -#endif - ; - openfile->placewewant = xplustabs(); - } - -#ifndef NANO_SMALL - /* If the mark was on this line after the wrap point, we move it - * down. If it was on the next line and we wrapped onto that line, - * we move it right. */ - if (openfile->mark_set) { - if (openfile->mark_begin == line && openfile->mark_begin_x > - wrap_loc) { - openfile->mark_begin = line->next; - openfile->mark_begin_x -= wrap_loc - indent_len + 1; - } else if (wrapping && openfile->mark_begin == line->next) - openfile->mark_begin_x += after_break_len; - } -#endif - - return TRUE; -} -#endif /* !DISABLE_WRAPPING */ - -#ifndef DISABLE_SPELLER -/* A word is misspelled in the file. Let the user replace it. We - * return FALSE if the user cancels. */ -bool do_int_spell_fix(const char *word) -{ - char *save_search, *save_replace; - size_t match_len, current_x_save = openfile->current_x; - size_t pww_save = openfile->placewewant; - filestruct *edittop_save = openfile->edittop; - filestruct *current_save = openfile->current; - /* Save where we are. */ - bool canceled = FALSE; - /* The return value. */ - bool case_sens_set = ISSET(CASE_SENSITIVE); -#ifndef NANO_SMALL - bool backwards_search_set = ISSET(BACKWARDS_SEARCH); -#endif -#ifdef HAVE_REGEX_H - bool regexp_set = ISSET(USE_REGEXP); -#endif -#ifndef NANO_SMALL - bool old_mark_set = openfile->mark_set; - bool added_magicline = FALSE; - /* Whether we added a magicline after filebot. */ - 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; -#endif - - /* Make sure spell-check is case sensitive. */ - SET(CASE_SENSITIVE); - -#ifndef NANO_SMALL - /* Make sure spell-check goes forward only. */ - UNSET(BACKWARDS_SEARCH); -#endif -#ifdef HAVE_REGEX_H - /* Make sure spell-check doesn't use regular expressions. */ - UNSET(USE_REGEXP); -#endif - - /* Save the current search/replace strings. */ - search_init_globals(); - save_search = last_search; - save_replace = last_replace; - - /* Set the search/replace strings to the misspelled word. */ - last_search = mallocstrcpy(NULL, word); - last_replace = mallocstrcpy(NULL, word); - -#ifndef NANO_SMALL - if (old_mark_set) { - /* If the mark is on, partition the filestruct so that it - * contains only the marked text, keep track of whether the text - * will have a magicline added when we're done correcting - * misspelled words, and turn the mark off. */ - mark_order((const filestruct **)&top, &top_x, - (const filestruct **)&bot, &bot_x, &right_side_up); - filepart = partition_filestruct(top, top_x, bot, bot_x); - added_magicline = (openfile->filebot->data[0] != '\0'); - openfile->mark_set = FALSE; - } -#endif - - /* Start from the top of the file. */ - openfile->edittop = openfile->fileage; - openfile->current = openfile->fileage; - openfile->current_x = (size_t)-1; - openfile->placewewant = 0; - - /* Find the first whole-word occurrence of word. */ - findnextstr_wrap_reset(); - while (findnextstr(TRUE, TRUE, FALSE, openfile->fileage, 0, word, - &match_len)) { - if (is_whole_word(openfile->current_x, openfile->current->data, - word)) { - size_t xpt = xplustabs(); - char *exp_word = display_string(openfile->current->data, - xpt, strnlenpt(openfile->current->data, - openfile->current_x + match_len) - xpt, FALSE); - - edit_refresh(); - - do_replace_highlight(TRUE, exp_word); - - /* Allow all instances of the word to be corrected. */ - canceled = (statusq(FALSE, spell_list, word, -#ifndef NANO_SMALL - NULL, -#endif - _("Edit a replacement")) == -1); - - do_replace_highlight(FALSE, exp_word); - - free(exp_word); - - if (!canceled && strcmp(word, answer) != 0) { - openfile->current_x--; - do_replace_loop(word, openfile->current, - &openfile->current_x, TRUE, &canceled); - } - - break; - } - } - -#ifndef NANO_SMALL - if (old_mark_set) { - /* If the mark was on and we added a magicline, remove it - * now. */ - if (added_magicline) - remove_magicline(); - - /* Put the beginning and the end of the mark at the beginning - * and the end of the spell-checked text. */ - if (openfile->fileage == openfile->filebot) - bot_x += top_x; - if (right_side_up) { - openfile->mark_begin_x = top_x; - current_x_save = bot_x; - } else { - current_x_save = top_x; - openfile->mark_begin_x = bot_x; - } - - /* Unpartition the filestruct so that it contains all the text - * again, and turn the mark back on. */ - unpartition_filestruct(&filepart); - openfile->mark_set = TRUE; - } -#endif - - /* Restore the search/replace strings. */ - free(last_search); - last_search = save_search; - free(last_replace); - last_replace = save_replace; - - /* Restore where we were. */ - openfile->edittop = edittop_save; - openfile->current = current_save; - openfile->current_x = current_x_save; - openfile->placewewant = pww_save; - - /* Restore case sensitivity setting. */ - if (!case_sens_set) - UNSET(CASE_SENSITIVE); - -#ifndef NANO_SMALL - /* Restore search/replace direction. */ - if (backwards_search_set) - SET(BACKWARDS_SEARCH); -#endif -#ifdef HAVE_REGEX_H - /* Restore regular expression usage setting. */ - if (regexp_set) - SET(USE_REGEXP); -#endif - - return !canceled; -} - -/* Integrated spell checking using the spell program, filtered through - * the sort and uniq programs. Return NULL for normal termination, - * and the error string otherwise. */ -const char *do_int_speller(const char *tempfile_name) -{ - 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; - - /* Create all three pipes up front. */ - if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || - pipe(uniq_fd) == -1) - return _("Could not create pipe"); - - statusbar(_("Creating misspelled word list, please wait...")); - - /* A new process to run spell in. */ - if ((pid_spell = fork()) == 0) { - /* Child continues (i.e, future spell process). */ - close(spell_fd[0]); - - /* Replace the standard input with the temp file. */ - if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1) - goto close_pipes_and_exit; - - if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) - goto close_pipes_and_exit; - - 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; - - close(spell_fd[1]); - - /* 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. */ - close(spell_fd[1]); - - /* A new process to run sort in. */ - if ((pid_sort = fork()) == 0) { - /* Child continues (i.e, future spell 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; - - close(spell_fd[0]); - - /* Send sort's standard output to the new pipe. */ - if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO) - goto close_pipes_and_exit; - - close(sort_fd[1]); - - /* Start the sort program. Use -f to remove mixed case. If - * this isn't portable, let me know. */ - execlp("sort", "sort", "-f", NULL); - - /* This should not be reached if sort is found. */ - exit(1); - } - - close(spell_fd[0]); - close(sort_fd[1]); - - /* 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; - - close(sort_fd[0]); - - /* Send uniq's standard output to the new pipe. */ - if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO) - goto close_pipes_and_exit; - - close(uniq_fd[1]); - - /* Start the uniq program; we are using PATH. */ - execlp("uniq", "uniq", NULL); - - /* This should not be reached if uniq is found. */ - exit(1); - } - - close(sort_fd[0]); - close(uniq_fd[1]); - - /* 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"); - } - - /* 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"); - } - - /* 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); - - 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; - } - - *read_buff_ptr = '\0'; - close(uniq_fd[0]); - - /* Process the spelling errors. */ - 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) { - if (!do_int_spell_fix(read_buff_word)) { - read_buff_word = read_buff_ptr; - break; - } - } - read_buff_word = read_buff_ptr + 1; - } - read_buff_ptr++; - } - - /* Special case: the last word doesn't end with '\r' or '\n'. */ - if (read_buff_word != read_buff_ptr) - do_int_spell_fix(read_buff_word); - - free(read_buff); - replace_abort(); - edit_refresh(); - - /* Process the end of the spell process. */ - waitpid(pid_spell, &spell_status, 0); - waitpid(pid_sort, &sort_status, 0); - waitpid(pid_uniq, &uniq_status, 0); - - if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status)) - return _("Error invoking \"spell\""); - - if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status)) - return _("Error invoking \"sort -f\""); - - if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status)) - return _("Error invoking \"uniq\""); - - /* Otherwise... */ - return NULL; - - close_pipes_and_exit: - /* Don't leak any handles. */ - close(tempfile_fd); - 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); -} - -/* External spell checking. Return value: NULL for normal termination, - * otherwise the error string. */ -const char *do_alt_speller(char *tempfile_name) -{ - int alt_spell_status; - size_t current_x_save = openfile->current_x; - size_t pww_save = openfile->placewewant; - ssize_t current_y_save = openfile->current_y; - ssize_t lineno_save = openfile->current->lineno; - pid_t pid_spell; - char *ptr; - static int arglen = 3; - static char **spellargs = NULL; - FILE *f; -#ifndef NANO_SMALL - bool old_mark_set = openfile->mark_set; - bool added_magicline = FALSE; - /* Whether we added a magicline after filebot. */ - 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; - ssize_t mb_lineno_save = 0; - /* We're going to close the current file, and open the output of - * the alternate spell command. The line that mark_begin points - * to will be freed, so we save the line number and restore it - * afterwards. */ - size_t totsize_save = openfile->totsize; - /* Our saved value of totsize, used when we spell-check a marked - * selection. */ - - if (old_mark_set) { - /* If the mark is on, save the number of the line it starts on, - * and then turn the mark off. */ - mb_lineno_save = openfile->mark_begin->lineno; - openfile->mark_set = FALSE; - } -#endif - - endwin(); - - /* Set up an argument list to pass execvp(). */ - if (spellargs == NULL) { - spellargs = (char **)nmalloc(arglen * sizeof(char *)); - - spellargs[0] = strtok(alt_speller, " "); - while ((ptr = strtok(NULL, " ")) != NULL) { - arglen++; - spellargs = (char **)nrealloc(spellargs, arglen * - sizeof(char *)); - spellargs[arglen - 3] = ptr; - } - spellargs[arglen - 1] = NULL; - } - spellargs[arglen - 2] = tempfile_name; - - /* Start a new process for the alternate speller. */ - if ((pid_spell = fork()) == 0) { - /* Start alternate spell program; we are using PATH. */ - execvp(spellargs[0], spellargs); - - /* Should not be reached, if alternate speller is found!!! */ - exit(1); - } - - /* Could not fork?? */ - if (pid_spell < 0) - return _("Could not fork"); - - /* Wait for alternate speller to complete. */ - wait(&alt_spell_status); - - refresh(); - - /* Restore the terminal to its previous state. */ - terminal_init(); - - /* Turn the cursor back on for sure. */ - curs_set(1); - - if (!WIFEXITED(alt_spell_status) || - WEXITSTATUS(alt_spell_status) != 0) { - char *altspell_error; - char *invoke_error = _("Error invoking \"%s\""); - -#ifndef NANO_SMALL - /* Turn the mark back on if it was on before. */ - openfile->mark_set = old_mark_set; -#endif - - altspell_error = - charalloc(strlen(invoke_error) + - strlen(alt_speller) + 1); - sprintf(altspell_error, invoke_error, alt_speller); - return altspell_error; - } - -#ifndef NANO_SMALL - if (old_mark_set) { - /* If the mark was on, partition the filestruct so that it - * contains only the marked text, and keep track of whether the - * temp file (which should contain the spell-checked marked - * text) will have a magicline added when it's reloaded. */ - mark_order((const filestruct **)&top, &top_x, - (const filestruct **)&bot, &bot_x, &right_side_up); - filepart = partition_filestruct(top, top_x, bot, bot_x); - added_magicline = (openfile->filebot->data[0] != '\0'); - - /* Get the number of characters in the marked text, and subtract - * it from the saved value of totsize. */ - totsize_save -= get_totsize(top, bot); - } -#endif - - /* Set up the window size. */ - window_size_init(); - - /* Reinitialize the text of the current buffer. */ - free_filestruct(openfile->fileage); - initialize_buffer_text(); - - /* Reload the temp file. Open it, read it into the current buffer, - * and move back to the first line of the buffer. */ - open_file(tempfile_name, FALSE, &f); - read_file(f, tempfile_name); - openfile->current = openfile->fileage; - -#ifndef NANO_SMALL - if (old_mark_set) { - filestruct *top_save = openfile->fileage; - - /* If the mark was on and we added a magicline, remove it - * now. */ - if (added_magicline) - remove_magicline(); - - /* Put the beginning and the end of the mark at the beginning - * and the end of the spell-checked text. */ - if (openfile->fileage == openfile->filebot) - bot_x += top_x; - if (right_side_up) { - openfile->mark_begin_x = top_x; - current_x_save = bot_x; - } else { - current_x_save = top_x; - openfile->mark_begin_x = bot_x; - } - - /* Unpartition the filestruct so that it contains all the text - * again. Note that we've replaced the marked text originally - * in the partition with the spell-checked marked text in the - * temp file. */ - unpartition_filestruct(&filepart); - - /* Renumber starting with the beginning line of the old - * partition. Also set totlines to the new number of lines in - * the file, add the number of characters in the spell-checked - * marked text to the saved value of totsize, and then make that - * saved value the actual value. */ - renumber(top_save); - openfile->totlines = openfile->filebot->lineno; - totsize_save += openfile->totsize; - openfile->totsize = totsize_save; - - /* Assign mark_begin to the line where the mark began before. */ - do_gotopos(mb_lineno_save, openfile->mark_begin_x, - current_y_save, 0); - openfile->mark_begin = openfile->current; - - /* Assign mark_begin_x to the location in mark_begin where the - * mark began before, adjusted for any shortening of the - * line. */ - openfile->mark_begin_x = openfile->current_x; - - /* Turn the mark back on. */ - openfile->mark_set = TRUE; - } -#endif - - /* Go back to the old position, and mark the file as modified. */ - do_gotopos(lineno_save, current_x_save, current_y_save, pww_save); - set_modified(); - - return NULL; -} - -void do_spell(void) -{ - int i; - FILE *temp_file; - char *temp = safe_tempfile(&temp_file); - const char *spell_msg; - - if (temp == NULL) { - statusbar(_("Could not create temp file: %s"), strerror(errno)); - return; - } - -#ifndef NANO_SMALL - if (openfile->mark_set) - i = write_marked_file(temp, temp_file, TRUE, FALSE); - else -#endif - i = write_file(temp, temp_file, TRUE, FALSE, FALSE); - - if (i == -1) { - statusbar(_("Error writing temp file: %s"), strerror(errno)); - free(temp); - return; - } - - spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) : - do_int_speller(temp); - unlink(temp); - free(temp); - - /* If the spell-checker printed any error messages onscreen, make - * sure that they're cleared off. */ - total_refresh(); - - if (spell_msg != NULL) { - if (errno == 0) - /* Don't display an error message of "Success". */ - statusbar(_("Spell checking failed: %s"), spell_msg); - else - statusbar(_("Spell checking failed: %s: %s"), spell_msg, - strerror(errno)); - } else - statusbar(_("Finished checking spelling")); -} -#endif /* !DISABLE_SPELLER */ - -#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING) -/* We are trying to break a chunk off line. We find the last blank such - * 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 - * blank in that group of blanks. The terminating '\0' counts as a - * blank, as does a '\n' if newline is TRUE. */ -ssize_t break_line(const char *line, ssize_t goal, bool newline) -{ - ssize_t blank_loc = -1; - /* Current tentative return value. Index of the last blank we - * found with short enough display width. */ - ssize_t cur_loc = 0; - /* Current index in line. */ - int line_len; - - assert(line != NULL); - - while (*line != '\0' && goal >= 0) { - size_t pos = 0; - - line_len = parse_mbchar(line, NULL, NULL, &pos); - - if (is_blank_mbchar(line) || (newline && *line == '\n')) { - blank_loc = cur_loc; - - if (newline && *line == '\n') - break; - } - - goal -= pos; - line += line_len; - cur_loc += line_len; - } - - if (goal >= 0) - /* In fact, the whole line displays shorter than goal. */ - return cur_loc; - - if (blank_loc == -1) { - /* No blank was found that was short enough. */ - bool found_blank = FALSE; - - while (*line != '\0') { - line_len = parse_mbchar(line, NULL, NULL, NULL); - - if (is_blank_mbchar(line) || (newline && *line == '\n')) { - if (!found_blank) - found_blank = TRUE; - } else if (found_blank) - return cur_loc - line_len; - - line += line_len; - cur_loc += line_len; - } - - return -1; - } - - /* Move to the last blank after blank_loc, if there is one. */ - line -= cur_loc; - line += blank_loc; - line_len = parse_mbchar(line, NULL, NULL, NULL); - line += line_len; - - while (*line != '\0' && (is_blank_mbchar(line) || - (newline && *line == '\n'))) { - line_len = parse_mbchar(line, NULL, NULL, NULL); - - line += line_len; - blank_loc += line_len; - } - - return blank_loc; -} -#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */ - -#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY) -/* 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) -{ - size_t len = 0; - char *blank_mb; - int blank_mb_len; - - assert(line != NULL); - - blank_mb = charalloc(mb_cur_max()); - - while (*line != '\0') { - blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL); - - if (!is_blank_mbchar(blank_mb)) - break; - - line += blank_mb_len; - len += blank_mb_len; - } - - free(blank_mb); - - return len; -} -#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */ - -#ifndef DISABLE_JUSTIFY -/* 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) -{ - char *end, *new_end, *new_paragraph_data; - size_t shift = 0; -#ifndef NANO_SMALL - size_t mark_shift = 0; -#endif - - /* These four asserts are assumptions about the input data. */ - assert(paragraph != NULL); - assert(paragraph->data != NULL); - assert(skip < strlen(paragraph->data)); - assert(!is_blank_mbchar(paragraph->data + skip)); - - 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; - - while (*end != '\0') { - int end_len; - - /* If this character is blank, make sure that it's a space with - * no blanks after it. */ - if (is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - *new_end = ' '; - new_end++; - end += end_len; - - while (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - end += end_len; - shift += end_len; - -#ifndef NANO_SMALL - /* Keep track of the change in the current line. */ - if (openfile->mark_set && openfile->mark_begin == - paragraph && openfile->mark_begin_x >= end - - paragraph->data) - mark_shift += end_len; -#endif - } - /* If this character is punctuation optionally followed by a - * bracket and then followed by blanks, make sure there are no - * more than two blanks after it, and make sure that the blanks - * are spaces. */ - } else if (mbstrchr(punct, end) != NULL) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - while (end_len > 0) { - *new_end = *end; - new_end++; - end++; - end_len--; - } - - if (*end != '\0' && mbstrchr(brackets, end) != NULL) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - while (end_len > 0) { - *new_end = *end; - new_end++; - end++; - end_len--; - } - } - - if (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - *new_end = ' '; - new_end++; - end += end_len; - } - - if (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - *new_end = ' '; - new_end++; - end += end_len; - } - - while (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - end += end_len; - shift += end_len; - -#ifndef NANO_SMALL - /* Keep track of the change in the current line. */ - if (openfile->mark_set && openfile->mark_begin == - paragraph && openfile->mark_begin_x >= end - - paragraph->data) - mark_shift += end_len; -#endif - } - /* If this character is neither blank nor punctuation, leave it - * alone. */ - } else { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - while (end_len > 0) { - *new_end = *end; - new_end++; - end++; - end_len--; - } - } - } - - assert(*end == '\0'); - - *new_end = *end; - - /* Make sure that there are no spaces at the end of the line. */ - while (new_end > new_paragraph_data + skip && - *(new_end - 1) == ' ') { - new_end--; - shift++; - } - - if (shift > 0) { - openfile->totsize -= shift; - null_at(&new_paragraph_data, new_end - new_paragraph_data); - free(paragraph->data); - paragraph->data = new_paragraph_data; - -#ifndef NANO_SMALL - /* Adjust the mark coordinates to compensate for the change in - * the current line. */ - if (openfile->mark_set && openfile->mark_begin == paragraph) { - openfile->mark_begin_x -= mark_shift; - if (openfile->mark_begin_x > new_end - new_paragraph_data) - openfile->mark_begin_x = new_end - new_paragraph_data; - } -#endif - } else - free(new_paragraph_data); -} - -/* The "quote part" of a line is the largest initial substring matching - * the quote string. This function returns the length of the quote part - * of the given line. - * - * Note that if !HAVE_REGEX_H then we match concatenated copies of - * quotestr. */ -size_t quote_length(const char *line) -{ -#ifdef HAVE_REGEX_H - 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; -#else /* !HAVE_REGEX_H */ - size_t qdepth = 0; - - /* Compute quote depth level. */ - while (strncmp(line + qdepth, quotestr, quotelen) == 0) - qdepth += quotelen; - return qdepth; -#endif /* !HAVE_REGEX_H */ -} - -/* a_line and b_line are lines of text. The quotation part of a_line is - * the first a_quote characters. Check that the quotation part of - * b_line is the same. */ -bool quotes_match(const char *a_line, size_t a_quote, const char - *b_line) -{ - /* Here is the assumption about a_quote. */ - assert(a_quote == quote_length(a_line)); - - return (a_quote == quote_length(b_line) && - strncmp(a_line, b_line, a_quote) == 0); -} - -/* We assume a_line and b_line have no quote part. Then, we return - * whether b_line could follow a_line in a paragraph. */ -bool indents_match(const char *a_line, size_t a_indent, const char - *b_line, size_t b_indent) -{ - assert(a_indent == indent_length(a_line)); - assert(b_indent == indent_length(b_line)); - - return (b_indent <= a_indent && - strncmp(a_line, b_line, b_indent) == 0); -} - -/* Is foo the beginning of a paragraph? - * - * A line of text consists of a "quote part", followed by an - * "indentation part", followed by text. The functions quote_length() - * and indent_length() calculate these parts. - * - * A line is "part of a paragraph" if it has a part not in the quote - * part or the indentation. - * - * A line is "the beginning of a paragraph" if it is part of a - * paragraph and - * 1) it is the top line of the file, or - * 2) the line above it is not part of a paragraph, or - * 3) the line above it does not have precisely the same quote - * part, or - * 4) the indentation of this line is not an initial substring of - * the indentation of the previous line, or - * 5) this line has no quote part and some indentation, and - * autoindent isn't turned on. - * The reason for number 5) is that if autoindent isn't turned on, - * then an indented line is expected to start a paragraph, as in - * books. Thus, nano can justify an indented paragraph only if - * autoindent is turned on. */ -bool begpar(const filestruct *const foo) -{ - size_t quote_len; - size_t indent_len; - size_t temp_id_len; - - /* Case 1). */ - if (foo->prev == NULL) - return TRUE; - - quote_len = quote_length(foo->data); - indent_len = indent_length(foo->data + quote_len); - - /* Not part of a paragraph. */ - if (foo->data[quote_len + indent_len] == '\0') - return FALSE; - - /* Case 3). */ - if (!quotes_match(foo->data, quote_len, foo->prev->data)) - return TRUE; - - temp_id_len = indent_length(foo->prev->data + quote_len); - - /* Case 2) or 5) or 4). */ - if (foo->prev->data[quote_len + temp_id_len] == '\0' || - (quote_len == 0 && indent_len > 0 -#ifndef NANO_SMALL - && !ISSET(AUTOINDENT) -#endif - ) || !indents_match(foo->prev->data + quote_len, temp_id_len, - foo->data + quote_len, indent_len)) - return TRUE; - - return FALSE; -} - -/* Is foo inside a paragraph? */ -bool inpar(const filestruct *const foo) -{ - size_t quote_len; - - if (foo == NULL) - return FALSE; - - quote_len = quote_length(foo->data); - - return foo->data[quote_len + indent_length(foo->data + - quote_len)] != '\0'; -} - -/* Put the next par_len lines, starting with first_line, into the - * justify buffer, leaving copies of those lines in place. Assume there - * are enough lines after first_line. Return the new copy of - * first_line. */ -filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t - quote_len) -{ - filestruct *top = first_line; - /* The top of the paragraph we're backing up. */ - filestruct *bot = first_line; - /* The bottom of the paragraph we're backing up. */ - size_t i; - /* Generic loop variable. */ - size_t current_x_save = openfile->current_x; - ssize_t fl_lineno_save = first_line->lineno; - ssize_t edittop_lineno_save = openfile->edittop->lineno; - ssize_t current_lineno_save = openfile->current->lineno; -#ifndef NANO_SMALL - bool old_mark_set = openfile->mark_set; - ssize_t mb_lineno_save = 0; - size_t mark_begin_x_save = 0; - - if (old_mark_set) { - mb_lineno_save = openfile->mark_begin->lineno; - mark_begin_x_save = openfile->mark_begin_x; - } -#endif - - /* Move bot down par_len lines to the newline after the last line of - * the paragraph. */ - for (i = par_len; i > 0; i--) - bot = bot->next; - - /* Move the paragraph from the current buffer's filestruct to the - * justify buffer. */ - move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0); - - /* Copy the paragraph back to the current buffer's filestruct from - * the justify buffer. */ - copy_from_filestruct(jusbuffer, jusbottom); - - /* Move upward from the last line of the paragraph to the first - * line, putting first_line, edittop, current, and mark_begin at the - * same lines in the copied paragraph that they had in the original - * paragraph. */ - top = openfile->current->prev; - for (i = par_len; i > 0; i--) { - if (top->lineno == fl_lineno_save) - first_line = top; - if (top->lineno == edittop_lineno_save) - openfile->edittop = top; - if (top->lineno == current_lineno_save) - openfile->current = top; -#ifndef NANO_SMALL - if (old_mark_set && top->lineno == mb_lineno_save) { - openfile->mark_begin = top; - openfile->mark_begin_x = mark_begin_x_save; - } -#endif - top = top->prev; - } - - /* Put current_x at the same place in the copied paragraph that it - * had in the original paragraph. */ - openfile->current_x = current_x_save; - - set_modified(); - - return first_line; -} - -/* Find the beginning of the current paragraph if we're in one, or the - * beginning of the next paragraph if we're not. Afterwards, save the - * quote length and paragraph length in *quote and *par. Return TRUE if - * we found a paragraph, or FALSE if there was an error or we didn't - * find a paragraph. - * - * See the comment at begpar() for more about when a line is the - * beginning of a paragraph. */ -bool find_paragraph(size_t *const quote, size_t *const par) -{ - size_t quote_len; - /* Length of the initial quotation of the paragraph we search - * for. */ - size_t par_len; - /* Number of lines in the paragraph we search for. */ - filestruct *current_save; - /* The line at the beginning of the paragraph we search for. */ - ssize_t current_y_save; - /* The y-coordinate at the beginning of the paragraph we search - * for. */ - -#ifdef HAVE_REGEX_H - if (quoterc != 0) { - statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr); - return FALSE; - } -#endif - - assert(openfile->current != NULL); - - /* Move back to the beginning of the current line. */ - openfile->current_x = 0; - openfile->placewewant = 0; - - /* Find the first line of the current or next paragraph. First, if - * the current line isn't in a paragraph, move forward to the line - * after the last line of the next paragraph. If we end up on the - * same line, or the line before that isn't in a paragraph, it means - * that there aren't any paragraphs left, so get out. Otherwise, - * move back to the last line of the paragraph. If the current line - * is in a paragraph and it isn't the first line of that paragraph, - * move back to the first line. */ - if (!inpar(openfile->current)) { - current_save = openfile->current; - - do_para_end(FALSE); - if (openfile->current == current_save || - !inpar(openfile->current->prev)) - return FALSE; - if (openfile->current->prev != NULL) - openfile->current = openfile->current->prev; - } - if (!begpar(openfile->current)) - do_para_begin(FALSE); - - /* Now current is the first line of the paragraph. Set quote_len to - * the quotation length of that line, and set par_len to the number - * of lines in this paragraph. */ - quote_len = quote_length(openfile->current->data); - current_save = openfile->current; - current_y_save = openfile->current_y; - do_para_end(FALSE); - par_len = openfile->current->lineno - current_save->lineno; - openfile->current = current_save; - openfile->current_y = current_y_save; - - /* Save the values of quote_len and par_len. */ - assert(quote != NULL && par != NULL); - - *quote = quote_len; - *par = par_len; - - return TRUE; -} - -/* If full_justify is TRUE, justify the entire file. Otherwise, justify - * the current paragraph. */ -void do_justify(bool full_justify) -{ - filestruct *first_par_line = NULL; - /* Will be the first line of the resulting justified paragraph. - * For restoring after unjustify. */ - filestruct *last_par_line; - /* Will be the line containing the newline after the last line - * of the result. Also for restoring after unjustify. */ - - /* We save these variables to be restored if the user unjustifies. - * Note that we don't need to save totlines. */ - size_t current_x_save = openfile->current_x; - size_t pww_save = openfile->placewewant; - ssize_t current_y_save = openfile->current_y; - bool modified_save = openfile->modified; - size_t totsize_save = openfile->totsize; - filestruct *edittop_save = openfile->edittop; - filestruct *current_save = openfile->current; -#ifndef NANO_SMALL - filestruct *mark_begin_save = openfile->mark_begin; - size_t mark_begin_x_save = openfile->mark_begin_x; -#endif - int kbinput; - bool meta_key, func_key, s_or_t, ran_func, finished; - - /* If we're justifying the entire file, start at the beginning. */ - if (full_justify) - openfile->current = openfile->fileage; - - last_par_line = openfile->current; - - while (TRUE) { - size_t i; - /* Generic loop variable. */ - size_t quote_len; - /* Length of the initial quotation of the paragraph we - * justify. */ - size_t indent_len; - /* Length of the initial indentation of the paragraph we - * justify. */ - size_t par_len; - /* Number of lines in the paragraph we justify. */ - ssize_t break_pos; - /* Where we will break lines. */ - char *indent_string; - /* The first indentation that doesn't match the initial - * indentation of the paragraph we justify. This is put at - * the beginning of every line broken off the first - * justified line of the paragraph. (Note that this works - * because a paragraph can only contain two indentations at - * most: the initial one, and a different one starting on a - * line after the first. See the comment at begpar() for - * more about when a line is part of a paragraph.) */ - - /* Find the first line of the paragraph to be justified. That - * is the start of this paragraph if we're in one, or the start - * of the next otherwise. Save the quote length and paragraph - * length (number of lines). Don't refresh the screen yet, - * since we'll do that after we justify. - * - * If the search failed, we do one of two things. If we're - * justifying the whole file, we've found at least one - * paragraph, and the search didn't leave us on the last line of - * the file, it means that we should justify all the way to the - * last line of the file, so set the last line of the text to be - * justified to the last line of the file and break out of the - * loop. Otherwise, it means that there are no paragraph(s) to - * justify, so refresh the screen and get out. */ - if (!find_paragraph("e_len, &par_len)) { - if (full_justify && first_par_line != NULL && - first_par_line != openfile->filebot) { - last_par_line = openfile->filebot; - break; - } else { - edit_refresh(); - return; - } - } - - /* If we haven't already done it, copy the original paragraph(s) - * to the justify buffer. */ - if (first_par_line == NULL) - first_par_line = backup_lines(openfile->current, - full_justify ? openfile->filebot->lineno - - openfile->current->lineno : par_len, quote_len); - - /* Initialize indent_string to a blank string. */ - indent_string = mallocstrcpy(NULL, ""); - - /* Find the first indentation in the paragraph that doesn't - * match the indentation of the first line, and save it in - * indent_string. If all the indentations are the same, save - * the indentation of the first line in indent_string. */ - { - const filestruct *indent_line = openfile->current; - bool past_first_line = FALSE; - - for (i = 0; i < par_len; i++) { - indent_len = quote_len + - indent_length(indent_line->data + quote_len); - - if (indent_len != strlen(indent_string)) { - indent_string = mallocstrncpy(indent_string, - indent_line->data, indent_len + 1); - indent_string[indent_len] = '\0'; - - if (past_first_line) - break; - } - - if (indent_line == openfile->current) - past_first_line = TRUE; - - indent_line = indent_line->next; - } - } - - /* Now tack all the lines of the paragraph together, skipping - * the quoting and indentation on all lines after the first. */ - for (i = 0; i < par_len - 1; i++) { - filestruct *next_line = openfile->current->next; - size_t line_len = strlen(openfile->current->data); - size_t next_line_len = - strlen(openfile->current->next->data); - - indent_len = quote_len + - indent_length(openfile->current->next->data + - quote_len); - - next_line_len -= indent_len; - openfile->totsize -= indent_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. */ - if (line_len > 0 && - openfile->current->data[line_len - 1] != ' ') { - line_len++; - openfile->current->data = - charealloc(openfile->current->data, - line_len + 1); - openfile->current->data[line_len - 1] = ' '; - openfile->current->data[line_len] = '\0'; - openfile->totsize++; - } - - openfile->current->data = - charealloc(openfile->current->data, line_len + - next_line_len + 1); - strcat(openfile->current->data, next_line->data + - indent_len); - - /* Don't destroy edittop! */ - if (openfile->edittop == next_line) - openfile->edittop = openfile->current; - -#ifndef NANO_SMALL - /* Adjust the mark coordinates to compensate for the change - * in the next line. */ - if (openfile->mark_set && openfile->mark_begin == - next_line) { - openfile->mark_begin = openfile->current; - openfile->mark_begin_x += line_len - indent_len; - } -#endif - - unlink_node(next_line); - delete_node(next_line); - - /* If we've removed the next line, we need to go through - * this line again. */ - i--; - - par_len--; - openfile->totlines--; - openfile->totsize--; - } - - /* Call justify_format() on the paragraph, which will remove - * excess spaces from it and change all blank characters to - * spaces. */ - justify_format(openfile->current, quote_len + - indent_length(openfile->current->data + quote_len)); - - while (par_len > 0 && - strlenpt(openfile->current->data) > fill) { - size_t line_len = strlen(openfile->current->data); - - indent_len = strlen(indent_string); - - /* If this line is too long, try to wrap it to the next line - * to make it short enough. */ - break_pos = - break_line(openfile->current->data + indent_len, fill - - strnlenpt(openfile->current->data, indent_len), FALSE); - - /* We can't break the line, or don't need to, so get out. */ - if (break_pos == -1 || break_pos + indent_len == line_len) - break; - - /* Move forward to the character after the indentation and - * just after the space. */ - break_pos += indent_len + 1; - - assert(break_pos <= line_len); - - /* Make a new line, and copy the text after where we're - * going to break this line to the beginning of the new - * line. */ - splice_node(openfile->current, - make_new_node(openfile->current), - openfile->current->next); - - /* If this paragraph is non-quoted, and autoindent isn't - * turned on, set the indentation length to zero so that the - * indentation is treated as part of the line. */ - if (quote_len == 0 -#ifndef NANO_SMALL - && !ISSET(AUTOINDENT) -#endif - ) - indent_len = 0; - - /* Copy the text after where we're going to break the - * current line to the next line. */ - openfile->current->next->data = charalloc(indent_len + 1 + - line_len - break_pos); - strncpy(openfile->current->next->data, indent_string, - indent_len); - strcpy(openfile->current->next->data + indent_len, - openfile->current->data + break_pos); - - par_len++; - openfile->totlines++; - openfile->totsize += indent_len + 1; - -#ifndef NANO_SMALL - /* Adjust the mark coordinates to compensate for the change - * in the current line. */ - if (openfile->mark_set && openfile->mark_begin == - openfile->current && openfile->mark_begin_x > - break_pos) { - openfile->mark_begin = openfile->current->next; - openfile->mark_begin_x -= break_pos - indent_len; - } -#endif - - /* Break the current line. */ - null_at(&openfile->current->data, break_pos); - - /* Go to the next line. */ - par_len--; - openfile->current_y++; - openfile->current = openfile->current->next; - } - - /* We're done breaking lines, so we don't need indent_string - * anymore. */ - free(indent_string); - - /* Go to the next line, the line after the last line of the - * paragraph. */ - openfile->current_y++; - openfile->current = openfile->current->next; - - /* We've just justified a paragraph. If we're not justifying the - * entire file, break out of the loop. Otherwise, continue the - * loop so that we justify all the paragraphs in the file. */ - if (!full_justify) - break; - } - - /* We are now done justifying the paragraph or the file, so clean - * up. totlines, totsize, and current_y have been maintained above. - * Set last_par_line to the new end of the paragraph, update - * fileage, and renumber since edit_refresh() needs the line numbers - * to be right (but only do the last two if we actually justified - * something). */ - last_par_line = openfile->current; - if (first_par_line != NULL) { - if (first_par_line->prev == NULL) - openfile->fileage = first_par_line; - renumber(first_par_line); - } - - edit_refresh(); - - statusbar(_("Can now UnJustify!")); - - /* If constant cursor position display is on, make sure the current - * cursor position will be properly displayed on the statusbar. */ - if (ISSET(CONST_UPDATE)) - do_cursorpos(TRUE); - - /* Display the shortcut list with UnJustify. */ - shortcut_init(TRUE); - display_main_list(); - - /* Now get a keystroke and see if it's unjustify. If not, put back - * the keystroke and return. */ - kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func, - &finished, FALSE); - - if (!meta_key && !func_key && s_or_t && - kbinput == NANO_UNJUSTIFY_KEY) { - /* Restore the justify we just did (ungrateful user!). */ - openfile->current = current_save; - openfile->current_x = current_x_save; - openfile->placewewant = pww_save; - openfile->current_y = current_y_save; - openfile->edittop = edittop_save; - - /* Splice the justify buffer back into the file, but only if we - * actually justified something. */ - if (first_par_line != NULL) { - filestruct *top_save; - - /* Partition the filestruct so that it contains only the - * text of the justified paragraph. */ - filepart = partition_filestruct(first_par_line, 0, - last_par_line, 0); - - /* Remove the text of the justified paragraph, and - * put the text in the justify buffer in its place. */ - free_filestruct(openfile->fileage); - openfile->fileage = jusbuffer; - openfile->filebot = jusbottom; - - top_save = openfile->fileage; - - /* Unpartition the filestruct so that it contains all the - * text again. Note that the justified paragraph has been - * replaced with the unjustified paragraph. */ - unpartition_filestruct(&filepart); - - /* Renumber starting with the beginning line of the old - * partition. */ - renumber(top_save); - - /* Restore variables from before the justify. */ - openfile->totsize = totsize_save; - openfile->totlines = openfile->filebot->lineno; -#ifndef NANO_SMALL - if (openfile->mark_set) { - openfile->mark_begin = mark_begin_save; - openfile->mark_begin_x = mark_begin_x_save; - } -#endif - openfile->modified = modified_save; - - /* Clear the justify buffer. */ - jusbuffer = NULL; - - if (!openfile->modified) - titlebar(NULL); - edit_refresh(); - } - } else { - unget_kbinput(kbinput, meta_key, func_key); - - /* Blow away the text in the justify buffer. */ - free_filestruct(jusbuffer); - jusbuffer = NULL; - } - - blank_statusbar(); - - /* Display the shortcut list with UnCut. */ - shortcut_init(FALSE); - display_main_list(); -} - -void do_justify_void(void) -{ - do_justify(FALSE); -} - -void do_full_justify(void) -{ - do_justify(TRUE); -} -#endif /* !DISABLE_JUSTIFY */ - void do_exit(void) { int i; diff --git a/src/nano.h b/src/nano.h index a131a9e7..bc0ad56c 100644 --- a/src/nano.h +++ b/src/nano.h @@ -90,8 +90,14 @@ /* Mark a string that will be sent to gettext() later. */ #include +#include #include #include +#include +#ifdef HAVE_REGEX_H +#include +#endif +#include /* If no vsnprintf(), use the version from glib 2.x. */ #ifndef HAVE_VSNPRINTF diff --git a/src/proto.h b/src/proto.h index 6e34971d..8bee99c1 100644 --- a/src/proto.h +++ b/src/proto.h @@ -24,16 +24,10 @@ #define PROTO_H 1 /* Externs. */ - -#include -#include -#include -#ifdef HAVE_REGEX_H -#include -#endif #include "nano.h" #ifndef DISABLE_WRAPJUSTIFY +extern ssize_t fill; extern ssize_t wrap_at; #endif extern int editwinrows; @@ -394,51 +388,14 @@ void version(void); int no_more_space(void); int no_help(void); void nano_disabled_msg(void); -#ifndef NANO_SMALL -void cancel_command(int signal); -bool execute_command(const char *command); -#endif void do_verbatim_input(void); void do_backspace(void); void do_delete(void); void do_tab(void); void do_enter(void); #ifndef NANO_SMALL -void do_word_count(void); void do_mark(void); #endif -#ifndef DISABLE_WRAPPING -void wrap_reset(void); -bool do_wrap(filestruct *line); -#endif -#ifndef DISABLE_SPELLER -bool do_int_spell_fix(const char *word); -const char *do_int_speller(const char *tempfile_name); -const char *do_alt_speller(char *tempfile_name); -void do_spell(void); -#endif -#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING) -ssize_t break_line(const char *line, ssize_t goal, bool newline); -#endif -#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY) -size_t indent_length(const char *line); -#endif -#ifndef DISABLE_JUSTIFY -void justify_format(filestruct *paragraph, size_t skip); -size_t quote_length(const char *line); -bool quotes_match(const char *a_line, size_t a_quote, const char - *b_line); -bool indents_match(const char *a_line, size_t a_indent, const char - *b_line, size_t b_indent); -bool begpar(const filestruct *const foo); -bool inpar(const filestruct *const foo); -filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t - quote_len); -bool find_paragraph(size_t *const quote, size_t *const par); -void do_justify(bool full_justify); -void do_justify_void(void); -void do_full_justify(void); -#endif /* !DISABLE_JUSTIFY */ void do_exit(void); void signal_init(void); void handle_hupterm(int signal); @@ -491,7 +448,6 @@ void not_found_msg(const char *str); void search_abort(void); void search_init_globals(void); int search_init(bool replacing, bool use_answer); -bool is_whole_word(size_t pos, const char *buf, const char *word); bool findnextstr(bool can_display_wrap, bool wholeword, bool no_sameline, const filestruct *begin, size_t begin_x, const char *needle, size_t *needle_len); @@ -535,14 +491,48 @@ char *get_history_completion(filestruct **h, const char *s, size_t len); #endif #endif /* !NANO_SMALL */ +/* Public functions in text.c. */ +#ifndef NANO_SMALL +void cancel_command(int signal); +bool execute_command(const char *command); +#endif +#ifndef DISABLE_WRAPPING +void wrap_reset(void); +bool do_wrap(filestruct *line); +#endif +#ifndef DISABLE_SPELLER +bool do_int_spell_fix(const char *word); +const char *do_int_speller(const char *tempfile_name); +const char *do_alt_speller(char *tempfile_name); +void do_spell(void); +#endif +#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING) +ssize_t break_line(const char *line, ssize_t goal, bool newline); +#endif +#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY) +size_t indent_length(const char *line); +#endif +#ifndef DISABLE_JUSTIFY +void justify_format(filestruct *paragraph, size_t skip); +size_t quote_length(const char *line); +bool quotes_match(const char *a_line, size_t a_quote, const char + *b_line); +bool indents_match(const char *a_line, size_t a_indent, const char + *b_line, size_t b_indent); +bool begpar(const filestruct *const foo); +bool inpar(const filestruct *const foo); +filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t + quote_len); +bool find_paragraph(size_t *const quote, size_t *const par); +void do_justify(bool full_justify); +void do_justify_void(void); +void do_full_justify(void); +#endif /* !DISABLE_JUSTIFY */ +#ifndef NANO_SMALL +void do_word_count(void); +#endif + /* Public functions in utils.c. */ -#ifdef HAVE_REGEX_H -#ifdef BROKEN_REGEXEC -int safe_regexec(const regex_t *preg, const char *string, size_t nmatch, - regmatch_t pmatch[], int eflags); -#endif -int regexp_bol_or_eol(const regex_t *preg, const char *string); -#endif int digits(size_t n); void get_homedir(void); bool parse_num(const char *str, ssize_t *val); @@ -559,6 +549,14 @@ ssize_t ngetline(char **lineptr, size_t *n, FILE *stream); ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream); #endif #endif /* !NANO_SMALL && ENABLE_NANORC */ +#ifdef HAVE_REGEX_H +#ifdef BROKEN_REGEXEC +int safe_regexec(const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags); +#endif +int regexp_bol_or_eol(const regex_t *preg, const char *string); +#endif +bool is_whole_word(size_t pos, const char *buf, const char *word); const char *strstrwrapper(const char *haystack, const char *needle, const char *start); void nperror(const char *s); diff --git a/src/rcfile.c b/src/rcfile.c index ac3559e8..eb398767 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -24,14 +24,12 @@ #include #endif -#include #include #include #include #include #include #include -#include #include "proto.h" #ifdef ENABLE_NANORC diff --git a/src/search.c b/src/search.c index fcd62b93..53d3ac35 100644 --- a/src/search.c +++ b/src/search.c @@ -24,13 +24,11 @@ #include #endif -#include #include #include #include #include #include -#include #include "proto.h" static bool search_last_line = FALSE; @@ -256,30 +254,6 @@ int search_init(bool replacing, bool use_answer) return 0; } -bool is_whole_word(size_t pos, const char *buf, const char *word) -{ - char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max()); - size_t word_end = pos + strlen(word); - bool retval; - - assert(buf != NULL && pos <= strlen(buf) && word != NULL); - - parse_mbchar(buf + move_mbleft(buf, pos), p, NULL, NULL); - parse_mbchar(buf + word_end, r, NULL, NULL); - - /* If we're at the beginning of the line or the character before the - * word isn't a non-punctuation "word" character, and if we're at - * the end of the line or the character after the word isn't a - * non-punctuation "word" character, we have a whole word. */ - retval = (pos == 0 || !is_word_mbchar(p, FALSE)) && - (word_end == strlen(buf) || !is_word_mbchar(r, FALSE)); - - free(p); - free(r); - - return retval; -} - /* Look for needle, starting at (current, current_x). If no_sameline is * TRUE, skip over begin when looking for needle. begin is the line * where we first started searching, at column begin_x. If diff --git a/src/text.c b/src/text.c new file mode 100644 index 00000000..e2a03809 --- /dev/null +++ b/src/text.c @@ -0,0 +1,1915 @@ +/* $Id$ */ +/************************************************************************** + * text.c * + * * + * Copyright (C) 2005 Chris Allegretta * + * This program 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 2, or (at your option) * + * any later version. * + * * + * This program 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * + * 02110-1301, USA. * + * * + **************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "proto.h" + +#ifndef NANO_SMALL +static pid_t pid; /* The PID of the newly forked process + * in execute_command(). It must be + * global because the cancel_command() + * signal handler needs it. */ +#endif +#ifndef DISABLE_WRAPPING +static bool same_line_wrap = FALSE; + /* Should we prepend wrapped text to the next line? */ +#endif +#ifndef DISABLE_JUSTIFY +static filestruct *jusbottom = NULL; + /* Pointer to end of justify buffer. */ +#endif + +#ifndef NANO_SMALL +void cancel_command(int signal) +{ + if (kill(pid, SIGKILL) == -1) + nperror("kill"); +} + +/* Return TRUE on success. */ +bool execute_command(const char *command) +{ + int fd[2]; + FILE *f; + struct sigaction oldaction, newaction; + /* Original and temporary handlers for SIGINT. */ + bool sig_failed = FALSE; + /* Did sigaction() fail without changing the signal handlers? */ + + /* Make our pipes. */ + if (pipe(fd) == -1) { + statusbar(_("Could not pipe")); + return FALSE; + } + + /* Fork a child. */ + if ((pid = fork()) == 0) { + close(fd[0]); + dup2(fd[1], fileno(stdout)); + dup2(fd[1], fileno(stderr)); + + /* If execl() returns at all, there was an error. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + /* Else continue as parent. */ + close(fd[1]); + + if (pid == -1) { + close(fd[0]); + statusbar(_("Could not fork")); + return FALSE; + } + + /* Before we start reading the forked command's output, we set + * things up so that Ctrl-C will cancel the new process. */ + + /* Enable interpretation of the special control keys so that we get + * SIGINT when Ctrl-C is pressed. */ + enable_signals(); + + if (sigaction(SIGINT, NULL, &newaction) == -1) { + sig_failed = TRUE; + nperror("sigaction"); + } else { + newaction.sa_handler = cancel_command; + if (sigaction(SIGINT, &newaction, &oldaction) == -1) { + sig_failed = TRUE; + nperror("sigaction"); + } + } + + /* Note that now oldaction is the previous SIGINT signal handler, + * to be restored later. */ + + f = fdopen(fd[0], "rb"); + if (f == NULL) + nperror("fdopen"); + + read_file(f, "stdin"); + + /* If multibuffer mode is on, we could be here in view mode. If so, + * don't set the modification flag. */ + if (!ISSET(VIEW_MODE)) + set_modified(); + + if (wait(NULL) == -1) + nperror("wait"); + + if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1) + nperror("sigaction"); + + /* Disable interpretation of the special control keys so that we can + * use Ctrl-C for other things. */ + disable_signals(); + + return TRUE; +} +#endif /* !NANO_SMALL */ + +#ifndef DISABLE_WRAPPING +void wrap_reset(void) +{ + same_line_wrap = FALSE; +} + +/* We wrap the given line. Precondition: we assume the cursor has been + * moved forward since the last typed character. Return value: whether + * we wrapped. */ +bool do_wrap(filestruct *line) +{ + size_t line_len; + /* Length of the line we wrap. */ + ssize_t wrap_loc; + /* Index of line->data where we wrap. */ +#ifndef NANO_SMALL + const char *indent_string = NULL; + /* Indentation to prepend to the new line. */ + size_t indent_len = 0; /* The length of indent_string. */ +#endif + const char *after_break; /* The text after the wrap point. */ + size_t after_break_len; /* The length of after_break. */ + bool wrapping = FALSE; /* Do we prepend to the next line? */ + const char *next_line = NULL; + /* The next line, minus indentation. */ + size_t next_line_len = 0; /* The length of next_line. */ + char *new_line = NULL; /* The line we create. */ + size_t new_line_len = 0; /* The eventual length of new_line. */ + + /* 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 + * 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! */ + + assert(line != NULL && line->data != NULL); + + /* Save the length of the line. */ + line_len = strlen(line->data); + + /* Find the last blank where we can break the line. */ + wrap_loc = break_line(line->data, fill, FALSE); + + /* 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; + +#ifndef NANO_SMALL + /* If autoindent is turned on, and we're on the character just after + * the indentation, we don't wrap. */ + if (ISSET(AUTOINDENT)) { + /* Get the indentation of this line. */ + indent_string = line->data; + indent_len = indent_length(indent_string); + + if (wrap_loc == indent_len) + return FALSE; + } +#endif + + /* 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 same_line_wrap flag is + * set. */ + + /* after_break is the text that will be wrapped to the next line. */ + after_break = line->data + wrap_loc; + after_break_len = line_len - wrap_loc; + + assert(strlen(after_break) == after_break_len); + + /* We prepend the wrapped text to the next line, if the + * same_line_wrap flag is set, there is a next line, and prepending + * would not make the line too long. */ + if (same_line_wrap && line->next != NULL) { + const char *end = after_break + move_mbleft(after_break, + after_break_len); + + /* If after_break doesn't end in a blank, make sure it ends in a + * space. */ + if (!is_blank_mbchar(end)) { + line_len++; + line->data = charealloc(line->data, line_len + 1); + line->data[line_len - 1] = ' '; + line->data[line_len] = '\0'; + after_break = line->data + wrap_loc; + after_break_len++; + openfile->totsize++; + } + + next_line = line->next->data; + next_line_len = strlen(next_line); + + if (after_break_len + next_line_len <= fill) { + wrapping = TRUE; + new_line_len += next_line_len; + } + } + + /* new_line_len is now the length of the text that will be wrapped + * to the next line, plus (if we're prepending to it) the length of + * the text of the next line. */ + new_line_len += after_break_len; + +#ifndef NANO_SMALL + if (ISSET(AUTOINDENT)) { + if (wrapping) { + /* If we're wrapping, the indentation will come from the + * next line. */ + indent_string = next_line; + indent_len = indent_length(indent_string); + next_line += indent_len; + } else { + /* Otherwise, it will come from this line, in which case + * we should increase new_line_len to make room for it. */ + new_line_len += indent_len; + openfile->totsize += mbstrnlen(indent_string, indent_len); + } + } +#endif + + /* Now we allocate the new line and copy the text into it. */ + new_line = charalloc(new_line_len + 1); + new_line[0] = '\0'; + +#ifndef NANO_SMALL + if (ISSET(AUTOINDENT)) { + /* Copy the indentation. */ + strncpy(new_line, indent_string, indent_len); + new_line[indent_len] = '\0'; + new_line_len += indent_len; + } +#endif + + /* Copy all the text after the wrap point of the current line. */ + strcat(new_line, after_break); + + /* Break the current line at the wrap point. */ + null_at(&line->data, wrap_loc); + + if (wrapping) { + /* If we're wrapping, copy the text from the next line, minus + * the indentation that we already copied above. */ + strcat(new_line, next_line); + + free(line->next->data); + line->next->data = new_line; + } else { + /* Otherwise, make a new line and copy the text after where we + * broke this line to the beginning of the new line. */ + splice_node(openfile->current, make_new_node(openfile->current), + openfile->current->next); + + openfile->current->next->data = new_line; + + openfile->totlines++; + openfile->totsize++; + } + + /* Step 3, clean up. Reposition the cursor and mark, and do some + * other sundry things. */ + + /* Set the same_line_wrap flag, so that later wraps of this line + * will be prepended to the next line. */ + same_line_wrap = TRUE; + + /* Each line knows its line number. We recalculate these if we + * inserted a new line. */ + if (!wrapping) + renumber(line); + + /* If the cursor was after the break point, we must move it. We + * also clear the same_line_wrap flag in this case. */ + if (openfile->current_x > wrap_loc) { + same_line_wrap = FALSE; + openfile->current = openfile->current->next; + openfile->current_x -= wrap_loc +#ifndef NANO_SMALL + - indent_len +#endif + ; + openfile->placewewant = xplustabs(); + } + +#ifndef NANO_SMALL + /* If the mark was on this line after the wrap point, we move it + * down. If it was on the next line and we wrapped onto that line, + * we move it right. */ + if (openfile->mark_set) { + if (openfile->mark_begin == line && openfile->mark_begin_x > + wrap_loc) { + openfile->mark_begin = line->next; + openfile->mark_begin_x -= wrap_loc - indent_len + 1; + } else if (wrapping && openfile->mark_begin == line->next) + openfile->mark_begin_x += after_break_len; + } +#endif + + return TRUE; +} +#endif /* !DISABLE_WRAPPING */ + +#ifndef DISABLE_SPELLER +/* A word is misspelled in the file. Let the user replace it. We + * return FALSE if the user cancels. */ +bool do_int_spell_fix(const char *word) +{ + char *save_search, *save_replace; + size_t match_len, current_x_save = openfile->current_x; + size_t pww_save = openfile->placewewant; + filestruct *edittop_save = openfile->edittop; + filestruct *current_save = openfile->current; + /* Save where we are. */ + bool canceled = FALSE; + /* The return value. */ + bool case_sens_set = ISSET(CASE_SENSITIVE); +#ifndef NANO_SMALL + bool backwards_search_set = ISSET(BACKWARDS_SEARCH); +#endif +#ifdef HAVE_REGEX_H + bool regexp_set = ISSET(USE_REGEXP); +#endif +#ifndef NANO_SMALL + bool old_mark_set = openfile->mark_set; + bool added_magicline = FALSE; + /* Whether we added a magicline after filebot. */ + 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; +#endif + + /* Make sure spell-check is case sensitive. */ + SET(CASE_SENSITIVE); + +#ifndef NANO_SMALL + /* Make sure spell-check goes forward only. */ + UNSET(BACKWARDS_SEARCH); +#endif +#ifdef HAVE_REGEX_H + /* Make sure spell-check doesn't use regular expressions. */ + UNSET(USE_REGEXP); +#endif + + /* Save the current search/replace strings. */ + search_init_globals(); + save_search = last_search; + save_replace = last_replace; + + /* Set the search/replace strings to the misspelled word. */ + last_search = mallocstrcpy(NULL, word); + last_replace = mallocstrcpy(NULL, word); + +#ifndef NANO_SMALL + if (old_mark_set) { + /* If the mark is on, partition the filestruct so that it + * contains only the marked text, keep track of whether the text + * will have a magicline added when we're done correcting + * misspelled words, and turn the mark off. */ + mark_order((const filestruct **)&top, &top_x, + (const filestruct **)&bot, &bot_x, &right_side_up); + filepart = partition_filestruct(top, top_x, bot, bot_x); + added_magicline = (openfile->filebot->data[0] != '\0'); + openfile->mark_set = FALSE; + } +#endif + + /* Start from the top of the file. */ + openfile->edittop = openfile->fileage; + openfile->current = openfile->fileage; + openfile->current_x = (size_t)-1; + openfile->placewewant = 0; + + /* Find the first whole-word occurrence of word. */ + findnextstr_wrap_reset(); + while (findnextstr(TRUE, TRUE, FALSE, openfile->fileage, 0, word, + &match_len)) { + if (is_whole_word(openfile->current_x, openfile->current->data, + word)) { + size_t xpt = xplustabs(); + char *exp_word = display_string(openfile->current->data, + xpt, strnlenpt(openfile->current->data, + openfile->current_x + match_len) - xpt, FALSE); + + edit_refresh(); + + do_replace_highlight(TRUE, exp_word); + + /* Allow all instances of the word to be corrected. */ + canceled = (statusq(FALSE, spell_list, word, +#ifndef NANO_SMALL + NULL, +#endif + _("Edit a replacement")) == -1); + + do_replace_highlight(FALSE, exp_word); + + free(exp_word); + + if (!canceled && strcmp(word, answer) != 0) { + openfile->current_x--; + do_replace_loop(word, openfile->current, + &openfile->current_x, TRUE, &canceled); + } + + break; + } + } + +#ifndef NANO_SMALL + if (old_mark_set) { + /* If the mark was on and we added a magicline, remove it + * now. */ + if (added_magicline) + remove_magicline(); + + /* Put the beginning and the end of the mark at the beginning + * and the end of the spell-checked text. */ + if (openfile->fileage == openfile->filebot) + bot_x += top_x; + if (right_side_up) { + openfile->mark_begin_x = top_x; + current_x_save = bot_x; + } else { + current_x_save = top_x; + openfile->mark_begin_x = bot_x; + } + + /* Unpartition the filestruct so that it contains all the text + * again, and turn the mark back on. */ + unpartition_filestruct(&filepart); + openfile->mark_set = TRUE; + } +#endif + + /* Restore the search/replace strings. */ + free(last_search); + last_search = save_search; + free(last_replace); + last_replace = save_replace; + + /* Restore where we were. */ + openfile->edittop = edittop_save; + openfile->current = current_save; + openfile->current_x = current_x_save; + openfile->placewewant = pww_save; + + /* Restore case sensitivity setting. */ + if (!case_sens_set) + UNSET(CASE_SENSITIVE); + +#ifndef NANO_SMALL + /* Restore search/replace direction. */ + if (backwards_search_set) + SET(BACKWARDS_SEARCH); +#endif +#ifdef HAVE_REGEX_H + /* Restore regular expression usage setting. */ + if (regexp_set) + SET(USE_REGEXP); +#endif + + return !canceled; +} + +/* Integrated spell checking using the spell program, filtered through + * the sort and uniq programs. Return NULL for normal termination, + * and the error string otherwise. */ +const char *do_int_speller(const char *tempfile_name) +{ + 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; + + /* Create all three pipes up front. */ + if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || + pipe(uniq_fd) == -1) + return _("Could not create pipe"); + + statusbar(_("Creating misspelled word list, please wait...")); + + /* A new process to run spell in. */ + if ((pid_spell = fork()) == 0) { + /* Child continues (i.e, future spell process). */ + close(spell_fd[0]); + + /* Replace the standard input with the temp file. */ + if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1) + goto close_pipes_and_exit; + + if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) + goto close_pipes_and_exit; + + 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; + + close(spell_fd[1]); + + /* 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. */ + close(spell_fd[1]); + + /* A new process to run sort in. */ + if ((pid_sort = fork()) == 0) { + /* Child continues (i.e, future spell 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; + + close(spell_fd[0]); + + /* Send sort's standard output to the new pipe. */ + if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO) + goto close_pipes_and_exit; + + close(sort_fd[1]); + + /* Start the sort program. Use -f to remove mixed case. If + * this isn't portable, let me know. */ + execlp("sort", "sort", "-f", NULL); + + /* This should not be reached if sort is found. */ + exit(1); + } + + close(spell_fd[0]); + close(sort_fd[1]); + + /* 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; + + close(sort_fd[0]); + + /* Send uniq's standard output to the new pipe. */ + if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO) + goto close_pipes_and_exit; + + close(uniq_fd[1]); + + /* Start the uniq program; we are using PATH. */ + execlp("uniq", "uniq", NULL); + + /* This should not be reached if uniq is found. */ + exit(1); + } + + close(sort_fd[0]); + close(uniq_fd[1]); + + /* 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"); + } + + /* 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"); + } + + /* 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); + + 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; + } + + *read_buff_ptr = '\0'; + close(uniq_fd[0]); + + /* Process the spelling errors. */ + 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) { + if (!do_int_spell_fix(read_buff_word)) { + read_buff_word = read_buff_ptr; + break; + } + } + read_buff_word = read_buff_ptr + 1; + } + read_buff_ptr++; + } + + /* Special case: the last word doesn't end with '\r' or '\n'. */ + if (read_buff_word != read_buff_ptr) + do_int_spell_fix(read_buff_word); + + free(read_buff); + replace_abort(); + edit_refresh(); + + /* Process the end of the spell process. */ + waitpid(pid_spell, &spell_status, 0); + waitpid(pid_sort, &sort_status, 0); + waitpid(pid_uniq, &uniq_status, 0); + + if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status)) + return _("Error invoking \"spell\""); + + if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status)) + return _("Error invoking \"sort -f\""); + + if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status)) + return _("Error invoking \"uniq\""); + + /* Otherwise... */ + return NULL; + + close_pipes_and_exit: + /* Don't leak any handles. */ + close(tempfile_fd); + 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); +} + +/* External spell checking. Return value: NULL for normal termination, + * otherwise the error string. */ +const char *do_alt_speller(char *tempfile_name) +{ + int alt_spell_status; + size_t current_x_save = openfile->current_x; + size_t pww_save = openfile->placewewant; + ssize_t current_y_save = openfile->current_y; + ssize_t lineno_save = openfile->current->lineno; + pid_t pid_spell; + char *ptr; + static int arglen = 3; + static char **spellargs = NULL; + FILE *f; +#ifndef NANO_SMALL + bool old_mark_set = openfile->mark_set; + bool added_magicline = FALSE; + /* Whether we added a magicline after filebot. */ + 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; + ssize_t mb_lineno_save = 0; + /* We're going to close the current file, and open the output of + * the alternate spell command. The line that mark_begin points + * to will be freed, so we save the line number and restore it + * afterwards. */ + size_t totsize_save = openfile->totsize; + /* Our saved value of totsize, used when we spell-check a marked + * selection. */ + + if (old_mark_set) { + /* If the mark is on, save the number of the line it starts on, + * and then turn the mark off. */ + mb_lineno_save = openfile->mark_begin->lineno; + openfile->mark_set = FALSE; + } +#endif + + endwin(); + + /* Set up an argument list to pass execvp(). */ + if (spellargs == NULL) { + spellargs = (char **)nmalloc(arglen * sizeof(char *)); + + spellargs[0] = strtok(alt_speller, " "); + while ((ptr = strtok(NULL, " ")) != NULL) { + arglen++; + spellargs = (char **)nrealloc(spellargs, arglen * + sizeof(char *)); + spellargs[arglen - 3] = ptr; + } + spellargs[arglen - 1] = NULL; + } + spellargs[arglen - 2] = tempfile_name; + + /* Start a new process for the alternate speller. */ + if ((pid_spell = fork()) == 0) { + /* Start alternate spell program; we are using PATH. */ + execvp(spellargs[0], spellargs); + + /* Should not be reached, if alternate speller is found!!! */ + exit(1); + } + + /* If we couldn't fork, get out. */ + if (pid_spell < 0) + return _("Could not fork"); + + /* Wait for alternate speller to complete. */ + wait(&alt_spell_status); + + refresh(); + + /* Restore the terminal to its previous state. */ + terminal_init(); + + /* Turn the cursor back on for sure. */ + curs_set(1); + + if (!WIFEXITED(alt_spell_status) || + WEXITSTATUS(alt_spell_status) != 0) { + char *altspell_error; + char *invoke_error = _("Error invoking \"%s\""); + +#ifndef NANO_SMALL + /* Turn the mark back on if it was on before. */ + openfile->mark_set = old_mark_set; +#endif + + altspell_error = + charalloc(strlen(invoke_error) + + strlen(alt_speller) + 1); + sprintf(altspell_error, invoke_error, alt_speller); + return altspell_error; + } + +#ifndef NANO_SMALL + if (old_mark_set) { + /* If the mark was on, partition the filestruct so that it + * contains only the marked text, and keep track of whether the + * temp file (which should contain the spell-checked marked + * text) will have a magicline added when it's reloaded. */ + mark_order((const filestruct **)&top, &top_x, + (const filestruct **)&bot, &bot_x, &right_side_up); + filepart = partition_filestruct(top, top_x, bot, bot_x); + added_magicline = (openfile->filebot->data[0] != '\0'); + + /* Get the number of characters in the marked text, and subtract + * it from the saved value of totsize. */ + totsize_save -= get_totsize(top, bot); + } +#endif + + /* Set up the window size. */ + window_size_init(); + + /* Reinitialize the text of the current buffer. */ + free_filestruct(openfile->fileage); + initialize_buffer_text(); + + /* Reload the temp file. Open it, read it into the current buffer, + * and move back to the first line of the buffer. */ + open_file(tempfile_name, FALSE, &f); + read_file(f, tempfile_name); + openfile->current = openfile->fileage; + +#ifndef NANO_SMALL + if (old_mark_set) { + filestruct *top_save = openfile->fileage; + + /* If the mark was on and we added a magicline, remove it + * now. */ + if (added_magicline) + remove_magicline(); + + /* Put the beginning and the end of the mark at the beginning + * and the end of the spell-checked text. */ + if (openfile->fileage == openfile->filebot) + bot_x += top_x; + if (right_side_up) { + openfile->mark_begin_x = top_x; + current_x_save = bot_x; + } else { + current_x_save = top_x; + openfile->mark_begin_x = bot_x; + } + + /* Unpartition the filestruct so that it contains all the text + * again. Note that we've replaced the marked text originally + * in the partition with the spell-checked marked text in the + * temp file. */ + unpartition_filestruct(&filepart); + + /* Renumber starting with the beginning line of the old + * partition. Also set totlines to the new number of lines in + * the file, add the number of characters in the spell-checked + * marked text to the saved value of totsize, and then make that + * saved value the actual value. */ + renumber(top_save); + openfile->totlines = openfile->filebot->lineno; + totsize_save += openfile->totsize; + openfile->totsize = totsize_save; + + /* Assign mark_begin to the line where the mark began before. */ + do_gotopos(mb_lineno_save, openfile->mark_begin_x, + current_y_save, 0); + openfile->mark_begin = openfile->current; + + /* Assign mark_begin_x to the location in mark_begin where the + * mark began before, adjusted for any shortening of the + * line. */ + openfile->mark_begin_x = openfile->current_x; + + /* Turn the mark back on. */ + openfile->mark_set = TRUE; + } +#endif + + /* Go back to the old position, and mark the file as modified. */ + do_gotopos(lineno_save, current_x_save, current_y_save, pww_save); + set_modified(); + + return NULL; +} + +void do_spell(void) +{ + int i; + FILE *temp_file; + char *temp = safe_tempfile(&temp_file); + const char *spell_msg; + + if (temp == NULL) { + statusbar(_("Could not create temp file: %s"), strerror(errno)); + return; + } + +#ifndef NANO_SMALL + if (openfile->mark_set) + i = write_marked_file(temp, temp_file, TRUE, FALSE); + else +#endif + i = write_file(temp, temp_file, TRUE, FALSE, FALSE); + + if (i == -1) { + statusbar(_("Error writing temp file: %s"), strerror(errno)); + free(temp); + return; + } + + spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) : + do_int_speller(temp); + unlink(temp); + free(temp); + + /* If the spell-checker printed any error messages onscreen, make + * sure that they're cleared off. */ + total_refresh(); + + if (spell_msg != NULL) { + if (errno == 0) + /* Don't display an error message of "Success". */ + statusbar(_("Spell checking failed: %s"), spell_msg); + else + statusbar(_("Spell checking failed: %s: %s"), spell_msg, + strerror(errno)); + } else + statusbar(_("Finished checking spelling")); +} +#endif /* !DISABLE_SPELLER */ + +#if !defined(DISABLE_HELP) || !defined(DISABLE_JUSTIFY) || !defined(DISABLE_WRAPPING) +/* We are trying to break a chunk off line. We find the last blank such + * 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 + * blank in that group of blanks. The terminating '\0' counts as a + * blank, as does a '\n' if newline is TRUE. */ +ssize_t break_line(const char *line, ssize_t goal, bool newline) +{ + ssize_t blank_loc = -1; + /* Current tentative return value. Index of the last blank we + * found with short enough display width. */ + ssize_t cur_loc = 0; + /* Current index in line. */ + int line_len; + + assert(line != NULL); + + while (*line != '\0' && goal >= 0) { + size_t pos = 0; + + line_len = parse_mbchar(line, NULL, NULL, &pos); + + if (is_blank_mbchar(line) || (newline && *line == '\n')) { + blank_loc = cur_loc; + + if (newline && *line == '\n') + break; + } + + goal -= pos; + line += line_len; + cur_loc += line_len; + } + + if (goal >= 0) + /* In fact, the whole line displays shorter than goal. */ + return cur_loc; + + if (blank_loc == -1) { + /* No blank was found that was short enough. */ + bool found_blank = FALSE; + + while (*line != '\0') { + line_len = parse_mbchar(line, NULL, NULL, NULL); + + if (is_blank_mbchar(line) || (newline && *line == '\n')) { + if (!found_blank) + found_blank = TRUE; + } else if (found_blank) + return cur_loc - line_len; + + line += line_len; + cur_loc += line_len; + } + + return -1; + } + + /* Move to the last blank after blank_loc, if there is one. */ + line -= cur_loc; + line += blank_loc; + line_len = parse_mbchar(line, NULL, NULL, NULL); + line += line_len; + + while (*line != '\0' && (is_blank_mbchar(line) || + (newline && *line == '\n'))) { + line_len = parse_mbchar(line, NULL, NULL, NULL); + + line += line_len; + blank_loc += line_len; + } + + return blank_loc; +} +#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */ + +#if !defined(NANO_SMALL) || !defined(DISABLE_JUSTIFY) +/* 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) +{ + size_t len = 0; + char *blank_mb; + int blank_mb_len; + + assert(line != NULL); + + blank_mb = charalloc(mb_cur_max()); + + while (*line != '\0') { + blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL); + + if (!is_blank_mbchar(blank_mb)) + break; + + line += blank_mb_len; + len += blank_mb_len; + } + + free(blank_mb); + + return len; +} +#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */ + +#ifndef DISABLE_JUSTIFY +/* 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) +{ + char *end, *new_end, *new_paragraph_data; + size_t shift = 0; +#ifndef NANO_SMALL + size_t mark_shift = 0; +#endif + + /* These four asserts are assumptions about the input data. */ + assert(paragraph != NULL); + assert(paragraph->data != NULL); + assert(skip < strlen(paragraph->data)); + assert(!is_blank_mbchar(paragraph->data + skip)); + + 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; + + while (*end != '\0') { + int end_len; + + /* If this character is blank, make sure that it's a space with + * no blanks after it. */ + if (is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + *new_end = ' '; + new_end++; + end += end_len; + + while (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + end += end_len; + shift += end_len; + +#ifndef NANO_SMALL + /* Keep track of the change in the current line. */ + if (openfile->mark_set && openfile->mark_begin == + paragraph && openfile->mark_begin_x >= end - + paragraph->data) + mark_shift += end_len; +#endif + } + /* If this character is punctuation optionally followed by a + * bracket and then followed by blanks, make sure there are no + * more than two blanks after it, and make sure that the blanks + * are spaces. */ + } else if (mbstrchr(punct, end) != NULL) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } + + if (*end != '\0' && mbstrchr(brackets, end) != NULL) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } + } + + if (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + *new_end = ' '; + new_end++; + end += end_len; + } + + if (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + *new_end = ' '; + new_end++; + end += end_len; + } + + while (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + end += end_len; + shift += end_len; + +#ifndef NANO_SMALL + /* Keep track of the change in the current line. */ + if (openfile->mark_set && openfile->mark_begin == + paragraph && openfile->mark_begin_x >= end - + paragraph->data) + mark_shift += end_len; +#endif + } + /* If this character is neither blank nor punctuation, leave it + * alone. */ + } else { + end_len = parse_mbchar(end, NULL, NULL, NULL); + + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } + } + } + + assert(*end == '\0'); + + *new_end = *end; + + /* Make sure that there are no spaces at the end of the line. */ + while (new_end > new_paragraph_data + skip && + *(new_end - 1) == ' ') { + new_end--; + shift++; + } + + if (shift > 0) { + openfile->totsize -= shift; + null_at(&new_paragraph_data, new_end - new_paragraph_data); + free(paragraph->data); + paragraph->data = new_paragraph_data; + +#ifndef NANO_SMALL + /* Adjust the mark coordinates to compensate for the change in + * the current line. */ + if (openfile->mark_set && openfile->mark_begin == paragraph) { + openfile->mark_begin_x -= mark_shift; + if (openfile->mark_begin_x > new_end - new_paragraph_data) + openfile->mark_begin_x = new_end - new_paragraph_data; + } +#endif + } else + free(new_paragraph_data); +} + +/* The "quote part" of a line is the largest initial substring matching + * the quote string. This function returns the length of the quote part + * of the given line. + * + * Note that if !HAVE_REGEX_H then we match concatenated copies of + * quotestr. */ +size_t quote_length(const char *line) +{ +#ifdef HAVE_REGEX_H + 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; +#else /* !HAVE_REGEX_H */ + size_t qdepth = 0; + + /* Compute quote depth level. */ + while (strncmp(line + qdepth, quotestr, quotelen) == 0) + qdepth += quotelen; + return qdepth; +#endif /* !HAVE_REGEX_H */ +} + +/* a_line and b_line are lines of text. The quotation part of a_line is + * the first a_quote characters. Check that the quotation part of + * b_line is the same. */ +bool quotes_match(const char *a_line, size_t a_quote, const char + *b_line) +{ + /* Here is the assumption about a_quote. */ + assert(a_quote == quote_length(a_line)); + + return (a_quote == quote_length(b_line) && + strncmp(a_line, b_line, a_quote) == 0); +} + +/* We assume a_line and b_line have no quote part. Then, we return + * whether b_line could follow a_line in a paragraph. */ +bool indents_match(const char *a_line, size_t a_indent, const char + *b_line, size_t b_indent) +{ + assert(a_indent == indent_length(a_line)); + assert(b_indent == indent_length(b_line)); + + return (b_indent <= a_indent && + strncmp(a_line, b_line, b_indent) == 0); +} + +/* Is foo the beginning of a paragraph? + * + * A line of text consists of a "quote part", followed by an + * "indentation part", followed by text. The functions quote_length() + * and indent_length() calculate these parts. + * + * A line is "part of a paragraph" if it has a part not in the quote + * part or the indentation. + * + * A line is "the beginning of a paragraph" if it is part of a + * paragraph and + * 1) it is the top line of the file, or + * 2) the line above it is not part of a paragraph, or + * 3) the line above it does not have precisely the same quote + * part, or + * 4) the indentation of this line is not an initial substring of + * the indentation of the previous line, or + * 5) this line has no quote part and some indentation, and + * autoindent isn't turned on. + * The reason for number 5) is that if autoindent isn't turned on, + * then an indented line is expected to start a paragraph, as in + * books. Thus, nano can justify an indented paragraph only if + * autoindent is turned on. */ +bool begpar(const filestruct *const foo) +{ + size_t quote_len; + size_t indent_len; + size_t temp_id_len; + + /* Case 1). */ + if (foo->prev == NULL) + return TRUE; + + quote_len = quote_length(foo->data); + indent_len = indent_length(foo->data + quote_len); + + /* Not part of a paragraph. */ + if (foo->data[quote_len + indent_len] == '\0') + return FALSE; + + /* Case 3). */ + if (!quotes_match(foo->data, quote_len, foo->prev->data)) + return TRUE; + + temp_id_len = indent_length(foo->prev->data + quote_len); + + /* Case 2) or 5) or 4). */ + if (foo->prev->data[quote_len + temp_id_len] == '\0' || + (quote_len == 0 && indent_len > 0 +#ifndef NANO_SMALL + && !ISSET(AUTOINDENT) +#endif + ) || !indents_match(foo->prev->data + quote_len, temp_id_len, + foo->data + quote_len, indent_len)) + return TRUE; + + return FALSE; +} + +/* Is foo inside a paragraph? */ +bool inpar(const filestruct *const foo) +{ + size_t quote_len; + + if (foo == NULL) + return FALSE; + + quote_len = quote_length(foo->data); + + return foo->data[quote_len + indent_length(foo->data + + quote_len)] != '\0'; +} + +/* Put the next par_len lines, starting with first_line, into the + * justify buffer, leaving copies of those lines in place. Assume there + * are enough lines after first_line. Return the new copy of + * first_line. */ +filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t + quote_len) +{ + filestruct *top = first_line; + /* The top of the paragraph we're backing up. */ + filestruct *bot = first_line; + /* The bottom of the paragraph we're backing up. */ + size_t i; + /* Generic loop variable. */ + size_t current_x_save = openfile->current_x; + ssize_t fl_lineno_save = first_line->lineno; + ssize_t edittop_lineno_save = openfile->edittop->lineno; + ssize_t current_lineno_save = openfile->current->lineno; +#ifndef NANO_SMALL + bool old_mark_set = openfile->mark_set; + ssize_t mb_lineno_save = 0; + size_t mark_begin_x_save = 0; + + if (old_mark_set) { + mb_lineno_save = openfile->mark_begin->lineno; + mark_begin_x_save = openfile->mark_begin_x; + } +#endif + + /* Move bot down par_len lines to the newline after the last line of + * the paragraph. */ + for (i = par_len; i > 0; i--) + bot = bot->next; + + /* Move the paragraph from the current buffer's filestruct to the + * justify buffer. */ + move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0); + + /* Copy the paragraph back to the current buffer's filestruct from + * the justify buffer. */ + copy_from_filestruct(jusbuffer, jusbottom); + + /* Move upward from the last line of the paragraph to the first + * line, putting first_line, edittop, current, and mark_begin at the + * same lines in the copied paragraph that they had in the original + * paragraph. */ + top = openfile->current->prev; + for (i = par_len; i > 0; i--) { + if (top->lineno == fl_lineno_save) + first_line = top; + if (top->lineno == edittop_lineno_save) + openfile->edittop = top; + if (top->lineno == current_lineno_save) + openfile->current = top; +#ifndef NANO_SMALL + if (old_mark_set && top->lineno == mb_lineno_save) { + openfile->mark_begin = top; + openfile->mark_begin_x = mark_begin_x_save; + } +#endif + top = top->prev; + } + + /* Put current_x at the same place in the copied paragraph that it + * had in the original paragraph. */ + openfile->current_x = current_x_save; + + set_modified(); + + return first_line; +} + +/* Find the beginning of the current paragraph if we're in one, or the + * beginning of the next paragraph if we're not. Afterwards, save the + * quote length and paragraph length in *quote and *par. Return TRUE if + * we found a paragraph, or FALSE if there was an error or we didn't + * find a paragraph. + * + * See the comment at begpar() for more about when a line is the + * beginning of a paragraph. */ +bool find_paragraph(size_t *const quote, size_t *const par) +{ + size_t quote_len; + /* Length of the initial quotation of the paragraph we search + * for. */ + size_t par_len; + /* Number of lines in the paragraph we search for. */ + filestruct *current_save; + /* The line at the beginning of the paragraph we search for. */ + ssize_t current_y_save; + /* The y-coordinate at the beginning of the paragraph we search + * for. */ + +#ifdef HAVE_REGEX_H + if (quoterc != 0) { + statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr); + return FALSE; + } +#endif + + assert(openfile->current != NULL); + + /* Move back to the beginning of the current line. */ + openfile->current_x = 0; + openfile->placewewant = 0; + + /* Find the first line of the current or next paragraph. First, if + * the current line isn't in a paragraph, move forward to the line + * after the last line of the next paragraph. If we end up on the + * same line, or the line before that isn't in a paragraph, it means + * that there aren't any paragraphs left, so get out. Otherwise, + * move back to the last line of the paragraph. If the current line + * is in a paragraph and it isn't the first line of that paragraph, + * move back to the first line. */ + if (!inpar(openfile->current)) { + current_save = openfile->current; + + do_para_end(FALSE); + if (openfile->current == current_save || + !inpar(openfile->current->prev)) + return FALSE; + if (openfile->current->prev != NULL) + openfile->current = openfile->current->prev; + } + if (!begpar(openfile->current)) + do_para_begin(FALSE); + + /* Now current is the first line of the paragraph. Set quote_len to + * the quotation length of that line, and set par_len to the number + * of lines in this paragraph. */ + quote_len = quote_length(openfile->current->data); + current_save = openfile->current; + current_y_save = openfile->current_y; + do_para_end(FALSE); + par_len = openfile->current->lineno - current_save->lineno; + openfile->current = current_save; + openfile->current_y = current_y_save; + + /* Save the values of quote_len and par_len. */ + assert(quote != NULL && par != NULL); + + *quote = quote_len; + *par = par_len; + + return TRUE; +} + +/* If full_justify is TRUE, justify the entire file. Otherwise, justify + * the current paragraph. */ +void do_justify(bool full_justify) +{ + filestruct *first_par_line = NULL; + /* Will be the first line of the resulting justified paragraph. + * For restoring after unjustify. */ + filestruct *last_par_line; + /* Will be the line containing the newline after the last line + * of the result. Also for restoring after unjustify. */ + + /* We save these variables to be restored if the user unjustifies. + * Note that we don't need to save totlines. */ + size_t current_x_save = openfile->current_x; + size_t pww_save = openfile->placewewant; + ssize_t current_y_save = openfile->current_y; + bool modified_save = openfile->modified; + size_t totsize_save = openfile->totsize; + filestruct *edittop_save = openfile->edittop; + filestruct *current_save = openfile->current; +#ifndef NANO_SMALL + filestruct *mark_begin_save = openfile->mark_begin; + size_t mark_begin_x_save = openfile->mark_begin_x; +#endif + int kbinput; + bool meta_key, func_key, s_or_t, ran_func, finished; + + /* If we're justifying the entire file, start at the beginning. */ + if (full_justify) + openfile->current = openfile->fileage; + + last_par_line = openfile->current; + + while (TRUE) { + size_t i; + /* Generic loop variable. */ + size_t quote_len; + /* Length of the initial quotation of the paragraph we + * justify. */ + size_t indent_len; + /* Length of the initial indentation of the paragraph we + * justify. */ + size_t par_len; + /* Number of lines in the paragraph we justify. */ + ssize_t break_pos; + /* Where we will break lines. */ + char *indent_string; + /* The first indentation that doesn't match the initial + * indentation of the paragraph we justify. This is put at + * the beginning of every line broken off the first + * justified line of the paragraph. (Note that this works + * because a paragraph can only contain two indentations at + * most: the initial one, and a different one starting on a + * line after the first. See the comment at begpar() for + * more about when a line is part of a paragraph.) */ + + /* Find the first line of the paragraph to be justified. That + * is the start of this paragraph if we're in one, or the start + * of the next otherwise. Save the quote length and paragraph + * length (number of lines). Don't refresh the screen yet, + * since we'll do that after we justify. + * + * If the search failed, we do one of two things. If we're + * justifying the whole file, we've found at least one + * paragraph, and the search didn't leave us on the last line of + * the file, it means that we should justify all the way to the + * last line of the file, so set the last line of the text to be + * justified to the last line of the file and break out of the + * loop. Otherwise, it means that there are no paragraph(s) to + * justify, so refresh the screen and get out. */ + if (!find_paragraph("e_len, &par_len)) { + if (full_justify && first_par_line != NULL && + first_par_line != openfile->filebot) { + last_par_line = openfile->filebot; + break; + } else { + edit_refresh(); + return; + } + } + + /* If we haven't already done it, copy the original paragraph(s) + * to the justify buffer. */ + if (first_par_line == NULL) + first_par_line = backup_lines(openfile->current, + full_justify ? openfile->filebot->lineno - + openfile->current->lineno : par_len, quote_len); + + /* Initialize indent_string to a blank string. */ + indent_string = mallocstrcpy(NULL, ""); + + /* Find the first indentation in the paragraph that doesn't + * match the indentation of the first line, and save it in + * indent_string. If all the indentations are the same, save + * the indentation of the first line in indent_string. */ + { + const filestruct *indent_line = openfile->current; + bool past_first_line = FALSE; + + for (i = 0; i < par_len; i++) { + indent_len = quote_len + + indent_length(indent_line->data + quote_len); + + if (indent_len != strlen(indent_string)) { + indent_string = mallocstrncpy(indent_string, + indent_line->data, indent_len + 1); + indent_string[indent_len] = '\0'; + + if (past_first_line) + break; + } + + if (indent_line == openfile->current) + past_first_line = TRUE; + + indent_line = indent_line->next; + } + } + + /* Now tack all the lines of the paragraph together, skipping + * the quoting and indentation on all lines after the first. */ + for (i = 0; i < par_len - 1; i++) { + filestruct *next_line = openfile->current->next; + size_t line_len = strlen(openfile->current->data); + size_t next_line_len = + strlen(openfile->current->next->data); + + indent_len = quote_len + + indent_length(openfile->current->next->data + + quote_len); + + next_line_len -= indent_len; + openfile->totsize -= indent_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. */ + if (line_len > 0 && + openfile->current->data[line_len - 1] != ' ') { + line_len++; + openfile->current->data = + charealloc(openfile->current->data, + line_len + 1); + openfile->current->data[line_len - 1] = ' '; + openfile->current->data[line_len] = '\0'; + openfile->totsize++; + } + + openfile->current->data = + charealloc(openfile->current->data, line_len + + next_line_len + 1); + strcat(openfile->current->data, next_line->data + + indent_len); + + /* Don't destroy edittop! */ + if (openfile->edittop == next_line) + openfile->edittop = openfile->current; + +#ifndef NANO_SMALL + /* Adjust the mark coordinates to compensate for the change + * in the next line. */ + if (openfile->mark_set && openfile->mark_begin == + next_line) { + openfile->mark_begin = openfile->current; + openfile->mark_begin_x += line_len - indent_len; + } +#endif + + unlink_node(next_line); + delete_node(next_line); + + /* If we've removed the next line, we need to go through + * this line again. */ + i--; + + par_len--; + openfile->totlines--; + openfile->totsize--; + } + + /* Call justify_format() on the paragraph, which will remove + * excess spaces from it and change all blank characters to + * spaces. */ + justify_format(openfile->current, quote_len + + indent_length(openfile->current->data + quote_len)); + + while (par_len > 0 && + strlenpt(openfile->current->data) > fill) { + size_t line_len = strlen(openfile->current->data); + + indent_len = strlen(indent_string); + + /* If this line is too long, try to wrap it to the next line + * to make it short enough. */ + break_pos = + break_line(openfile->current->data + indent_len, fill - + strnlenpt(openfile->current->data, indent_len), FALSE); + + /* We can't break the line, or don't need to, so get out. */ + if (break_pos == -1 || break_pos + indent_len == line_len) + break; + + /* Move forward to the character after the indentation and + * just after the space. */ + break_pos += indent_len + 1; + + assert(break_pos <= line_len); + + /* Make a new line, and copy the text after where we're + * going to break this line to the beginning of the new + * line. */ + splice_node(openfile->current, + make_new_node(openfile->current), + openfile->current->next); + + /* If this paragraph is non-quoted, and autoindent isn't + * turned on, set the indentation length to zero so that the + * indentation is treated as part of the line. */ + if (quote_len == 0 +#ifndef NANO_SMALL + && !ISSET(AUTOINDENT) +#endif + ) + indent_len = 0; + + /* Copy the text after where we're going to break the + * current line to the next line. */ + openfile->current->next->data = charalloc(indent_len + 1 + + line_len - break_pos); + strncpy(openfile->current->next->data, indent_string, + indent_len); + strcpy(openfile->current->next->data + indent_len, + openfile->current->data + break_pos); + + par_len++; + openfile->totlines++; + openfile->totsize += indent_len + 1; + +#ifndef NANO_SMALL + /* Adjust the mark coordinates to compensate for the change + * in the current line. */ + if (openfile->mark_set && openfile->mark_begin == + openfile->current && openfile->mark_begin_x > + break_pos) { + openfile->mark_begin = openfile->current->next; + openfile->mark_begin_x -= break_pos - indent_len; + } +#endif + + /* Break the current line. */ + null_at(&openfile->current->data, break_pos); + + /* Go to the next line. */ + par_len--; + openfile->current_y++; + openfile->current = openfile->current->next; + } + + /* We're done breaking lines, so we don't need indent_string + * anymore. */ + free(indent_string); + + /* Go to the next line, the line after the last line of the + * paragraph. */ + openfile->current_y++; + openfile->current = openfile->current->next; + + /* We've just justified a paragraph. If we're not justifying the + * entire file, break out of the loop. Otherwise, continue the + * loop so that we justify all the paragraphs in the file. */ + if (!full_justify) + break; + } + + /* We are now done justifying the paragraph or the file, so clean + * up. totlines, totsize, and current_y have been maintained above. + * Set last_par_line to the new end of the paragraph, update + * fileage, and renumber since edit_refresh() needs the line numbers + * to be right (but only do the last two if we actually justified + * something). */ + last_par_line = openfile->current; + if (first_par_line != NULL) { + if (first_par_line->prev == NULL) + openfile->fileage = first_par_line; + renumber(first_par_line); + } + + edit_refresh(); + + statusbar(_("Can now UnJustify!")); + + /* If constant cursor position display is on, make sure the current + * cursor position will be properly displayed on the statusbar. */ + if (ISSET(CONST_UPDATE)) + do_cursorpos(TRUE); + + /* Display the shortcut list with UnJustify. */ + shortcut_init(TRUE); + display_main_list(); + + /* Now get a keystroke and see if it's unjustify. If not, put back + * the keystroke and return. */ + kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func, + &finished, FALSE); + + if (!meta_key && !func_key && s_or_t && + kbinput == NANO_UNJUSTIFY_KEY) { + /* Restore the justify we just did (ungrateful user!). */ + openfile->current = current_save; + openfile->current_x = current_x_save; + openfile->placewewant = pww_save; + openfile->current_y = current_y_save; + openfile->edittop = edittop_save; + + /* Splice the justify buffer back into the file, but only if we + * actually justified something. */ + if (first_par_line != NULL) { + filestruct *top_save; + + /* Partition the filestruct so that it contains only the + * text of the justified paragraph. */ + filepart = partition_filestruct(first_par_line, 0, + last_par_line, 0); + + /* Remove the text of the justified paragraph, and + * put the text in the justify buffer in its place. */ + free_filestruct(openfile->fileage); + openfile->fileage = jusbuffer; + openfile->filebot = jusbottom; + + top_save = openfile->fileage; + + /* Unpartition the filestruct so that it contains all the + * text again. Note that the justified paragraph has been + * replaced with the unjustified paragraph. */ + unpartition_filestruct(&filepart); + + /* Renumber starting with the beginning line of the old + * partition. */ + renumber(top_save); + + /* Restore variables from before the justify. */ + openfile->totsize = totsize_save; + openfile->totlines = openfile->filebot->lineno; +#ifndef NANO_SMALL + if (openfile->mark_set) { + openfile->mark_begin = mark_begin_save; + openfile->mark_begin_x = mark_begin_x_save; + } +#endif + openfile->modified = modified_save; + + /* Clear the justify buffer. */ + jusbuffer = NULL; + + if (!openfile->modified) + titlebar(NULL); + edit_refresh(); + } + } else { + unget_kbinput(kbinput, meta_key, func_key); + + /* Blow away the text in the justify buffer. */ + free_filestruct(jusbuffer); + jusbuffer = NULL; + } + + blank_statusbar(); + + /* Display the shortcut list with UnCut. */ + shortcut_init(FALSE); + display_main_list(); +} + +void do_justify_void(void) +{ + do_justify(FALSE); +} + +void do_full_justify(void) +{ + do_justify(TRUE); +} +#endif /* !DISABLE_JUSTIFY */ + +#ifndef NANO_SMALL +void do_word_count(void) +{ + size_t words = 0, current_x_save = openfile->current_x; + size_t pww_save = openfile->placewewant; + filestruct *current_save = openfile->current; + bool old_mark_set = openfile->mark_set; + bool added_magicline = FALSE; + /* Whether we added a magicline after filebot. */ + filestruct *top, *bot; + size_t top_x, bot_x; + + if (old_mark_set) { + /* If the mark is on, partition the filestruct so that it + * contains only the marked text, keep track of whether the text + * will need a magicline added while we're counting words, add + * the magicline if necessary, and turn the mark off. */ + mark_order((const filestruct **)&top, &top_x, + (const filestruct **)&bot, &bot_x, NULL); + filepart = partition_filestruct(top, top_x, bot, bot_x); + if ((added_magicline = (openfile->filebot->data[0] != '\0'))) + new_magicline(); + openfile->mark_set = FALSE; + } + + /* 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 so that we match the output of "wc -w"), 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_x != 0) { + if (do_next_word(TRUE, FALSE)) + words++; + } + + if (old_mark_set) { + /* If the mark was on and we added a magicline, remove it + * now. */ + if (added_magicline) + remove_magicline(); + + /* Unpartition the filestruct so that it contains all the text + * again, and turn the mark back on. */ + unpartition_filestruct(&filepart); + openfile->mark_set = TRUE; + } + + /* Restore where we were. */ + openfile->current = current_save; + openfile->current_x = current_x_save; + openfile->placewewant = pww_save; + + /* Display the total word count on the statusbar. */ + statusbar("%s: %lu", old_mark_set ? _("Word Count in Selection") : + _("Word Count"), (unsigned long)words); +} +#endif /* !NANO_SMALL */ diff --git a/src/utils.c b/src/utils.c index 026c1c4f..3313b532 100644 --- a/src/utils.c +++ b/src/utils.c @@ -24,37 +24,14 @@ #include #endif -#include #include #include #include #include #include #include -#include #include "proto.h" -#ifdef HAVE_REGEX_H -#ifdef BROKEN_REGEXEC -/* Work around a potential segfault in glibc 2.2.3's regexec(). */ -int safe_regexec(const regex_t *preg, const char *string, size_t nmatch, - regmatch_t pmatch[], int eflags) -{ - if (string != NULL && *string != '\0') - return regexec(preg, string, nmatch, pmatch, eflags); - - return REG_NOMATCH; -} -#endif - -int regexp_bol_or_eol(const regex_t *preg, const char *string) -{ - return (regexec(preg, string, 0, NULL, 0) == 0 && - regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) == - REG_NOMATCH); -} -#endif /* HAVE_REGEX_H */ - int digits(size_t n) { int i = 1; @@ -240,6 +217,52 @@ ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream) #endif #endif /* !NANO_SMALL && ENABLE_NANORC */ +#ifdef HAVE_REGEX_H +#ifdef BROKEN_REGEXEC +/* Work around a potential segfault in glibc 2.2.3's regexec(). */ +int safe_regexec(const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags) +{ + if (string != NULL && *string != '\0') + return regexec(preg, string, nmatch, pmatch, eflags); + + return REG_NOMATCH; +} +#endif + +int regexp_bol_or_eol(const regex_t *preg, const char *string) +{ + return (regexec(preg, string, 0, NULL, 0) == 0 && + regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) == + REG_NOMATCH); +} +#endif /* HAVE_REGEX_H */ + +/* Is the word starting at position pos in buf a whole word? */ +bool is_whole_word(size_t pos, const char *buf, const char *word) +{ + char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max()); + size_t word_end = pos + strlen(word); + bool retval; + + assert(buf != NULL && pos <= strlen(buf) && word != NULL); + + parse_mbchar(buf + move_mbleft(buf, pos), p, NULL, NULL); + parse_mbchar(buf + word_end, r, NULL, NULL); + + /* If we're at the beginning of the line or the character before the + * word isn't a non-punctuation "word" character, and if we're at + * the end of the line or the character after the word isn't a + * non-punctuation "word" character, we have a whole word. */ + retval = (pos == 0 || !is_word_mbchar(p, FALSE)) && + (word_end == strlen(buf) || !is_word_mbchar(r, FALSE)); + + free(p); + free(r); + + return retval; +} + /* If we are searching backwards, we will find the last match that * starts no later than start. Otherwise we find the first match * starting no earlier than start. If we are doing a regexp search, we diff --git a/src/winio.c b/src/winio.c index fe690a89..6e93749c 100644 --- a/src/winio.c +++ b/src/winio.c @@ -26,10 +26,8 @@ #include #include -#include #include #include -#include #include "proto.h" static int *key_buffer = NULL;