}
#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)