From: David Lawrence Ramsey Date: Mon, 25 Jul 2005 02:33:45 +0000 (+0000) Subject: reorder a few functions X-Git-Tag: v1.3.9~124 X-Git-Url: https://git.wh0rd.org/?a=commitdiff_plain;h=cc8185fe2b54d04c93eace144ffef24fede5e525;p=nano.git reorder a few functions git-svn-id: svn://svn.savannah.gnu.org/nano/trunk/nano@2922 35c25a1d-7b9e-4130-9fde-d3aeb78583b8 --- diff --git a/src/proto.h b/src/proto.h index fa6a309a..b7d0414b 100644 --- a/src/proto.h +++ b/src/proto.h @@ -500,12 +500,6 @@ bool execute_command(const char *command); 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 @@ -528,6 +522,12 @@ void do_justify(bool full_justify); void do_justify_void(void); void do_full_justify(void); #endif /* !DISABLE_JUSTIFY */ +#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 #ifndef NANO_SMALL void do_word_count(void); #endif diff --git a/src/text.c b/src/text.c index e2a03809..a4dc220a 100644 --- a/src/text.c +++ b/src/text.c @@ -356,1499 +356,1499 @@ bool do_wrap(filestruct *line) } #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) +#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) { - 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 + 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; - /* Make sure spell-check is case sensitive. */ - SET(CASE_SENSITIVE); + assert(line != NULL); -#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 + while (*line != '\0' && goal >= 0) { + size_t pos = 0; - /* Save the current search/replace strings. */ - search_init_globals(); - save_search = last_search; - save_replace = last_replace; + line_len = parse_mbchar(line, NULL, NULL, &pos); - /* Set the search/replace strings to the misspelled word. */ - last_search = mallocstrcpy(NULL, word); - last_replace = mallocstrcpy(NULL, word); + if (is_blank_mbchar(line) || (newline && *line == '\n')) { + blank_loc = cur_loc; -#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; + if (newline && *line == '\n') + break; + } + + goal -= pos; + line += line_len; + cur_loc += line_len; } -#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; + if (goal >= 0) + /* In fact, the whole line displays shorter than goal. */ + return cur_loc; - /* 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); + if (blank_loc == -1) { + /* No blank was found that was short enough. */ + bool found_blank = FALSE; - edit_refresh(); + while (*line != '\0') { + line_len = parse_mbchar(line, NULL, NULL, NULL); - do_replace_highlight(TRUE, exp_word); + if (is_blank_mbchar(line) || (newline && *line == '\n')) { + if (!found_blank) + found_blank = TRUE; + } else if (found_blank) + return cur_loc - line_len; - /* Allow all instances of the word to be corrected. */ - canceled = (statusq(FALSE, spell_list, word, -#ifndef NANO_SMALL - NULL, -#endif - _("Edit a replacement")) == -1); + line += line_len; + cur_loc += line_len; + } - do_replace_highlight(FALSE, exp_word); + return -1; + } - free(exp_word); + /* 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; - if (!canceled && strcmp(word, answer) != 0) { - openfile->current_x--; - do_replace_loop(word, openfile->current, - &openfile->current_x, TRUE, &canceled); - } + while (*line != '\0' && (is_blank_mbchar(line) || + (newline && *line == '\n'))) { + line_len = parse_mbchar(line, NULL, NULL, NULL); - break; - } + line += line_len; + blank_loc += line_len; } -#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(); + return blank_loc; +} +#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */ - /* 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; - } +#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; - /* 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 + assert(line != NULL); - /* Restore the search/replace strings. */ - free(last_search); - last_search = save_search; - free(last_replace); - last_replace = save_replace; + blank_mb = charalloc(mb_cur_max()); - /* Restore where we were. */ - openfile->edittop = edittop_save; - openfile->current = current_save; - openfile->current_x = current_x_save; - openfile->placewewant = pww_save; + while (*line != '\0') { + blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL); - /* Restore case sensitivity setting. */ - if (!case_sens_set) - UNSET(CASE_SENSITIVE); + if (!is_blank_mbchar(blank_mb)) + break; -#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 + line += blank_mb_len; + len += blank_mb_len; + } - return !canceled; + free(blank_mb); + + return len; } +#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */ -/* 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) +#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 *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; + char *end, *new_end, *new_paragraph_data; + size_t shift = 0; +#ifndef NANO_SMALL + size_t mark_shift = 0; +#endif - /* Create all three pipes up front. */ - if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || - pipe(uniq_fd) == -1) - return _("Could not create pipe"); + /* 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)); - statusbar(_("Creating misspelled word list, please wait...")); + 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; - /* 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); - } + while (*end != '\0') { + int end_len; - /* Parent continues here. */ - close(spell_fd[1]); + /* 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); - /* 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; + *new_end = ' '; + new_end++; + end += end_len; - close(spell_fd[0]); + while (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); - /* Send sort's standard output to the new pipe. */ - if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO) - goto close_pipes_and_exit; + end += end_len; + shift += end_len; - close(sort_fd[1]); +#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); - /* Start the sort program. Use -f to remove mixed case. If - * this isn't portable, let me know. */ - execlp("sort", "sort", "-f", NULL); + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } - /* This should not be reached if sort is found. */ - exit(1); - } + if (*end != '\0' && mbstrchr(brackets, end) != NULL) { + end_len = parse_mbchar(end, NULL, NULL, NULL); - close(spell_fd[0]); - close(sort_fd[1]); + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } + } - /* 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; + if (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); - close(sort_fd[0]); + *new_end = ' '; + new_end++; + end += end_len; + } - /* Send uniq's standard output to the new pipe. */ - if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO) - goto close_pipes_and_exit; + if (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); - close(uniq_fd[1]); + *new_end = ' '; + new_end++; + end += end_len; + } - /* Start the uniq program; we are using PATH. */ - execlp("uniq", "uniq", NULL); + while (*end != '\0' && is_blank_mbchar(end)) { + end_len = parse_mbchar(end, NULL, NULL, NULL); - /* This should not be reached if uniq is found. */ - exit(1); - } + end += end_len; + shift += end_len; - close(sort_fd[0]); - close(uniq_fd[1]); +#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); - /* 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"); + while (end_len > 0) { + *new_end = *end; + new_end++; + end++; + end_len--; + } + } } - /* 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"); - } + assert(*end == '\0'); - /* 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); + *new_end = *end; - 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; + /* 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++; } - *read_buff_ptr = '\0'; - close(uniq_fd[0]); - - /* Process the spelling errors. */ - read_buff_word = read_buff_ptr = read_buff; + if (shift > 0) { + openfile->totsize -= shift; + null_at(&new_paragraph_data, new_end - new_paragraph_data); + free(paragraph->data); + paragraph->data = new_paragraph_data; - 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; +#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; } - 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(); +#endif + } else + free(new_paragraph_data); +} - /* 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); +/* 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 (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status)) - return _("Error invoking \"spell\""); + 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; - if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status)) - return _("Error invoking \"sort -f\""); + /* Compute quote depth level. */ + while (strncmp(line + qdepth, quotestr, quotelen) == 0) + qdepth += quotelen; + return qdepth; +#endif /* !HAVE_REGEX_H */ +} - if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status)) - return _("Error invoking \"uniq\""); +/* 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)); - /* Otherwise... */ - return NULL; + return (a_quote == quote_length(b_line) && + strncmp(a_line, b_line, a_quote) == 0); +} - 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); +/* 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); } -/* External spell checking. Return value: NULL for normal termination, - * otherwise the error string. */ -const char *do_alt_speller(char *tempfile_name) +/* 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) { - 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 + size_t quote_len; + size_t indent_len; + size_t temp_id_len; - endwin(); + /* Case 1). */ + if (foo->prev == NULL) + return TRUE; - /* Set up an argument list to pass execvp(). */ - if (spellargs == NULL) { - spellargs = (char **)nmalloc(arglen * sizeof(char *)); + quote_len = quote_length(foo->data); + indent_len = indent_length(foo->data + quote_len); - 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; + /* Not part of a paragraph. */ + if (foo->data[quote_len + indent_len] == '\0') + return FALSE; - /* 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); + /* Case 3). */ + if (!quotes_match(foo->data, quote_len, foo->prev->data)) + return TRUE; - /* Should not be reached, if alternate speller is found!!! */ - exit(1); - } + temp_id_len = indent_length(foo->prev->data + quote_len); - /* If we couldn't fork, get out. */ - if (pid_spell < 0) - return _("Could not fork"); + /* 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; - /* Wait for alternate speller to complete. */ - wait(&alt_spell_status); + return FALSE; +} - refresh(); +/* Is foo inside a paragraph? */ +bool inpar(const filestruct *const foo) +{ + size_t quote_len; - /* Restore the terminal to its previous state. */ - terminal_init(); + if (foo == NULL) + return FALSE; - /* Turn the cursor back on for sure. */ - curs_set(1); + quote_len = quote_length(foo->data); - if (!WIFEXITED(alt_spell_status) || - WEXITSTATUS(alt_spell_status) != 0) { - char *altspell_error; - char *invoke_error = _("Error invoking \"%s\""); + 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 - /* 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; - } + bool old_mark_set = openfile->mark_set; + ssize_t mb_lineno_save = 0; + size_t mark_begin_x_save = 0; -#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); + mb_lineno_save = openfile->mark_begin->lineno; + mark_begin_x_save = openfile->mark_begin_x; } #endif - /* Set up the window size. */ - window_size_init(); + /* 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; - /* Reinitialize the text of the current buffer. */ - free_filestruct(openfile->fileage); - initialize_buffer_text(); + /* Move the paragraph from the current buffer's filestruct to the + * justify buffer. */ + move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0); - /* 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; + /* 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) { - 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; + 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; + } - /* 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); + /* Put current_x at the same place in the copied paragraph that it + * had in the original paragraph. */ + openfile->current_x = current_x_save; - /* 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; + set_modified(); - /* 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; + return first_line; +} - /* 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; +/* 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. */ - /* Turn the mark back on. */ - openfile->mark_set = TRUE; +#ifdef HAVE_REGEX_H + if (quoterc != 0) { + statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr); + return FALSE; } #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(); + assert(openfile->current != NULL); - return NULL; -} + /* Move back to the beginning of the current line. */ + openfile->current_x = 0; + openfile->placewewant = 0; -void do_spell(void) -{ - int i; - FILE *temp_file; - char *temp = safe_tempfile(&temp_file); - const char *spell_msg; + /* 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; - if (temp == NULL) { - statusbar(_("Could not create temp file: %s"), strerror(errno)); - return; + 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); -#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; - } + /* 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; - spell_msg = (alt_speller != NULL) ? do_alt_speller(temp) : - do_int_speller(temp); - unlink(temp); - free(temp); + /* Save the values of quote_len and par_len. */ + assert(quote != NULL && par != NULL); - /* If the spell-checker printed any error messages onscreen, make - * sure that they're cleared off. */ - total_refresh(); + *quote = quote_len; + *par = par_len; - 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")); + return TRUE; } -#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) +/* If full_justify is TRUE, justify the entire file. Otherwise, justify + * the current paragraph. */ +void do_justify(bool full_justify) { - 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; + 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. */ - assert(line != NULL); + /* 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; - while (*line != '\0' && goal >= 0) { - size_t pos = 0; + /* If we're justifying the entire file, start at the beginning. */ + if (full_justify) + openfile->current = openfile->fileage; - line_len = parse_mbchar(line, NULL, NULL, &pos); + last_par_line = openfile->current; - if (is_blank_mbchar(line) || (newline && *line == '\n')) { - blank_loc = cur_loc; + 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.) */ - if (newline && *line == '\n') + /* 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; + } } - goal -= pos; - line += line_len; - cur_loc += line_len; - } + /* 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); - if (goal >= 0) - /* In fact, the whole line displays shorter than goal. */ - return cur_loc; + /* Initialize indent_string to a blank string. */ + indent_string = mallocstrcpy(NULL, ""); - if (blank_loc == -1) { - /* No blank was found that was short enough. */ - bool found_blank = FALSE; + /* 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; - while (*line != '\0') { - line_len = parse_mbchar(line, NULL, NULL, NULL); + for (i = 0; i < par_len; i++) { + indent_len = quote_len + + indent_length(indent_line->data + quote_len); - 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; - } + if (indent_len != strlen(indent_string)) { + indent_string = mallocstrncpy(indent_string, + indent_line->data, indent_len + 1); + indent_string[indent_len] = '\0'; - return -1; - } + if (past_first_line) + break; + } - /* 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; + if (indent_line == openfile->current) + past_first_line = TRUE; - while (*line != '\0' && (is_blank_mbchar(line) || - (newline && *line == '\n'))) { - line_len = parse_mbchar(line, NULL, NULL, NULL); + indent_line = indent_line->next; + } + } - line += line_len; - blank_loc += line_len; - } + /* 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); - return blank_loc; -} -#endif /* !DISABLE_HELP || !DISABLE_JUSTIFY || !DISABLE_WRAPPING */ + indent_len = quote_len + + indent_length(openfile->current->next->data + + quote_len); -#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; + next_line_len -= indent_len; + openfile->totsize -= indent_len; - assert(line != NULL); + /* 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++; + } - blank_mb = charalloc(mb_cur_max()); + openfile->current->data = + charealloc(openfile->current->data, line_len + + next_line_len + 1); + strcat(openfile->current->data, next_line->data + + indent_len); - while (*line != '\0') { - blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL); + /* Don't destroy edittop! */ + if (openfile->edittop == next_line) + openfile->edittop = openfile->current; - if (!is_blank_mbchar(blank_mb)) - break; +#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 - line += blank_mb_len; - len += blank_mb_len; - } + unlink_node(next_line); + delete_node(next_line); - free(blank_mb); + /* If we've removed the next line, we need to go through + * this line again. */ + i--; - return len; -} -#endif /* !NANO_SMALL || !DISABLE_JUSTIFY */ + par_len--; + openfile->totlines--; + openfile->totsize--; + } -#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 + /* 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)); - /* 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)); + while (par_len > 0 && + strlenpt(openfile->current->data) > fill) { + size_t line_len = strlen(openfile->current->data); - 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; + indent_len = strlen(indent_string); - while (*end != '\0') { - int end_len; + /* 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); - /* 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); + /* 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; - *new_end = ' '; - new_end++; - end += end_len; + /* Move forward to the character after the indentation and + * just after the space. */ + break_pos += indent_len + 1; - while (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); + assert(break_pos <= line_len); - end += end_len; - shift += end_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 - /* 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; + && !ISSET(AUTOINDENT) #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); + ) + indent_len = 0; - while (end_len > 0) { - *new_end = *end; - new_end++; - end++; - end_len--; - } + /* 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); - if (*end != '\0' && mbstrchr(brackets, end) != NULL) { - end_len = parse_mbchar(end, NULL, NULL, NULL); + par_len++; + openfile->totlines++; + openfile->totsize += indent_len + 1; - while (end_len > 0) { - *new_end = *end; - new_end++; - end++; - end_len--; - } +#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 - if (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); - - *new_end = ' '; - new_end++; - end += end_len; - } + /* Break the current line. */ + null_at(&openfile->current->data, break_pos); - if (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); + /* Go to the next line. */ + par_len--; + openfile->current_y++; + openfile->current = openfile->current->next; + } - *new_end = ' '; - new_end++; - end += end_len; - } + /* We're done breaking lines, so we don't need indent_string + * anymore. */ + free(indent_string); - while (*end != '\0' && is_blank_mbchar(end)) { - end_len = parse_mbchar(end, NULL, NULL, NULL); + /* Go to the next line, the line after the last line of the + * paragraph. */ + openfile->current_y++; + openfile->current = openfile->current->next; - 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--; - } - } + /* 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; } - 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++; + /* 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); } - if (shift > 0) { - openfile->totsize -= shift; - null_at(&new_paragraph_data, new_end - new_paragraph_data); - free(paragraph->data); - paragraph->data = new_paragraph_data; + edit_refresh(); -#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); -} + statusbar(_("Can now UnJustify!")); -/* 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 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); - 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; + /* Display the shortcut list with UnJustify. */ + shortcut_init(TRUE); + display_main_list(); - /* Compute quote depth level. */ - while (strncmp(line + qdepth, quotestr, quotelen) == 0) - qdepth += quotelen; - return qdepth; -#endif /* !HAVE_REGEX_H */ -} + /* 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); -/* 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)); + 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; - return (a_quote == quote_length(b_line) && - strncmp(a_line, b_line, a_quote) == 0); -} + /* Splice the justify buffer back into the file, but only if we + * actually justified something. */ + if (first_par_line != NULL) { + filestruct *top_save; -/* 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)); + /* 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); - return (b_indent <= a_indent && - strncmp(a_line, b_line, b_indent) == 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; -/* 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; + top_save = openfile->fileage; - /* Case 1). */ - if (foo->prev == NULL) - return TRUE; + /* 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); - quote_len = quote_length(foo->data); - indent_len = indent_length(foo->data + quote_len); + /* Renumber starting with the beginning line of the old + * partition. */ + renumber(top_save); - /* Not part of a paragraph. */ - if (foo->data[quote_len + indent_len] == '\0') - return FALSE; + /* 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; - /* Case 3). */ - if (!quotes_match(foo->data, quote_len, foo->prev->data)) - return TRUE; + /* Clear the justify buffer. */ + jusbuffer = NULL; - temp_id_len = indent_length(foo->prev->data + quote_len); + if (!openfile->modified) + titlebar(NULL); + edit_refresh(); + } + } else { + unget_kbinput(kbinput, meta_key, func_key); - /* 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; + /* Blow away the text in the justify buffer. */ + free_filestruct(jusbuffer); + jusbuffer = NULL; + } - return FALSE; + blank_statusbar(); + + /* Display the shortcut list with UnCut. */ + shortcut_init(FALSE); + display_main_list(); } -/* Is foo inside a paragraph? */ -bool inpar(const filestruct *const foo) +void do_justify_void(void) { - size_t quote_len; - - if (foo == NULL) - return FALSE; - - quote_len = quote_length(foo->data); + do_justify(FALSE); +} - return foo->data[quote_len + indent_length(foo->data + - quote_len)] != '\0'; +void do_full_justify(void) +{ + do_justify(TRUE); } +#endif /* !DISABLE_JUSTIFY */ -/* 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) +#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) { - 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; + 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; - ssize_t mb_lineno_save = 0; - size_t mark_begin_x_save = 0; + 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) { - mb_lineno_save = openfile->mark_begin->lineno; - mark_begin_x_save = openfile->mark_begin_x; + /* 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 - /* 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; + /* Start from the top of the file. */ + openfile->edittop = openfile->fileage; + openfile->current = openfile->fileage; + openfile->current_x = (size_t)-1; + openfile->placewewant = 0; - /* Move the paragraph from the current buffer's filestruct to the - * justify buffer. */ - move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 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); - /* Copy the paragraph back to the current buffer's filestruct from - * the justify buffer. */ - copy_from_filestruct(jusbuffer, jusbottom); + edit_refresh(); - /* 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; + do_replace_highlight(TRUE, exp_word); + + /* Allow all instances of the word to be corrected. */ + canceled = (statusq(FALSE, spell_list, word, #ifndef NANO_SMALL - if (old_mark_set && top->lineno == mb_lineno_save) { - openfile->mark_begin = top; - openfile->mark_begin_x = mark_begin_x_save; - } + NULL, #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; + _("Edit a replacement")) == -1); - set_modified(); + do_replace_highlight(FALSE, exp_word); - return first_line; -} + free(exp_word); -/* 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. */ + if (!canceled && strcmp(word, answer) != 0) { + openfile->current_x--; + do_replace_loop(word, openfile->current, + &openfile->current_x, TRUE, &canceled); + } -#ifdef HAVE_REGEX_H - if (quoterc != 0) { - statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr); - return FALSE; + break; + } } -#endif - assert(openfile->current != NULL); - - /* Move back to the beginning of the current line. */ - openfile->current_x = 0; - openfile->placewewant = 0; +#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(); - /* 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; + /* 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; + } - 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; + /* Unpartition the filestruct so that it contains all the text + * again, and turn the mark back on. */ + unpartition_filestruct(&filepart); + openfile->mark_set = TRUE; } - if (!begpar(openfile->current)) - do_para_begin(FALSE); +#endif - /* 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; + /* 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_y = current_y_save; + openfile->current_x = current_x_save; + openfile->placewewant = pww_save; - /* Save the values of quote_len and par_len. */ - assert(quote != NULL && par != NULL); + /* Restore case sensitivity setting. */ + if (!case_sens_set) + UNSET(CASE_SENSITIVE); - *quote = quote_len; - *par = par_len; +#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 TRUE; + return !canceled; } -/* If full_justify is TRUE, justify the entire file. Otherwise, justify - * the current paragraph. */ -void do_justify(bool full_justify) +/* 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) { - 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. */ + 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; - /* 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; + /* Create all three pipes up front. */ + if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || + pipe(uniq_fd) == -1) + return _("Could not create pipe"); - /* If we're justifying the entire file, start at the beginning. */ - if (full_justify) - openfile->current = openfile->fileage; + statusbar(_("Creating misspelled word list, please wait...")); - last_par_line = openfile->current; + /* A new process to run spell in. */ + if ((pid_spell = fork()) == 0) { + /* Child continues (i.e, future spell process). */ + close(spell_fd[0]); - 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.) */ + /* Replace the standard input with the temp file. */ + if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1) + goto close_pipes_and_exit; - /* 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 (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) + goto close_pipes_and_exit; - /* 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); + close(tempfile_fd); - /* Initialize indent_string to a blank string. */ - indent_string = mallocstrcpy(NULL, ""); + /* Send spell's standard output to the pipe. */ + if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO) + goto close_pipes_and_exit; - /* 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; + 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); + } - for (i = 0; i < par_len; i++) { - indent_len = quote_len + - indent_length(indent_line->data + quote_len); + close(spell_fd[0]); + close(sort_fd[1]); - if (indent_len != strlen(indent_string)) { - indent_string = mallocstrncpy(indent_string, - indent_line->data, indent_len + 1); - indent_string[indent_len] = '\0'; + /* 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; - if (past_first_line) - break; - } + close(sort_fd[0]); - if (indent_line == openfile->current) - past_first_line = TRUE; + /* Send uniq's standard output to the new pipe. */ + if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO) + goto close_pipes_and_exit; - indent_line = indent_line->next; - } - } + close(uniq_fd[1]); - /* 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); + /* Start the uniq program; we are using PATH. */ + execlp("uniq", "uniq", NULL); - indent_len = quote_len + - indent_length(openfile->current->next->data + - quote_len); + /* This should not be reached if uniq is found. */ + exit(1); + } - next_line_len -= indent_len; - openfile->totsize -= indent_len; + close(sort_fd[0]); + close(uniq_fd[1]); - /* 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++; - } + /* 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"); + } - openfile->current->data = - charealloc(openfile->current->data, line_len + - next_line_len + 1); - strcat(openfile->current->data, next_line->data + - indent_len); + /* 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"); + } - /* Don't destroy edittop! */ - if (openfile->edittop == next_line) - openfile->edittop = openfile->current; + /* 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); -#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 + 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; + } - unlink_node(next_line); - delete_node(next_line); + *read_buff_ptr = '\0'; + close(uniq_fd[0]); - /* If we've removed the next line, we need to go through - * this line again. */ - i--; + /* Process the spelling errors. */ + read_buff_word = read_buff_ptr = read_buff; - par_len--; - openfile->totlines--; - openfile->totsize--; + 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++; + } - /* 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)); + /* 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); - while (par_len > 0 && - strlenpt(openfile->current->data) > fill) { - size_t line_len = strlen(openfile->current->data); + free(read_buff); + replace_abort(); + edit_refresh(); - indent_len = strlen(indent_string); + /* 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 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); + if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status)) + return _("Error invoking \"spell\""); - /* 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; + if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status)) + return _("Error invoking \"sort -f\""); - /* Move forward to the character after the indentation and - * just after the space. */ - break_pos += indent_len + 1; + if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status)) + return _("Error invoking \"uniq\""); - assert(break_pos <= line_len); + /* Otherwise... */ + return NULL; - /* 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); + 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); +} - /* 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 +/* 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 - && !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; + 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. */ -#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; - } + 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 - /* 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); + endwin(); - /* Go to the next line, the line after the last line of the - * paragraph. */ - openfile->current_y++; - openfile->current = openfile->current->next; + /* Set up an argument list to pass execvp(). */ + if (spellargs == NULL) { + spellargs = (char **)nmalloc(arglen * sizeof(char *)); - /* 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; + 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; - /* 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); + /* 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); } - edit_refresh(); + /* If we couldn't fork, get out. */ + if (pid_spell < 0) + return _("Could not fork"); - statusbar(_("Can now UnJustify!")); + /* Wait for alternate speller to complete. */ + wait(&alt_spell_status); - /* 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); + refresh(); - /* Display the shortcut list with UnJustify. */ - shortcut_init(TRUE); - display_main_list(); + /* Restore the terminal to its previous state. */ + terminal_init(); - /* 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); + /* Turn the cursor back on for sure. */ + curs_set(1); - 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; + if (!WIFEXITED(alt_spell_status) || + WEXITSTATUS(alt_spell_status) != 0) { + char *altspell_error; + char *invoke_error = _("Error invoking \"%s\""); - /* Splice the justify buffer back into the file, but only if we - * actually justified something. */ - if (first_par_line != NULL) { - filestruct *top_save; +#ifndef NANO_SMALL + /* Turn the mark back on if it was on before. */ + openfile->mark_set = old_mark_set; +#endif - /* 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); + altspell_error = + charalloc(strlen(invoke_error) + + strlen(alt_speller) + 1); + sprintf(altspell_error, invoke_error, alt_speller); + return altspell_error; + } - /* 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; +#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'); - top_save = openfile->fileage; + /* 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 - /* 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); + /* Set up the window size. */ + window_size_init(); - /* Renumber starting with the beginning line of the old - * partition. */ - renumber(top_save); + /* 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; - /* 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; + if (old_mark_set) { + filestruct *top_save = openfile->fileage; - /* Clear the justify buffer. */ - jusbuffer = NULL; + /* If the mark was on and we added a magicline, remove it + * now. */ + if (added_magicline) + remove_magicline(); - if (!openfile->modified) - titlebar(NULL); - edit_refresh(); + /* 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; } - } else { - unget_kbinput(kbinput, meta_key, func_key); - /* Blow away the text in the justify buffer. */ - free_filestruct(jusbuffer); - jusbuffer = NULL; + /* 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 - blank_statusbar(); + /* 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(); - /* Display the shortcut list with UnCut. */ - shortcut_init(FALSE); - display_main_list(); + return NULL; } -void do_justify_void(void) +void do_spell(void) { - do_justify(FALSE); -} + int i; + FILE *temp_file; + char *temp = safe_tempfile(&temp_file); + const char *spell_msg; -void do_full_justify(void) -{ - do_justify(TRUE); + 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_JUSTIFY */ +#endif /* !DISABLE_SPELLER */ #ifndef NANO_SMALL void do_word_count(void)