"Fossies" - the Fresh Open Source Software Archive

Member "nano-4.5/src/search.c" (4 Oct 2019, 29871 Bytes) of package /linux/misc/nano-4.5.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "search.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.4_vs_4.5.

    1 /**************************************************************************
    2  *   search.c  --  This file is part of GNU nano.                         *
    3  *                                                                        *
    4  *   Copyright (C) 1999-2011, 2013-2019 Free Software Foundation, Inc.    *
    5  *   Copyright (C) 2015-2018 Benno Schulenberg                            *
    6  *                                                                        *
    7  *   GNU nano is free software: you can redistribute it and/or modify     *
    8  *   it under the terms of the GNU General Public License as published    *
    9  *   by the Free Software Foundation, either version 3 of the License,    *
   10  *   or (at your option) any later version.                               *
   11  *                                                                        *
   12  *   GNU nano is distributed in the hope that it will be useful,          *
   13  *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
   14  *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
   15  *   See the GNU General Public License for more details.                 *
   16  *                                                                        *
   17  *   You should have received a copy of the GNU General Public License    *
   18  *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
   19  *                                                                        *
   20  **************************************************************************/
   21 
   22 #include "proto.h"
   23 
   24 #include <string.h>
   25 
   26 static bool came_full_circle = FALSE;
   27         /* Have we reached the starting line again while searching? */
   28 static bool have_compiled_regexp = FALSE;
   29         /* Whether we have compiled a regular expression for the search. */
   30 
   31 /* Compile the given regular expression and store it in search_regexp.
   32  * Return TRUE if the expression is valid, and FALSE otherwise. */
   33 bool regexp_init(const char *regexp)
   34 {
   35     int value = regcomp(&search_regexp, regexp,
   36                 NANO_REG_EXTENDED | (ISSET(CASE_SENSITIVE) ? 0 : REG_ICASE));
   37 
   38     /* If regex compilation failed, show the error message. */
   39     if (value != 0) {
   40         size_t len = regerror(value, &search_regexp, NULL, 0);
   41         char *str = charalloc(len);
   42 
   43         regerror(value, &search_regexp, str, len);
   44         statusline(ALERT, _("Bad regex \"%s\": %s"), regexp, str);
   45         free(str);
   46 
   47         return FALSE;
   48     }
   49 
   50     have_compiled_regexp = TRUE;
   51 
   52     return TRUE;
   53 }
   54 
   55 /* Free a compiled regular expression, if one was compiled; and schedule a
   56  * full screen refresh when the mark is on, in case the cursor has moved. */
   57 void tidy_up_after_search(void)
   58 {
   59     if (have_compiled_regexp) {
   60         regfree(&search_regexp);
   61         have_compiled_regexp = FALSE;
   62     }
   63 #ifndef NANO_TINY
   64     if (openfile->mark)
   65         refresh_needed = TRUE;
   66 #endif
   67 }
   68 
   69 /* Prepare the prompt and ask the user what to search for.  Keep looping
   70  * as long as the user presses a toggle, and only take action and exit
   71  * when <Enter> is pressed or a non-toggle shortcut was executed. */
   72 void search_init(bool replacing, bool keep_the_answer)
   73 {
   74     char *thedefault;
   75         /* What will be searched for when the user typed nothing. */
   76 
   77     /* When starting a new search, clear the current answer. */
   78     if (!keep_the_answer)
   79         answer = mallocstrcpy(answer, NULL);
   80 
   81     /* If something was searched for earlier, include it in the prompt. */
   82     if (*last_search != '\0') {
   83         char *disp = display_string(last_search, 0, COLS / 3, FALSE, FALSE);
   84 
   85         thedefault = charalloc(strlen(disp) + 7);
   86         /* We use (COLS / 3) here because we need to see more on the line. */
   87         sprintf(thedefault, " [%s%s]", disp,
   88                 (breadth(last_search) > COLS / 3) ? "..." : "");
   89         free(disp);
   90     } else
   91         thedefault = mallocstrcpy(NULL, "");
   92 
   93     while (TRUE) {
   94         functionptrtype func;
   95         /* Ask the user what to search for (or replace). */
   96         int response = do_prompt(FALSE, FALSE,
   97                     inhelp ? MFINDINHELP : (replacing ? MREPLACE : MWHEREIS),
   98                     answer, &search_history, edit_refresh,
   99                     /* TRANSLATORS: This is the main search prompt. */
  100                     "%s%s%s%s%s%s", _("Search"),
  101                     /* TRANSLATORS: The next four modify the search prompt. */
  102                     ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : "",
  103                     ISSET(USE_REGEXP) ? _(" [Regexp]") : "",
  104                     ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") : "",
  105                     replacing ?
  106 #ifndef NANO_TINY
  107                     openfile->mark ? _(" (to replace) in selection") :
  108 #endif
  109                     _(" (to replace)") : "", thedefault);
  110 
  111         /* If the search was cancelled, or we have a blank answer and
  112          * nothing was searched for yet during this session, get out. */
  113         if (response == -1 || (response == -2 && *last_search == '\0')) {
  114             statusbar(_("Cancelled"));
  115             break;
  116         }
  117 
  118         /* If Enter was pressed, prepare to do a replace or a search. */
  119         if (response == 0 || response == -2) {
  120             /* If an actual answer was typed, remember it. */
  121             if (*answer != '\0') {
  122                 last_search = mallocstrcpy(last_search, answer);
  123 #ifdef ENABLE_HISTORIES
  124                 update_history(&search_history, answer);
  125 #endif
  126             }
  127 
  128             /* When not doing a regular-expression search, just search;
  129              * otherwise compile the search string, and only search when
  130              * the expression is valid. */
  131             if (!ISSET(USE_REGEXP) || regexp_init(last_search)) {
  132                 if (replacing)
  133                     ask_for_replacement();
  134                 else
  135                     go_looking();
  136             }
  137 
  138             break;
  139         }
  140 
  141         func = func_from_key(&response);
  142 
  143         /* If we're here, one of the five toggles was pressed, or
  144          * a shortcut was executed. */
  145         if (func == case_sens_void)
  146             TOGGLE(CASE_SENSITIVE);
  147         else if (func == backwards_void)
  148             TOGGLE(BACKWARDS_SEARCH);
  149         else if (func == regexp_void)
  150             TOGGLE(USE_REGEXP);
  151         else if (func == flip_replace) {
  152             if (ISSET(VIEW_MODE)) {
  153                 print_view_warning();
  154                 napms(600);
  155             } else
  156                 replacing = !replacing;
  157         } else if (func == flip_goto) {
  158             do_gotolinecolumn(openfile->current->lineno,
  159                                 openfile->placewewant + 1, TRUE, TRUE);
  160             break;
  161         } else
  162             break;
  163     }
  164 
  165     tidy_up_after_search();
  166     free(thedefault);
  167 }
  168 
  169 /* Look for needle, starting at (current, current_x).  begin is the line
  170  * where we first started searching, at column begin_x.  Return 1 when we
  171  * found something, 0 when nothing, and -2 on cancel.  When match_len is
  172  * not NULL, set it to the length of the found string, if any. */
  173 int findnextstr(const char *needle, bool whole_word_only, int modus,
  174         size_t *match_len, bool skipone, const linestruct *begin, size_t begin_x)
  175 {
  176     size_t found_len = strlen(needle);
  177         /* The length of a match -- will be recomputed for a regex. */
  178     int feedback = 0;
  179         /* When bigger than zero, show and wipe the "Searching..." message. */
  180     linestruct *line = openfile->current;
  181         /* The line that we will search through now. */
  182     const char *from = line->data + openfile->current_x;
  183         /* The point in the line from where we start searching. */
  184     const char *found = NULL;
  185         /* A pointer to the location of the match, if any. */
  186     size_t found_x;
  187         /* The x coordinate of a found occurrence. */
  188     time_t lastkbcheck = time(NULL);
  189         /* The time we last looked at the keyboard. */
  190 
  191     /* Set non-blocking input so that we can just peek for a Cancel. */
  192     disable_waiting();
  193 
  194     if (begin == NULL)
  195         came_full_circle = FALSE;
  196 
  197     /* Start searching through the lines, looking for the needle. */
  198     while (TRUE) {
  199         /* Glance at the keyboard once every second. */
  200         if (time(NULL) - lastkbcheck > 0) {
  201             int input = parse_kbinput(edit);
  202 
  203             lastkbcheck = time(NULL);
  204 
  205             /* Consume all waiting keystrokes until a Cancel. */
  206             while (input != ERR) {
  207                 if (func_from_key(&input) == do_cancel) {
  208                     statusbar(_("Cancelled"));
  209                     enable_waiting();
  210                     return -2;
  211                 }
  212                 input = parse_kbinput(NULL);
  213             }
  214 
  215             if (++feedback > 0)
  216                 /* TRANSLATORS: This is shown when searching takes
  217                  * more than half a second. */
  218                 statusbar(_("Searching..."));
  219         }
  220 
  221         /* When starting a new search, skip the first character, then
  222          * (in either case) search for the needle in the current line. */
  223         if (skipone) {
  224             skipone = FALSE;
  225             if (ISSET(BACKWARDS_SEARCH) && from != line->data) {
  226                 from = line->data + step_left(line->data, from - line->data);
  227                 found = strstrwrapper(line->data, needle, from);
  228             } else if (!ISSET(BACKWARDS_SEARCH) && *from != '\0') {
  229                 from += char_length(from);
  230                 found = strstrwrapper(line->data, needle, from);
  231             }
  232         } else
  233             found = strstrwrapper(line->data, needle, from);
  234 
  235         if (found != NULL) {
  236             /* When doing a regex search, compute the length of the match. */
  237             if (ISSET(USE_REGEXP))
  238                 found_len = regmatches[0].rm_eo - regmatches[0].rm_so;
  239 #ifdef ENABLE_SPELLER
  240             /* When we're spell checking, a match should be a separate word;
  241              * if it's not, continue looking in the rest of the line. */
  242             if (whole_word_only && !is_separate_word(found - line->data,
  243                                                 found_len, line->data)) {
  244                 from = found + char_length(found);
  245                 continue;
  246             }
  247 #endif
  248             /* The match is valid. */
  249             break;
  250         }
  251 
  252         /* If we're back at the beginning, then there is no needle. */
  253         if (came_full_circle) {
  254             enable_waiting();
  255             return 0;
  256         }
  257 
  258         /* Move to the previous or next line in the file. */
  259         if (ISSET(BACKWARDS_SEARCH))
  260             line = line->prev;
  261         else
  262             line = line->next;
  263 
  264         /* If we've reached the start or end of the buffer, wrap around;
  265          * but stop when spell-checking or replacing in a region. */
  266         if (line == NULL) {
  267             if (whole_word_only || modus == INREGION) {
  268                 enable_waiting();
  269                 return 0;
  270             }
  271 
  272             if (ISSET(BACKWARDS_SEARCH))
  273                 line = openfile->filebot;
  274             else
  275                 line = openfile->filetop;
  276 
  277             if (modus == JUSTFIND) {
  278                 statusbar(_("Search Wrapped"));
  279                 /* Delay the "Searching..." message for at least two seconds. */
  280                 feedback = -2;
  281             }
  282         }
  283 
  284         /* If we've reached the original starting line, take note. */
  285         if (line == begin)
  286             came_full_circle = TRUE;
  287 
  288         /* Set the starting x to the start or end of the line. */
  289         from = line->data;
  290         if (ISSET(BACKWARDS_SEARCH))
  291             from += strlen(line->data);
  292     }
  293 
  294     found_x = found - line->data;
  295 
  296     enable_waiting();
  297 
  298     /* Ensure that the found occurrence is not beyond the starting x. */
  299     if (came_full_circle && ((!ISSET(BACKWARDS_SEARCH) && (found_x > begin_x ||
  300                         (modus == REPLACING && found_x == begin_x))) ||
  301                         (ISSET(BACKWARDS_SEARCH) && found_x < begin_x)))
  302         return 0;
  303 
  304     /* Set the current position to point at what we found. */
  305     openfile->current = line;
  306     openfile->current_x = found_x;
  307 
  308     /* When requested, pass back the length of the match. */
  309     if (match_len != NULL)
  310         *match_len = found_len;
  311 
  312     /* Wipe the "Searching..." message and unset the suppression flag. */
  313     if (feedback > 0) {
  314         wipe_statusbar();
  315         suppress_cursorpos = FALSE;
  316     }
  317 
  318     return 1;
  319 }
  320 
  321 /* Ask what to search for and then go looking for it. */
  322 void do_search(void)
  323 {
  324     search_init(FALSE, FALSE);
  325 }
  326 
  327 /* Search forward for a string. */
  328 void do_search_forward(void)
  329 {
  330     UNSET(BACKWARDS_SEARCH);
  331     do_search();
  332 }
  333 
  334 /* Search backwards for a string. */
  335 void do_search_backward(void)
  336 {
  337     SET(BACKWARDS_SEARCH);
  338     do_search();
  339 }
  340 
  341 /* Search for the last string without prompting. */
  342 void do_research(void)
  343 {
  344 #ifdef ENABLE_HISTORIES
  345     /* If nothing was searched for yet during this run of nano, but
  346      * there is a search history, take the most recent item. */
  347     if (*last_search == '\0' && searchbot->prev != NULL)
  348         last_search = mallocstrcpy(last_search, searchbot->prev->data);
  349 #endif
  350 
  351     if (*last_search == '\0') {
  352         statusbar(_("No current search pattern"));
  353         return;
  354     }
  355 
  356     if (ISSET(USE_REGEXP) && !regexp_init(last_search))
  357         return;
  358 
  359     /* Use the search-menu key bindings, to allow cancelling. */
  360     currmenu = MWHEREIS;
  361 
  362     wipe_statusbar();
  363 
  364     go_looking();
  365 
  366     tidy_up_after_search();
  367 }
  368 
  369 /* Search in the backward direction for the next occurrence. */
  370 void do_findprevious(void)
  371 {
  372     SET(BACKWARDS_SEARCH);
  373     do_research();
  374 }
  375 
  376 /* Search in the forward direction for the next occurrence. */
  377 void do_findnext(void)
  378 {
  379     UNSET(BACKWARDS_SEARCH);
  380     do_research();
  381 }
  382 
  383 /* Report on the status bar that the given string was not found. */
  384 void not_found_msg(const char *str)
  385 {
  386     char *disp = display_string(str, 0, (COLS / 2) + 1, FALSE, FALSE);
  387     size_t numchars = actual_x(disp, wideness(disp, COLS / 2));
  388 
  389     statusline(HUSH, _("\"%.*s%s\" not found"), numchars, disp,
  390                         (disp[numchars] == '\0') ? "" : "...");
  391     free(disp);
  392 }
  393 
  394 /* Search for the global string 'last_search'.  Inform the user when
  395  * the string occurs only once. */
  396 void go_looking(void)
  397 {
  398     linestruct *was_current = openfile->current;
  399     size_t was_current_x = openfile->current_x;
  400 
  401 //#define TIMEIT 12
  402 #ifdef TIMEIT
  403 #include <time.h>
  404     clock_t start = clock();
  405 #endif
  406 
  407     came_full_circle = FALSE;
  408 
  409     didfind = findnextstr(last_search, FALSE, JUSTFIND, NULL, TRUE,
  410                                 openfile->current, openfile->current_x);
  411 
  412     /* If we found something, and we're back at the exact same spot
  413      * where we started searching, then this is the only occurrence. */
  414     if (didfind == 1 && openfile->current == was_current &&
  415                 openfile->current_x == was_current_x)
  416         statusbar(_("This is the only occurrence"));
  417     else if (didfind == 0)
  418         not_found_msg(last_search);
  419 
  420 #ifdef TIMEIT
  421     statusline(HUSH, "Took: %.2f", (double)(clock() - start) / CLOCKS_PER_SEC);
  422 #endif
  423 
  424     edit_redraw(was_current, CENTERING);
  425 }
  426 
  427 /* Calculate the size of the replacement text, taking possible
  428  * subexpressions \1 to \9 into account.  Return the replacement
  429  * text in the passed string only when create is TRUE. */
  430 int replace_regexp(char *string, bool create)
  431 {
  432     const char *c = answer;
  433     size_t replacement_size = 0;
  434 
  435     /* Iterate through the replacement text to handle subexpression
  436      * replacement using \1, \2, \3, etc. */
  437     while (*c != '\0') {
  438         int num = (*(c + 1) - '0');
  439 
  440         if (*c != '\\' || num < 1 || num > 9 || num > search_regexp.re_nsub) {
  441             if (create)
  442                 *string++ = *c;
  443             c++;
  444             replacement_size++;
  445         } else {
  446             size_t i = regmatches[num].rm_eo - regmatches[num].rm_so;
  447 
  448             /* Skip over the replacement expression. */
  449             c += 2;
  450 
  451             /* But add the length of the subexpression to new_size. */
  452             replacement_size += i;
  453 
  454             /* And if create is TRUE, append the result of the
  455              * subexpression match to the new line. */
  456             if (create) {
  457                 strncpy(string, openfile->current->data +
  458                                         regmatches[num].rm_so, i);
  459                 string += i;
  460             }
  461         }
  462     }
  463 
  464     if (create)
  465         *string = '\0';
  466 
  467     return replacement_size;
  468 }
  469 
  470 /* Return a copy of the current line with one needle replaced. */
  471 char *replace_line(const char *needle)
  472 {
  473     size_t new_size = strlen(openfile->current->data) + 1;
  474     size_t match_len;
  475     char *copy;
  476 
  477     /* First adjust the size of the new line for the change. */
  478     if (ISSET(USE_REGEXP)) {
  479         match_len = regmatches[0].rm_eo - regmatches[0].rm_so;
  480         new_size += replace_regexp(NULL, FALSE) - match_len;
  481     } else {
  482         match_len = strlen(needle);
  483         new_size += strlen(answer) - match_len;
  484     }
  485 
  486     copy = charalloc(new_size);
  487 
  488     /* Copy the head of the original line. */
  489     strncpy(copy, openfile->current->data, openfile->current_x);
  490 
  491     /* Add the replacement text. */
  492     if (ISSET(USE_REGEXP))
  493         replace_regexp(copy + openfile->current_x, TRUE);
  494     else
  495         strcpy(copy + openfile->current_x, answer);
  496 
  497     /* Copy the tail of the original line. */
  498     strcat(copy, openfile->current->data + openfile->current_x + match_len);
  499 
  500     return copy;
  501 }
  502 
  503 /* Step through each occurrence of the search string and prompt the user
  504  * before replacing it.  We seek for needle, and replace it with answer.
  505  * The parameters real_current and real_current_x are needed in order to
  506  * allow the cursor position to be updated when a word before the cursor
  507  * is replaced by a shorter word.  Return -1 if needle isn't found, -2 if
  508  * the seeking is aborted, else the number of replacements performed. */
  509 ssize_t do_replace_loop(const char *needle, bool whole_word_only,
  510         const linestruct *real_current, size_t *real_current_x)
  511 {
  512     ssize_t numreplaced = -1;
  513     size_t match_len;
  514     bool replaceall = FALSE;
  515     bool skipone = ISSET(BACKWARDS_SEARCH);
  516     int modus = REPLACING;
  517 #ifndef NANO_TINY
  518     linestruct *was_mark = openfile->mark;
  519     linestruct *top, *bot;
  520     size_t top_x, bot_x;
  521     bool right_side_up = FALSE;
  522         /* TRUE if (mark_begin, mark_begin_x) is the top of the mark,
  523          * FALSE if (current, current_x) is. */
  524 
  525     /* If the mark is on, frame the region, and turn the mark off. */
  526     if (openfile->mark) {
  527         get_region((const linestruct **)&top, &top_x,
  528                     (const linestruct **)&bot, &bot_x, &right_side_up);
  529         openfile->mark = NULL;
  530         modus = INREGION;
  531 
  532         /* Start either at the top or the bottom of the marked region. */
  533         if (!ISSET(BACKWARDS_SEARCH)) {
  534             openfile->current = top;
  535             openfile->current_x = top_x;
  536         } else {
  537             openfile->current = bot;
  538             openfile->current_x = bot_x;
  539         }
  540     }
  541 #endif /* !NANO_TINY */
  542 
  543     came_full_circle = FALSE;
  544 
  545     while (TRUE) {
  546         int choice = 0;
  547         int result = findnextstr(needle, whole_word_only, modus,
  548                         &match_len, skipone, real_current, *real_current_x);
  549 
  550         /* If nothing more was found, or the user aborted, stop looping. */
  551         if (result < 1) {
  552             if (result < 0)
  553                 numreplaced = -2;  /* It's a Cancel instead of Not found. */
  554             break;
  555         }
  556 
  557 #ifndef NANO_TINY
  558         /* An occurrence outside of the marked region means we're done. */
  559         if (was_mark && (openfile->current->lineno > bot->lineno ||
  560                                 openfile->current->lineno < top->lineno ||
  561                                 (openfile->current == bot &&
  562                                 openfile->current_x + match_len > bot_x) ||
  563                                 (openfile->current == top &&
  564                                 openfile->current_x < top_x)))
  565             break;
  566 #endif
  567 
  568         /* Indicate that we found the search string. */
  569         if (numreplaced == -1)
  570             numreplaced = 0;
  571 
  572         if (!replaceall) {
  573             spotlighted = TRUE;
  574             light_from_col = xplustabs();
  575             light_to_col = wideness(openfile->current->data,
  576                                         openfile->current_x + match_len);
  577 
  578             /* Refresh the edit window, scrolling it if necessary. */
  579             edit_refresh();
  580 
  581             /* TRANSLATORS: This is a prompt. */
  582             choice = do_yesno_prompt(TRUE, _("Replace this instance?"));
  583 
  584             spotlighted = FALSE;
  585 
  586             if (choice == -1)  /* The replacing was cancelled. */
  587                 break;
  588             else if (choice == 2)
  589                 replaceall = TRUE;
  590 
  591             /* When "No" or moving backwards, the search routine should
  592              * first move one character further before continuing. */
  593             skipone = (choice == 0 || ISSET(BACKWARDS_SEARCH));
  594         }
  595 
  596         if (choice == 1 || replaceall) {  /* Yes, replace it. */
  597             char *copy;
  598             size_t length_change;
  599 
  600 #ifndef NANO_TINY
  601             add_undo(REPLACE);
  602 #endif
  603             copy = replace_line(needle);
  604 
  605             length_change = strlen(copy) - strlen(openfile->current->data);
  606 
  607 #ifndef NANO_TINY
  608             /* If the mark was on and it was located after the cursor,
  609              * then adjust its x position for any text length changes. */
  610             if (was_mark && !right_side_up) {
  611                 if (openfile->current == was_mark &&
  612                         openfile->mark_x > openfile->current_x) {
  613                     if (openfile->mark_x < openfile->current_x + match_len)
  614                         openfile->mark_x = openfile->current_x;
  615                     else
  616                         openfile->mark_x += length_change;
  617                     bot_x = openfile->mark_x;
  618                 }
  619             }
  620 
  621             /* If the mark was not on or it was before the cursor, then
  622              * adjust the cursor's x position for any text length changes. */
  623             if (!was_mark || right_side_up) {
  624 #endif
  625                 if (openfile->current == real_current &&
  626                         openfile->current_x < *real_current_x) {
  627                     if (*real_current_x < openfile->current_x + match_len)
  628                         *real_current_x = openfile->current_x + match_len;
  629                     *real_current_x += length_change;
  630 #ifndef NANO_TINY
  631                     bot_x = *real_current_x;
  632                 }
  633 #endif
  634             }
  635 
  636             /* Don't find the same zero-length or BOL match again. */
  637             if (match_len == 0 || (*needle == '^' && ISSET(USE_REGEXP)))
  638                 skipone = TRUE;
  639 
  640             /* When moving forward, put the cursor just after the replacement
  641              * text, so that searching will continue there. */
  642             if (!ISSET(BACKWARDS_SEARCH))
  643                 openfile->current_x += match_len + length_change;
  644 
  645             /* Update the file size, and put the changed line into place. */
  646             openfile->totsize += mbstrlen(copy) - mbstrlen(openfile->current->data);
  647             free(openfile->current->data);
  648             openfile->current->data = copy;
  649 
  650             if (!replaceall) {
  651 #ifdef ENABLE_COLOR
  652                 /* When doing syntax coloring, the replacement might require
  653                  * a change of colors, so refresh the whole edit window. */
  654                 if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX))
  655                     edit_refresh();
  656                 else
  657 #endif
  658                     update_line(openfile->current, openfile->current_x);
  659             }
  660 
  661             set_modified();
  662             as_an_at = TRUE;
  663             numreplaced++;
  664         }
  665     }
  666 
  667     if (numreplaced == -1)
  668         not_found_msg(needle);
  669 #ifdef ENABLE_COLOR
  670     else if (numreplaced > 0)
  671         refresh_needed = TRUE;
  672 #endif
  673 #ifndef NANO_TINY
  674     openfile->mark = was_mark;
  675 #endif
  676 
  677     /* If "automatic newline" is enabled, and text has been added to the
  678      * magic line, make a new magic line. */
  679     if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0')
  680         new_magicline();
  681 
  682     return numreplaced;
  683 }
  684 
  685 /* Replace a string. */
  686 void do_replace(void)
  687 {
  688     if (ISSET(VIEW_MODE))
  689         print_view_warning();
  690     else {
  691         UNSET(BACKWARDS_SEARCH);
  692         search_init(TRUE, FALSE);
  693     }
  694 }
  695 
  696 /* Ask the user what the already given search string should be replaced with. */
  697 void ask_for_replacement(void)
  698 {
  699     linestruct *edittop_save, *begin;
  700     size_t firstcolumn_save, begin_x;
  701     ssize_t numreplaced;
  702     int response = do_prompt(FALSE, FALSE, MREPLACEWITH, NULL,
  703                         /* TRANSLATORS: This is a prompt. */
  704                         &replace_history, edit_refresh, _("Replace with"));
  705 
  706 #ifdef ENABLE_HISTORIES
  707     /* If the replace string is not "", add it to the replace history list. */
  708     if (response == 0)
  709         update_history(&replace_history, answer);
  710 #endif
  711 
  712     /* When cancelled, or when a function was run, get out. */
  713     if (response == -1) {
  714         statusbar(_("Cancelled"));
  715         return;
  716     } else if (response > 0)
  717         return;
  718 
  719     /* Save where we are. */
  720     edittop_save = openfile->edittop;
  721     firstcolumn_save = openfile->firstcolumn;
  722     begin = openfile->current;
  723     begin_x = openfile->current_x;
  724 
  725     numreplaced = do_replace_loop(last_search, FALSE, begin, &begin_x);
  726 
  727     /* Restore where we were. */
  728     openfile->edittop = edittop_save;
  729     openfile->firstcolumn = firstcolumn_save;
  730     openfile->current = begin;
  731     openfile->current_x = begin_x;
  732     refresh_needed = TRUE;
  733 
  734     if (numreplaced >= 0)
  735         statusline(HUSH, P_("Replaced %zd occurrence",
  736                 "Replaced %zd occurrences", numreplaced), numreplaced);
  737 }
  738 
  739 /* Go to the specified line and x position. */
  740 void goto_line_posx(ssize_t line, size_t pos_x)
  741 {
  742     for (openfile->current = openfile->filetop; line > 1 &&
  743                 openfile->current != openfile->filebot; line--)
  744         openfile->current = openfile->current->next;
  745 
  746     openfile->current_x = pos_x;
  747     openfile->placewewant = xplustabs();
  748 
  749     refresh_needed = TRUE;
  750 }
  751 
  752 /* Go to the specified line and column, or ask for them if interactive
  753  * is TRUE.  In the latter case also update the screen afterwards.
  754  * Note that both the line and column number should be one-based. */
  755 void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer,
  756         bool interactive)
  757 {
  758     if (interactive) {
  759         /* Ask for the line and column. */
  760         int response = do_prompt(FALSE, FALSE, MGOTOLINE,
  761                         use_answer ? answer : NULL, NULL, edit_refresh,
  762                         /* TRANSLATORS: This is a prompt. */
  763                         _("Enter line number, column number"));
  764 
  765         /* If the user cancelled or gave a blank answer, get out. */
  766         if (response < 0) {
  767             statusbar(_("Cancelled"));
  768             return;
  769         }
  770 
  771         if (func_from_key(&response) == flip_goto) {
  772             UNSET(BACKWARDS_SEARCH);
  773             /* Retain what the user typed so far and switch to searching. */
  774             search_init(FALSE, TRUE);
  775             return;
  776         }
  777 
  778         /* If a function was executed, we're done here. */
  779         if (response > 0)
  780             return;
  781 
  782         /* Try to extract one or two numbers from the user's response. */
  783         if (!parse_line_column(answer, &line, &column)) {
  784             statusline(ALERT, _("Invalid line or column number"));
  785             return;
  786         }
  787     } else {
  788         if (line == 0)
  789             line = openfile->current->lineno;
  790 
  791         if (column == 0)
  792             column = openfile->placewewant + 1;
  793     }
  794 
  795     /* Take a negative line number to mean: from the end of the file. */
  796     if (line < 0)
  797         line = openfile->filebot->lineno + line + 1;
  798     if (line < 1)
  799         line = 1;
  800 
  801     /* Iterate to the requested line. */
  802     for (openfile->current = openfile->filetop; line > 1 &&
  803                 openfile->current != openfile->filebot; line--)
  804         openfile->current = openfile->current->next;
  805 
  806     /* Take a negative column number to mean: from the end of the line. */
  807     if (column < 0)
  808         column = breadth(openfile->current->data) + column + 2;
  809     if (column < 1)
  810         column = 1;
  811 
  812     /* Set the x position that corresponds to the requested column. */
  813     openfile->current_x = actual_x(openfile->current->data, column - 1);
  814     openfile->placewewant = column - 1;
  815 
  816 #ifndef NANO_TINY
  817     if (ISSET(SOFTWRAP) && openfile->placewewant / editwincols >
  818                         breadth(openfile->current->data) / editwincols)
  819         openfile->placewewant = breadth(openfile->current->data);
  820 #endif
  821 
  822     /* When the position was manually given, center the target line. */
  823     if (interactive) {
  824         adjust_viewport(CENTERING);
  825         refresh_needed = TRUE;
  826     } else {
  827         int rows_from_tail;
  828 
  829 #ifndef NANO_TINY
  830         if (ISSET(SOFTWRAP)) {
  831             linestruct *currentline = openfile->current;
  832             size_t leftedge = leftedge_for(xplustabs(), openfile->current);
  833 
  834             rows_from_tail = (editwinrows / 2) - go_forward_chunks(
  835                                 editwinrows / 2, &currentline, &leftedge);
  836         } else
  837 #endif
  838             rows_from_tail = openfile->filebot->lineno -
  839                                 openfile->current->lineno;
  840 
  841         /* If the target line is close to the tail of the file, put the last
  842          * line or chunk on the bottom line of the screen; otherwise, just
  843          * center the target line. */
  844         if (rows_from_tail < editwinrows / 2 && !ISSET(JUMPY_SCROLLING)) {
  845             openfile->current_y = editwinrows - 1 - rows_from_tail;
  846             adjust_viewport(STATIONARY);
  847         } else
  848             adjust_viewport(CENTERING);
  849     }
  850 }
  851 
  852 /* Go to the specified line and column, asking for them beforehand. */
  853 void do_gotolinecolumn_void(void)
  854 {
  855     do_gotolinecolumn(openfile->current->lineno,
  856                         openfile->placewewant + 1, FALSE, TRUE);
  857 }
  858 
  859 #ifndef NANO_TINY
  860 /* Search, starting from the current position, for any of the two characters
  861  * in bracket_pair.  If reverse is TRUE, search backwards, otherwise forwards.
  862  * Return TRUE when one of the brackets was found, and FALSE otherwise. */
  863 bool find_a_bracket(bool reverse, const char *bracket_pair)
  864 {
  865     linestruct *line = openfile->current;
  866     const char *pointer, *found;
  867 
  868     /* Step away from the current bracket, either backwards or forwards. */
  869     if (reverse) {
  870         if (openfile->current_x == 0) {
  871             line = line->prev;
  872             if (line == NULL)
  873                 return FALSE;
  874             pointer = line->data + strlen(line->data);
  875         } else
  876             pointer = line->data + step_left(line->data, openfile->current_x);
  877     } else
  878         pointer = line->data + step_right(line->data, openfile->current_x);
  879 
  880     /* Now seek for any of the two brackets, either backwards or forwards. */
  881     while (TRUE) {
  882         if (reverse)
  883             found = mbrevstrpbrk(line->data, bracket_pair, pointer);
  884         else
  885             found = mbstrpbrk(pointer, bracket_pair);
  886 
  887         if (found)
  888             break;
  889 
  890         if (reverse)
  891             line = line->prev;
  892         else
  893             line = line->next;
  894 
  895         /* If we've reached the start or end of the buffer, get out. */
  896         if (line == NULL)
  897             return FALSE;
  898 
  899         pointer = line->data;
  900         if (reverse)
  901             pointer += strlen(line->data);
  902     }
  903 
  904     /* Set the current position to the found bracket. */
  905     openfile->current = line;
  906     openfile->current_x = found - line->data;
  907 
  908     return TRUE;
  909 }
  910 
  911 /* Search for a match to the bracket at the current cursor position, if
  912  * there is one. */
  913 void do_find_bracket(void)
  914 {
  915     linestruct *was_current = openfile->current;
  916     size_t was_current_x = openfile->current_x;
  917         /* The current cursor position, in case we don't find a complement. */
  918     const char *ch;
  919         /* The location in matchbrackets of the bracket under the cursor. */
  920     int ch_len;
  921         /* The length of ch in bytes. */
  922     const char *wanted_ch;
  923         /* The location in matchbrackets of the complementing bracket. */
  924     int wanted_ch_len;
  925         /* The length of wanted_ch in bytes. */
  926     char bracket_pair[MAXCHARLEN * 2 + 1];
  927         /* The pair of characters in ch and wanted_ch. */
  928     size_t halfway = 0;
  929         /* The index in matchbrackets where the closing brackets start. */
  930     size_t charcount = mbstrlen(matchbrackets) / 2;
  931         /* Half the number of characters in matchbrackets. */
  932     size_t balance = 1;
  933         /* The initial bracket count. */
  934     bool reverse;
  935         /* The direction we search. */
  936 
  937     ch = mbstrchr(matchbrackets, openfile->current->data + openfile->current_x);
  938 
  939     if (ch == NULL) {
  940         statusbar(_("Not a bracket"));
  941         return;
  942     }
  943 
  944     /* Find the halfway point in matchbrackets, where the closing ones start. */
  945     for (size_t i = 0; i < charcount; i++)
  946         halfway += char_length(matchbrackets + halfway);
  947 
  948     /* When on a closing bracket, we have to search backwards for a matching
  949      * opening bracket; otherwise, forward for a matching closing bracket. */
  950     reverse = (ch >= (matchbrackets + halfway));
  951 
  952     /* Step half the number of total characters either backwards or forwards
  953      * through matchbrackets to find the wanted complementary bracket. */
  954     wanted_ch = ch;
  955     while (charcount-- > 0) {
  956         if (reverse)
  957             wanted_ch = matchbrackets + step_left(matchbrackets,
  958                                                     wanted_ch - matchbrackets);
  959         else
  960             wanted_ch += char_length(wanted_ch);
  961     }
  962 
  963     ch_len = char_length(ch);
  964     wanted_ch_len = char_length(wanted_ch);
  965 
  966     /* Copy the two complementary brackets into a single string. */
  967     strncpy(bracket_pair, ch, ch_len);
  968     strncpy(bracket_pair + ch_len, wanted_ch, wanted_ch_len);
  969     bracket_pair[ch_len + wanted_ch_len] = '\0';
  970 
  971     while (find_a_bracket(reverse, bracket_pair)) {
  972         /* Increment/decrement balance for an identical/other bracket. */
  973         balance += (strncmp(openfile->current->data + openfile->current_x,
  974                             ch, ch_len) == 0) ? 1 : -1;
  975 
  976         /* When balance reached zero, we've found the complementary bracket. */
  977         if (balance == 0) {
  978             edit_redraw(was_current, FLOWING);
  979             return;
  980         }
  981     }
  982 
  983     statusbar(_("No matching bracket"));
  984 
  985     /* Restore the cursor position. */
  986     openfile->current = was_current;
  987     openfile->current_x = was_current_x;
  988 }
  989 #endif /* !NANO_TINY */