"Fossies" - the Fresh Open Source Software Archive

Member "nano-4.5/src/browser.c" (4 Oct 2019, 24523 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 "browser.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  *   browser.c  --  This file is part of GNU nano.                        *
    3  *                                                                        *
    4  *   Copyright (C) 2001-2011, 2013-2019 Free Software Foundation, Inc.    *
    5  *   Copyright (C) 2015-2016 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 <errno.h>
   25 #include <stdint.h>
   26 #include <string.h>
   27 #include <unistd.h>
   28 
   29 #ifdef ENABLE_BROWSER
   30 
   31 static char **filelist = NULL;
   32         /* The list of files to display in the file browser. */
   33 static size_t filelist_len = 0;
   34         /* The number of files in the list. */
   35 static size_t width = 0;
   36         /* The number of files that we can display per screen row. */
   37 static size_t longest = 0;
   38         /* The number of columns in the longest filename in the list. */
   39 static size_t selected = 0;
   40         /* The currently selected filename in the list; zero-based. */
   41 
   42 /* Our main file browser function.  path is the tilde-expanded path we
   43  * start browsing from. */
   44 char *do_browser(char *path)
   45 {
   46     char *retval = NULL;
   47     int kbinput;
   48     char *present_name = NULL;
   49         /* The name of the currently selected file, or of the directory we
   50          * were in before backing up to "..". */
   51     size_t old_selected;
   52         /* The number of the selected file before the current selected file. */
   53     functionptrtype func;
   54         /* The function of the key the user typed in. */
   55     DIR *dir;
   56         /* The directory whose contents we are showing. */
   57 
   58   read_directory_contents:
   59         /* We come here when we refresh or select a new directory. */
   60 
   61     path = free_and_assign(path, get_full_path(path));
   62 
   63     if (path != NULL)
   64         dir = opendir(path);
   65 
   66     if (path == NULL || dir == NULL) {
   67         statusline(ALERT, _("Cannot open directory: %s"), strerror(errno));
   68         /* If we don't have a file list yet, there is nothing to show. */
   69         if (filelist == NULL) {
   70             napms(1200);
   71             lastmessage = HUSH;
   72             free(path);
   73             free(present_name);
   74             return NULL;
   75         }
   76         path = mallocstrcpy(path, present_path);
   77         present_name = mallocstrcpy(present_name, filelist[selected]);
   78     }
   79 
   80     if (dir != NULL) {
   81         /* Get the file list, and set longest and width in the process. */
   82         read_the_list(path, dir);
   83         closedir(dir);
   84         dir = NULL;
   85     }
   86 
   87     /* If given, reselect the present_name and then discard it. */
   88     if (present_name != NULL) {
   89         browser_select_dirname(present_name);
   90 
   91         free(present_name);
   92         present_name = NULL;
   93     /* Otherwise, select the first file or directory in the list. */
   94     } else
   95         selected = 0;
   96 
   97     old_selected = (size_t)-1;
   98 
   99     present_path = mallocstrcpy(present_path, path);
  100 
  101     titlebar(path);
  102 
  103     while (TRUE) {
  104         lastmessage = HUSH;
  105 
  106         bottombars(MBROWSER);
  107 
  108         /* Display (or redisplay) the file list if the list itself or
  109          * the selected file has changed. */
  110         if (old_selected != selected || ISSET(SHOW_CURSOR))
  111             browser_refresh();
  112 
  113         old_selected = selected;
  114 
  115         kbinput = get_kbinput(edit, ISSET(SHOW_CURSOR));
  116 
  117 #ifdef ENABLE_MOUSE
  118         if (kbinput == KEY_MOUSE) {
  119             int mouse_x, mouse_y;
  120 
  121             /* We can click on the edit window to select a filename. */
  122             if (get_mouseinput(&mouse_y, &mouse_x, TRUE) == 0 &&
  123                         wmouse_trafo(edit, &mouse_y, &mouse_x, FALSE)) {
  124                 /* longest is the width of each column.  There
  125                  * are two spaces between each column. */
  126                 selected = selected - selected % (editwinrows * width) +
  127                                 (mouse_y * width) + (mouse_x / (longest + 2));
  128 
  129                 /* If they clicked beyond the end of a row,
  130                  * select the last filename in that row. */
  131                 if (mouse_x > width * (longest + 2))
  132                     selected--;
  133 
  134                 /* If we're beyond the list, select the last filename. */
  135                 if (selected > filelist_len - 1)
  136                     selected = filelist_len - 1;
  137 
  138                 /* If we selected the same filename as last time, fake a
  139                  * press of the Enter key so that the file is read in. */
  140                 if (old_selected == selected)
  141                     unget_kbinput(KEY_ENTER, FALSE);
  142             }
  143 
  144             continue;
  145         }
  146 #endif /* ENABLE_MOUSE */
  147 
  148         func = parse_browser_input(&kbinput);
  149 
  150         if (func == total_refresh) {
  151             total_redraw();
  152 #ifndef NANO_TINY
  153             /* Simulate a window resize to force a directory reread. */
  154             kbinput = KEY_WINCH;
  155 #endif
  156         } else if (func == do_help_void) {
  157 #ifdef ENABLE_HELP
  158             do_help_void();
  159 #ifndef NANO_TINY
  160             /* The window dimensions might have changed, so act as if. */
  161             kbinput = KEY_WINCH;
  162 #endif
  163 #else
  164             say_there_is_no_help();
  165 #endif
  166         } else if (func == do_search_forward) {
  167             do_filesearch(FORWARD);
  168         } else if (func == do_search_backward) {
  169             do_filesearch(BACKWARD);
  170         } else if (func == do_findprevious) {
  171             do_fileresearch(BACKWARD);
  172         } else if (func == do_findnext) {
  173             do_fileresearch(FORWARD);
  174         } else if (func == do_left) {
  175             if (selected > 0)
  176                 selected--;
  177         } else if (func == do_right) {
  178             if (selected < filelist_len - 1)
  179                 selected++;
  180         } else if (func == do_prev_word_void) {
  181             selected -= (selected % width);
  182         } else if (func == do_next_word_void) {
  183             selected += width - 1 - (selected % width);
  184             if (selected >= filelist_len)
  185                 selected = filelist_len - 1;
  186         } else if (func == do_up) {
  187             if (selected >= width)
  188                 selected -= width;
  189         } else if (func == do_down) {
  190             if (selected + width <= filelist_len - 1)
  191                 selected += width;
  192         } else if (func == do_prev_block) {
  193             selected = ((selected / (editwinrows * width)) *
  194                                 editwinrows * width) + selected % width;
  195         } else if (func == do_next_block) {
  196             selected = ((selected / (editwinrows * width)) *
  197                                 editwinrows * width) + selected % width +
  198                                 editwinrows * width - width;
  199             if (selected >= filelist_len)
  200                 selected = (filelist_len / width) * width + selected % width;
  201             if (selected >= filelist_len)
  202                 selected -= width;
  203         } else if (func == do_page_up) {
  204             if (selected < width)
  205                 selected = 0;
  206             else if (selected < editwinrows * width)
  207                 selected = selected % width;
  208             else
  209                 selected -= editwinrows * width;
  210         } else if (func == do_page_down) {
  211             if (selected + width >= filelist_len - 1)
  212                 selected = filelist_len - 1;
  213             else if (selected + editwinrows * width >= filelist_len)
  214                 selected = (selected + editwinrows * width - filelist_len) %
  215                                 width + filelist_len - width;
  216             else
  217                 selected += editwinrows * width;
  218         } else if (func == to_first_file) {
  219             selected = 0;
  220         } else if (func == to_last_file) {
  221             selected = filelist_len - 1;
  222         } else if (func == goto_dir_void) {
  223             /* Ask for the directory to go to. */
  224             if (do_prompt(TRUE, FALSE, MGOTODIR, NULL, NULL,
  225                             /* TRANSLATORS: This is a prompt. */
  226                             browser_refresh, _("Go To Directory")) < 0) {
  227                 statusbar(_("Cancelled"));
  228                 continue;
  229             }
  230 
  231             path = free_and_assign(path, real_dir_from_tilde(answer));
  232 
  233             /* If the given path is relative, join it with the current path. */
  234             if (*path != '/') {
  235                 path = charealloc(path, strlen(present_path) +
  236                                                 strlen(answer) + 1);
  237                 sprintf(path, "%s%s", present_path, answer);
  238             }
  239 
  240 #ifdef ENABLE_OPERATINGDIR
  241             if (outside_of_confinement(path, FALSE)) {
  242                 /* TRANSLATORS: This refers to the confining effect of the
  243                  * option --operatingdir, not of --restricted. */
  244                 statusline(ALERT, _("Can't go outside of %s"), operating_dir);
  245                 path = mallocstrcpy(path, present_path);
  246                 continue;
  247             }
  248 #endif
  249             /* Snip any trailing slashes, so the name can be compared. */
  250             while (strlen(path) > 1 && path[strlen(path) - 1] == '/')
  251                 path[strlen(path) - 1] = '\0';
  252 
  253             /* In case the specified directory cannot be entered, select it
  254              * (if it is in the current list) so it will be highlighted. */
  255             for (size_t j = 0; j < filelist_len; j++)
  256                 if (strcmp(filelist[j], path) == 0)
  257                     selected = j;
  258 
  259             /* Try opening and reading the specified directory. */
  260             goto read_directory_contents;
  261         } else if (func == do_enter) {
  262             struct stat st;
  263 
  264             /* It isn't possible to move up from the root directory. */
  265             if (strcmp(filelist[selected], "/..") == 0) {
  266                 statusline(ALERT, _("Can't move up a directory"));
  267                 continue;
  268             }
  269 
  270 #ifdef ENABLE_OPERATINGDIR
  271             /* Note: The selected file can be outside the operating
  272              * directory if it's ".." or if it's a symlink to a
  273              * directory outside the operating directory. */
  274             if (outside_of_confinement(filelist[selected], FALSE)) {
  275                 statusline(ALERT, _("Can't go outside of %s"), operating_dir);
  276                 continue;
  277             }
  278 #endif
  279             /* If for some reason the file is inaccessible, complain. */
  280             if (stat(filelist[selected], &st) == -1) {
  281                 statusline(ALERT, _("Error reading %s: %s"),
  282                                 filelist[selected], strerror(errno));
  283                 continue;
  284             }
  285 
  286             /* If it isn't a directory, a file was selected -- we're done. */
  287             if (!S_ISDIR(st.st_mode)) {
  288                 retval = mallocstrcpy(NULL, filelist[selected]);
  289                 break;
  290             }
  291 
  292             /* If we are moving up one level, remember where we came from, so
  293              * this directory can be highlighted and easily reentered. */
  294             if (strcmp(tail(filelist[selected]), "..") == 0)
  295                 present_name = strip_last_component(filelist[selected]);
  296 
  297             /* Try opening and reading the selected directory. */
  298             path = mallocstrcpy(path, filelist[selected]);
  299             goto read_directory_contents;
  300 #ifdef ENABLE_NANORC
  301         } else if (func == (functionptrtype)implant) {
  302             implant(first_sc_for(MBROWSER, func)->expansion);
  303 #endif
  304 #ifndef NANO_TINY
  305         } else if (kbinput == KEY_WINCH) {
  306             ;  /* Nothing to do. */
  307 #endif
  308         } else if (func == do_exit) {
  309             break;
  310         } else
  311             unbound_key(kbinput);
  312 
  313 #ifndef NANO_TINY
  314         /* If the window resized, refresh the file list. */
  315         if (kbinput == KEY_WINCH) {
  316             /* Remember the selected file, to be able to reselect it. */
  317             present_name = mallocstrcpy(NULL, filelist[selected]);
  318             /* Reread the contents of the current directory. */
  319             goto read_directory_contents;
  320         }
  321 #endif
  322     }
  323 
  324     titlebar(NULL);
  325     edit_refresh();
  326 
  327     free(path);
  328 
  329     free_chararray(filelist, filelist_len);
  330     filelist = NULL;
  331     filelist_len = 0;
  332 
  333     return retval;
  334 }
  335 
  336 /* The file browser front end.  We check to see if inpath has a
  337  * directory in it.  If it does, we start do_browser() from there.
  338  * Otherwise, we start do_browser() from the current directory. */
  339 char *do_browse_from(const char *inpath)
  340 {
  341     struct stat st;
  342     char *path;
  343         /* This holds the tilde-expanded version of inpath. */
  344 
  345     path = real_dir_from_tilde(inpath);
  346 
  347     /* Perhaps path is a directory.  If so, we'll pass it to
  348      * do_browser().  Or perhaps path is a directory / a file.  If so,
  349      * we'll try stripping off the last path element and passing it to
  350      * do_browser().  Or perhaps path doesn't have a directory portion
  351      * at all.  If so, we'll just pass the current directory to
  352      * do_browser(). */
  353     if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
  354         path = free_and_assign(path, strip_last_component(path));
  355 
  356         if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
  357             char *currentdir = charalloc(PATH_MAX + 1);
  358 
  359             free(path);
  360             path = getcwd(currentdir, PATH_MAX + 1);
  361 
  362             if (path == NULL) {
  363                 free(currentdir);
  364                 statusline(MILD, _("The working directory has disappeared"));
  365                 beep();
  366                 napms(1200);
  367                 return NULL;
  368             }
  369         }
  370     }
  371 
  372 #ifdef ENABLE_OPERATINGDIR
  373     /* If the resulting path isn't in the operating directory, use
  374      * the operating directory instead. */
  375     if (outside_of_confinement(path, FALSE))
  376         path = mallocstrcpy(path, operating_dir);
  377 #endif
  378 
  379     return do_browser(path);
  380 }
  381 
  382 /* Set filelist to the list of files contained in the directory path,
  383  * set filelist_len to the number of files in that list, set longest to
  384  * the width in columns of the longest filename in that list (between 15
  385  * and COLS), and set width to the number of files that we can display
  386  * per screen row.  And sort the list too. */
  387 void read_the_list(const char *path, DIR *dir)
  388 {
  389     const struct dirent *nextdir;
  390     size_t i = 0, path_len = strlen(path);
  391 
  392     longest = 0;
  393 
  394     /* Find the length of the longest filename in the current folder. */
  395     while ((nextdir = readdir(dir)) != NULL) {
  396         size_t name_len = breadth(nextdir->d_name);
  397 
  398         if (name_len > longest)
  399             longest = name_len;
  400 
  401         i++;
  402     }
  403 
  404     /* Put 10 characters' worth of blank space between columns of filenames
  405      * in the list whenever possible, as Pico does. */
  406     longest += 10;
  407 
  408     /* If needed, make room for ".. (parent dir)". */
  409     if (longest < 15)
  410         longest = 15;
  411     /* Make sure we're not wider than the window. */
  412     if (longest > COLS)
  413         longest = COLS;
  414 
  415     rewinddir(dir);
  416 
  417     free_chararray(filelist, filelist_len);
  418 
  419     filelist_len = i;
  420 
  421     filelist = (char **)nmalloc(filelist_len * sizeof(char *));
  422 
  423     i = 0;
  424 
  425     while ((nextdir = readdir(dir)) != NULL && i < filelist_len) {
  426         /* Don't show the "." entry. */
  427         if (strcmp(nextdir->d_name, ".") == 0)
  428             continue;
  429 
  430         filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1);
  431         sprintf(filelist[i], "%s%s", path, nextdir->d_name);
  432 
  433         i++;
  434     }
  435 
  436     /* Maybe the number of files in the directory changed between the
  437      * first time we scanned and the second.  i is the actual length of
  438      * filelist, so record it. */
  439     filelist_len = i;
  440 
  441     /* Sort the list of names. */
  442     qsort(filelist, filelist_len, sizeof(char *), diralphasort);
  443 
  444     /* Calculate how many files fit on a line -- feigning room for two
  445      * spaces beyond the right edge, and adding two spaces of padding
  446      * between columns. */
  447     width = (COLS + 2) / (longest + 2);
  448 }
  449 
  450 /* Return the function that is bound to the given key, accepting certain
  451  * plain characters too, for compatibility with Pico. */
  452 functionptrtype parse_browser_input(int *kbinput)
  453 {
  454     if (!meta_key) {
  455         switch (*kbinput) {
  456             case BS_CODE:
  457             case '-':
  458                 return do_page_up;
  459             case ' ':
  460                 return do_page_down;
  461             case 'W':
  462             case 'w':
  463             case '/':
  464                 return do_search_forward;
  465             case 'N':
  466                 return do_findprevious;
  467             case 'n':
  468                 return do_findnext;
  469             case 'G':
  470             case 'g':
  471                 return goto_dir_void;
  472             case '?':
  473                 return do_help_void;
  474             case 'S':
  475             case 's':
  476                 return do_enter;
  477             case 'E':
  478             case 'e':
  479             case 'Q':
  480             case 'q':
  481             case 'X':
  482             case 'x':
  483                 return do_exit;
  484         }
  485     }
  486     return func_from_key(kbinput);
  487 }
  488 
  489 /* Set width to the number of files that we can display per screen row,
  490  * if necessary, and display the list of files. */
  491 void browser_refresh(void)
  492 {
  493     size_t i;
  494     int row = 0, col = 0;
  495         /* The current row and column while the list is getting displayed. */
  496     int the_row = 0, the_column = 0;
  497         /* The row and column of the selected item. */
  498     char *info;
  499         /* The additional information that we'll display about a file. */
  500 
  501     titlebar(present_path);
  502     blank_edit();
  503 
  504     wmove(edit, 0, 0);
  505 
  506     i = selected - selected % (editwinrows * width);
  507 
  508     for (; i < filelist_len && row < editwinrows; i++) {
  509         struct stat st;
  510         const char *thename = tail(filelist[i]);
  511                 /* The filename we display, minus the path. */
  512         size_t namelen = breadth(thename);
  513                 /* The length of the filename in columns. */
  514         size_t infolen;
  515                 /* The length of the file information in columns. */
  516         size_t infomaxlen = 7;
  517                 /* The maximum length of the file information in columns:
  518                  * normally seven, but will be twelve for "(parent dir)". */
  519         bool dots = (COLS >= 15 && namelen >= longest - infomaxlen);
  520                 /* Whether to put an ellipsis before the filename?  We don't
  521                  * waste space on dots when there are fewer than 15 columns. */
  522         char *disp = display_string(thename, dots ?
  523                 namelen + infomaxlen + 4 - longest : 0, longest, FALSE, FALSE);
  524                 /* The filename (or a fragment of it) in displayable format.
  525                  * When a fragment, account for dots plus one space padding. */
  526 
  527         /* If this is the selected item, draw its highlighted bar upfront, and
  528          * remember its location to be able to place the cursor on it. */
  529         if (i == selected) {
  530             wattron(edit, interface_color_pair[SELECTED_TEXT]);
  531             mvwprintw(edit, row, col, "%*s", longest, " ");
  532             the_row = row;
  533             the_column = col;
  534         }
  535 
  536         /* If the name is too long, we display something like "...ename". */
  537         if (dots)
  538             mvwaddstr(edit, row, col, "...");
  539         mvwaddstr(edit, row, dots ? col + 3 : col, disp);
  540 
  541         free(disp);
  542 
  543         col += longest;
  544 
  545         /* Show information about the file: "--" for symlinks (except when
  546          * they point to a directory) and for files that have disappeared,
  547          * "(dir)" for directories, and the file size for normal files. */
  548         if (lstat(filelist[i], &st) == -1 || S_ISLNK(st.st_mode)) {
  549             if (stat(filelist[i], &st) == -1 || !S_ISDIR(st.st_mode))
  550                 info = mallocstrcpy(NULL, "--");
  551             else
  552                 /* TRANSLATORS: Try to keep this at most 7 characters. */
  553                 info = mallocstrcpy(NULL, _("(dir)"));
  554         } else if (S_ISDIR(st.st_mode)) {
  555             if (strcmp(thename, "..") == 0) {
  556                 /* TRANSLATORS: Try to keep this at most 12 characters. */
  557                 info = mallocstrcpy(NULL, _("(parent dir)"));
  558                 infomaxlen = 12;
  559             } else
  560                 info = mallocstrcpy(NULL, _("(dir)"));
  561         } else {
  562             off_t result = st.st_size;
  563             char modifier;
  564 
  565             info = charalloc(infomaxlen + 1);
  566 
  567             /* Massage the file size into a human-readable form. */
  568             if (st.st_size < (1 << 10))
  569                 modifier = ' ';  /* bytes */
  570             else if (st.st_size < (1 << 20)) {
  571                 result >>= 10;
  572                 modifier = 'K';  /* kilobytes */
  573             } else if (st.st_size < (1 << 30)) {
  574                 result >>= 20;
  575                 modifier = 'M';  /* megabytes */
  576             } else {
  577                 result >>= 30;
  578                 modifier = 'G';  /* gigabytes */
  579             }
  580 
  581             /* Show the size if less than a terabyte, else show "(huge)". */
  582             if (result < (1 << 10))
  583                 sprintf(info, "%4ju %cB", (intmax_t)result, modifier);
  584             else
  585                 /* TRANSLATORS: Try to keep this at most 7 characters.
  586                  * If necessary, you can leave out the parentheses. */
  587                 info = mallocstrcpy(info, _("(huge)"));
  588         }
  589 
  590         /* Make sure info takes up no more than infomaxlen columns. */
  591         infolen = breadth(info);
  592         if (infolen > infomaxlen) {
  593             info[actual_x(info, infomaxlen)] = '\0';
  594             infolen = infomaxlen;
  595         }
  596 
  597         mvwaddstr(edit, row, col - infolen, info);
  598 
  599         /* If this is the selected item, finish its highlighting. */
  600         if (i == selected)
  601             wattroff(edit, interface_color_pair[SELECTED_TEXT]);
  602 
  603         free(info);
  604 
  605         /* Add some space between the columns. */
  606         col += 2;
  607 
  608         /* If the next entry isn't going to fit on the current row,
  609          * move to the next row. */
  610         if (col > COLS - longest) {
  611             row++;
  612             col = 0;
  613         }
  614     }
  615 
  616     /* If requested, put the cursor on the selected item and switch it on. */
  617     if (ISSET(SHOW_CURSOR)) {
  618         wmove(edit, the_row, the_column);
  619         curs_set(1);
  620     }
  621 
  622     wnoutrefresh(edit);
  623 }
  624 
  625 /* Look for needle.  If we find it, set selected to its location.
  626  * Note that needle must be an exact match for a file in the list. */
  627 void browser_select_dirname(const char *needle)
  628 {
  629     size_t looking_at = 0;
  630 
  631     for (; looking_at < filelist_len; looking_at++) {
  632         if (strcmp(filelist[looking_at], needle) == 0) {
  633             selected = looking_at;
  634             break;
  635         }
  636     }
  637 
  638     /* If the sought name isn't found, move the highlight so that the
  639      * changed selection will be noticed. */
  640     if (looking_at == filelist_len) {
  641         --selected;
  642 
  643         /* Make sure we stay within the available range. */
  644         if (selected >= filelist_len)
  645             selected = filelist_len - 1;
  646     }
  647 }
  648 
  649 /* Prepare the prompt and ask the user what to search for.  If forwards is
  650  * TRUE, search forward in the list; otherwise, search backward.  Return -2
  651  * for a blank answer, -1 for Cancel, 0 when we have a string, and a
  652  * positive value when some function was run. */
  653 int filesearch_init(bool forwards)
  654 {
  655     char *thedefault;
  656     int response;
  657 
  658     /* If something was searched for before, show it between square brackets. */
  659     if (*last_search != '\0') {
  660         char *disp = display_string(last_search, 0, COLS / 3, FALSE, FALSE);
  661 
  662         thedefault = charalloc(strlen(disp) + 7);
  663         /* We use (COLS / 3) here because we need to see more on the line. */
  664         sprintf(thedefault, " [%s%s]", disp,
  665                 (breadth(last_search) > COLS / 3) ? "..." : "");
  666         free(disp);
  667     } else
  668         thedefault = mallocstrcpy(NULL, "");
  669 
  670     /* Now ask what to search for. */
  671     response = do_prompt(FALSE, FALSE, MWHEREISFILE, NULL, &search_history,
  672                         browser_refresh, "%s%s%s", _("Search"),
  673                         /* TRANSLATORS: A modifier of the Search prompt. */
  674                         !forwards ? _(" [Backwards]") : "", thedefault);
  675     free(thedefault);
  676 
  677     /* If only Enter was pressed but we have a previous string, it's okay. */
  678     if (response == -2 && *last_search != '\0')
  679         return 0;
  680 
  681     /* Otherwise negative responses are a bailout. */
  682     if (response < 0)
  683         statusbar(_("Cancelled"));
  684 
  685     return response;
  686 }
  687 
  688 /* Look for the given needle in the list of files.  If forwards is TRUE,
  689  * search forward in the list; otherwise, search backward. */
  690 void findfile(const char *needle, bool forwards)
  691 {
  692     size_t looking_at = selected;
  693         /* The location in the file list of the filename we're looking at. */
  694     const char *thename;
  695         /* The plain filename, without the path. */
  696     unsigned stash[sizeof(flags) / sizeof(flags[0])];
  697         /* A storage place for the current flag settings. */
  698 
  699     /* Save the settings of all flags. */
  700     memcpy(stash, flags, sizeof(flags));
  701 
  702     /* Search forward, case insensitive, and without regexes. */
  703     UNSET(BACKWARDS_SEARCH);
  704     UNSET(CASE_SENSITIVE);
  705     UNSET(USE_REGEXP);
  706 
  707     /* Step through each filename in the list until a match is found or
  708      * we've come back to the point where we started. */
  709     while (TRUE) {
  710         if (forwards) {
  711             if (looking_at++ == filelist_len - 1) {
  712                 looking_at = 0;
  713                 statusbar(_("Search Wrapped"));
  714             }
  715         } else {
  716             if (looking_at-- == 0) {
  717                 looking_at = filelist_len - 1;
  718                 statusbar(_("Search Wrapped"));
  719             }
  720         }
  721 
  722         /* Get the bare filename, without the path. */
  723         thename = tail(filelist[looking_at]);
  724 
  725         /* If the needle matches, we're done.  And if we're back at the file
  726          * where we started, it is the only occurrence. */
  727         if (strstrwrapper(thename, needle, thename)) {
  728             if (looking_at == selected)
  729                 statusbar(_("This is the only occurrence"));
  730             break;
  731         }
  732 
  733         /* If we're back at the beginning and didn't find any match... */
  734         if (looking_at == selected) {
  735             not_found_msg(needle);
  736             break;
  737         }
  738     }
  739 
  740     /* Restore the settings of all flags. */
  741     memcpy(flags, stash, sizeof(flags));
  742 
  743     /* Select the one we've found. */
  744     selected = looking_at;
  745 }
  746 
  747 /* Search for a filename.  If forwards is TRUE, search forward in the list;
  748  * otherwise, search backward.*/
  749 void do_filesearch(bool forwards)
  750 {
  751     /* If the user cancelled or jumped to first or last file, don't search. */
  752     if (filesearch_init(forwards) != 0)
  753         return;
  754 
  755     /* If answer is now "", copy last_search into answer. */
  756     if (*answer == '\0')
  757         answer = mallocstrcpy(answer, last_search);
  758     else
  759         last_search = mallocstrcpy(last_search, answer);
  760 
  761 #ifdef ENABLE_HISTORIES
  762     /* If answer is not empty, add the string to the search history list. */
  763     if (*answer != '\0')
  764         update_history(&search_history, answer);
  765 #endif
  766 
  767     findfile(answer, forwards);
  768 }
  769 
  770 /* Search again without prompting for the last given search string,
  771  * either forwards or backwards. */
  772 void do_fileresearch(bool forwards)
  773 {
  774     if (*last_search == '\0')
  775         statusbar(_("No current search pattern"));
  776     else
  777         findfile(last_search, forwards);
  778 }
  779 
  780 /* Select the first file in the list. */
  781 void to_first_file(void)
  782 {
  783     selected = 0;
  784 }
  785 
  786 /* Select the last file in the list. */
  787 void to_last_file(void)
  788 {
  789     selected = filelist_len - 1;
  790 }
  791 
  792 /* Strip one element from the end of path, and return the stripped path.
  793  * The returned string is dynamically allocated, and should be freed. */
  794 char *strip_last_component(const char *path)
  795 {
  796     char *copy = mallocstrcpy(NULL, path);
  797     char *last_slash = strrchr(copy, '/');
  798 
  799     if (last_slash != NULL)
  800         *last_slash = '\0';
  801 
  802     return copy;
  803 }
  804 
  805 #endif /* ENABLE_BROWSER */