Manual pages: mcmcdiffmceditmcview

root/src/filemanager/find.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. max
  2. parse_ignore_dirs
  3. find_load_options
  4. find_save_options
  5. add_to_list
  6. add_to_list_take
  7. stop_idle
  8. status_update
  9. found_num_update
  10. get_list_info
  11. find_check_regexp
  12. find_toggle_enable_ignore_dirs
  13. find_toggle_enable_params
  14. find_toggle_enable_content
  15. find_parm_callback
  16. find_parameters
  17. push_directory
  18. pop_directory
  19. queue_dir_free
  20. clear_stack
  21. insert_file
  22. find_add_match
  23. check_find_events
  24. search_content
  25. find_ignore_dir_search
  26. find_rotate_dash
  27. do_search
  28. init_find_vars
  29. find_do_view_edit
  30. view_edit_currently_selected_file
  31. find_calc_button_locations
  32. find_adjust_header
  33. find_relocate_buttons
  34. find_resize
  35. find_callback
  36. start_stop
  37. find_do_view_file
  38. find_do_edit_file
  39. setup_gui
  40. run_process
  41. kill_gui
  42. do_find
  43. find_cmd

   1 /*
   2    Find file command for the Midnight Commander
   3 
   4    Copyright (C) 1995-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written  by:
   8    Miguel de Icaza, 1995
   9    Slava Zanko <slavazanko@gmail.com>, 2013
  10    Andrew Borodin <aborodin@vmail.ru>, 2013-2022
  11 
  12    This file is part of the Midnight Commander.
  13 
  14    The Midnight Commander is free software: you can redistribute it
  15    and/or modify it under the terms of the GNU General Public License as
  16    published by the Free Software Foundation, either version 3 of the License,
  17    or (at your option) any later version.
  18 
  19    The Midnight Commander is distributed in the hope that it will be useful,
  20    but WITHOUT ANY WARRANTY; without even the implied warranty of
  21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22    GNU General Public License for more details.
  23 
  24    You should have received a copy of the GNU General Public License
  25    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  26  */
  27 
  28 /** \file find.c
  29  *  \brief Source: Find file command
  30  */
  31 
  32 #include <config.h>
  33 
  34 #include <ctype.h>
  35 #include <stdio.h>
  36 #include <stdlib.h>
  37 #include <string.h>
  38 #include <sys/stat.h>
  39 
  40 #include "lib/global.h"
  41 
  42 #include "lib/tty/tty.h"
  43 #include "lib/tty/key.h"
  44 #include "lib/skin.h"
  45 #include "lib/search.h"
  46 #include "lib/mcconfig.h"
  47 #include "lib/vfs/vfs.h"
  48 #include "lib/strutil.h"
  49 #include "lib/widget.h"
  50 #include "lib/util.h"  // canonicalize_pathname()
  51 
  52 #include "src/setup.h"    // verbose
  53 #include "src/history.h"  // MC_HISTORY_SHARED_SEARCH
  54 
  55 #include "dir.h"
  56 #include "cmd.h"  // find_cmd(), view_file_at_line()
  57 #include "boxes.h"
  58 #include "panelize.h"
  59 
  60 /*** global variables ****************************************************************************/
  61 
  62 /*** file scope macro definitions ****************************************************************/
  63 
  64 #define MAX_REFRESH_INTERVAL  (G_USEC_PER_SEC / 20)  // 50 ms
  65 #define MIN_REFRESH_FILE_SIZE (256 * 1024)           // 256 KB
  66 
  67 /*** file scope type declarations ****************************************************************/
  68 
  69 /* A couple of extra messages we need */
  70 enum
  71 {
  72     B_STOP = B_USER + 1,
  73     B_AGAIN,
  74     B_PANELIZE,
  75     B_TREE,
  76     B_VIEW
  77 };
  78 
  79 typedef enum
  80 {
  81     FIND_CONT = 0,
  82     FIND_SUSPEND,
  83     FIND_ABORT
  84 } FindProgressStatus;
  85 
  86 /* find file options */
  87 typedef struct
  88 {
  89     // file name options
  90     gboolean file_case_sens;
  91     gboolean file_pattern;
  92     gboolean find_recurs;
  93     gboolean follow_symlinks;
  94     gboolean skip_hidden;
  95     gboolean file_all_charsets;
  96 
  97     // file content options
  98     gboolean content_case_sens;
  99     gboolean content_regexp;
 100     gboolean content_first_hit;
 101     gboolean content_whole_words;
 102     gboolean content_all_charsets;
 103 
 104     // whether use ignore dirs or not
 105     gboolean ignore_dirs_enable;
 106     // list of directories to be ignored, separated by ':'
 107     char *ignore_dirs;
 108 } find_file_options_t;
 109 
 110 typedef struct
 111 {
 112     char *dir;
 113     gsize start;
 114     gsize end;
 115 } find_match_location_t;
 116 
 117 /*** forward declarations (file scope functions) *************************************************/
 118 
 119 /* button callbacks */
 120 static int start_stop (WButton *button, int action);
 121 static int find_do_view_file (WButton *button, int action);
 122 static int find_do_edit_file (WButton *button, int action);
 123 
 124 /*** file scope variables ************************************************************************/
 125 
 126 /* Parsed ignore dirs */
 127 static char **find_ignore_dirs = NULL;
 128 
 129 /* static variables to remember find parameters */
 130 static WInput *in_start;  // Start path
 131 static WInput *in_name;   // Filename
 132 static WInput *in_with;   // Text
 133 static WInput *in_ignore;
 134 static WLabel *content_label;        // 'Content:' label
 135 static WCheck *file_case_sens_cbox;  // "case sensitive" checkbox
 136 static WCheck *file_pattern_cbox;    // File name is glob or regexp
 137 static WCheck *recursively_cbox;
 138 static WCheck *follow_sym_cbox;
 139 static WCheck *skip_hidden_cbox;
 140 static WCheck *content_case_sens_cbox;    // "case sensitive" checkbox
 141 static WCheck *content_regexp_cbox;       // "find regular expression" checkbox
 142 static WCheck *content_first_hit_cbox;    // "First hit" checkbox"
 143 static WCheck *content_whole_words_cbox;  // "whole words" checkbox
 144 static WCheck *file_all_charsets_cbox;
 145 static WCheck *content_all_charsets_cbox;
 146 static WCheck *ignore_dirs_cbox;
 147 
 148 static gboolean running = FALSE;      // nice flag
 149 static char *find_pattern = NULL;     // Pattern to search
 150 static char *content_pattern = NULL;  // pattern to search inside files; if content_regexp_flag is
 151                                       // true, it contains the regex pattern, else the search string
 152 static gboolean content_is_empty = TRUE;  // remember content field state; initially is empty
 153 static unsigned long matches;             // Number of matches
 154 static gboolean is_start = FALSE;         // Status of the start/stop toggle button
 155 static char *old_dir = NULL;
 156 
 157 static gint64 last_refresh;
 158 
 159 /* Where did we stop */
 160 static gboolean resuming;
 161 static int last_line;
 162 static int last_pos;
 163 static off_t last_off;
 164 static int last_i;
 165 
 166 static size_t ignore_count = 0;
 167 
 168 static WDialog *find_dlg;        // The dialog
 169 static WLabel *status_label;     // Finished, Searching etc.
 170 static WLabel *found_num_label;  // Number of found items
 171 
 172 /* This keeps track of the directory stack */
 173 static GQueue dir_queue = G_QUEUE_INIT;
 174 
 175 static struct
 176 {
 177     int ret_cmd;
 178     button_flags_t flags;
 179     const char *text;
 180     int len;  // length including space and brackets
 181     int x;
 182     Widget *button;
 183     bcback_fn callback;
 184 } fbuts[] = {
 185     { B_ENTER, DEFPUSH_BUTTON, N_ ("&Chdir"), 0, 0, NULL, NULL },
 186     { B_AGAIN, NORMAL_BUTTON, N_ ("&Again"), 0, 0, NULL, NULL },
 187     { B_STOP, NORMAL_BUTTON, N_ ("S&uspend"), 0, 0, NULL, start_stop },
 188     { B_STOP, NORMAL_BUTTON, N_ ("Con&tinue"), 0, 0, NULL, NULL },
 189     { B_CANCEL, NORMAL_BUTTON, N_ ("&Quit"), 0, 0, NULL, NULL },
 190 
 191     { B_PANELIZE, NORMAL_BUTTON, N_ ("Pane&lize"), 0, 0, NULL, NULL },
 192     { B_VIEW, NORMAL_BUTTON, N_ ("&View - F3"), 0, 0, NULL, find_do_view_file },
 193     { B_VIEW, NORMAL_BUTTON, N_ ("&Edit - F4"), 0, 0, NULL, find_do_edit_file },
 194 };
 195 
 196 static const size_t fbuts_num = G_N_ELEMENTS (fbuts);
 197 static const size_t quit_button = 4;  // index of "Quit" button
 198 
 199 static WListbox *find_list;  // Listbox with the file list
 200 
 201 static find_file_options_t options = {
 202     TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL,
 203 };
 204 
 205 static char *in_start_dir = INPUT_LAST_TEXT;
 206 
 207 static mc_search_t *search_file_handle = NULL;
 208 static mc_search_t *search_content_handle = NULL;
 209 
 210 /* --------------------------------------------------------------------------------------------- */
 211 /*** file scope functions ************************************************************************/
 212 /* --------------------------------------------------------------------------------------------- */
 213 
 214 /* don't use max macro to avoid double str_term_width1() call in widget length calculation */
 215 #undef max
 216 
 217 static int
 218 max (int a, int b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 219 {
 220     return (a > b ? a : b);
 221 }
 222 
 223 /* --------------------------------------------------------------------------------------------- */
 224 
 225 static void
 226 parse_ignore_dirs (const char *ignore_dirs)
     /* [previous][next][first][last][top][bottom][index][help]  */
 227 {
 228     size_t r = 0, w = 0;  // read and write iterators
 229 
 230     if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0')
 231         return;
 232 
 233     find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
 234 
 235     /* Values like '/foo::/bar: produce holes in list.
 236      * Find and remove them */
 237     for (; find_ignore_dirs[r] != NULL; r++)
 238     {
 239         if (find_ignore_dirs[r][0] == '\0')
 240         {
 241             // empty entry -- skip it
 242             MC_PTR_FREE (find_ignore_dirs[r]);
 243             continue;
 244         }
 245 
 246         if (r != w)
 247         {
 248             // copy entry to the previous free array cell
 249             find_ignore_dirs[w] = find_ignore_dirs[r];
 250             find_ignore_dirs[r] = NULL;
 251         }
 252 
 253         canonicalize_pathname (find_ignore_dirs[w]);
 254         if (find_ignore_dirs[w][0] != '\0')
 255             w++;
 256         else
 257             MC_PTR_FREE (find_ignore_dirs[w]);
 258     }
 259 
 260     if (find_ignore_dirs[0] == NULL)
 261     {
 262         g_strfreev (find_ignore_dirs);
 263         find_ignore_dirs = NULL;
 264     }
 265 }
 266 
 267 /* --------------------------------------------------------------------------------------------- */
 268 
 269 static void
 270 find_load_options (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 271 {
 272     static gboolean loaded = FALSE;
 273 
 274     if (loaded)
 275         return;
 276 
 277     loaded = TRUE;
 278 
 279     options.file_case_sens =
 280         mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE);
 281     options.file_pattern =
 282         mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE);
 283     options.find_recurs =
 284         mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE);
 285     options.follow_symlinks =
 286         mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE);
 287     options.skip_hidden =
 288         mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE);
 289     options.file_all_charsets =
 290         mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE);
 291     options.content_case_sens =
 292         mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE);
 293     options.content_regexp =
 294         mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE);
 295     options.content_first_hit =
 296         mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE);
 297     options.content_whole_words =
 298         mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE);
 299     options.content_all_charsets =
 300         mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE);
 301     options.ignore_dirs_enable =
 302         mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE);
 303     options.ignore_dirs =
 304         mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", "");
 305 
 306     if (options.ignore_dirs[0] == '\0')
 307         MC_PTR_FREE (options.ignore_dirs);
 308 }
 309 
 310 /* --------------------------------------------------------------------------------------------- */
 311 
 312 static void
 313 find_save_options (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 314 {
 315     mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens",
 316                         options.file_case_sens);
 317     mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern",
 318                         options.file_pattern);
 319     mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs);
 320     mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks",
 321                         options.follow_symlinks);
 322     mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
 323     mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets",
 324                         options.file_all_charsets);
 325     mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens",
 326                         options.content_case_sens);
 327     mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp",
 328                         options.content_regexp);
 329     mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit",
 330                         options.content_first_hit);
 331     mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words",
 332                         options.content_whole_words);
 333     mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets",
 334                         options.content_all_charsets);
 335     mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable",
 336                         options.ignore_dirs_enable);
 337     mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs);
 338 }
 339 
 340 /* --------------------------------------------------------------------------------------------- */
 341 
 342 static inline char *
 343 add_to_list (const char *text, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 344 {
 345     return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
 346 }
 347 
 348 /* --------------------------------------------------------------------------------------------- */
 349 
 350 static inline char *
 351 add_to_list_take (char *text, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 352 {
 353     return listbox_add_item_take (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
 354 }
 355 
 356 /* --------------------------------------------------------------------------------------------- */
 357 
 358 static inline void
 359 stop_idle (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 360 {
 361     widget_idle (WIDGET (data), FALSE);
 362 }
 363 
 364 /* --------------------------------------------------------------------------------------------- */
 365 
 366 static inline void
 367 status_update (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 368 {
 369     label_set_text (status_label, text);
 370 }
 371 
 372 /* --------------------------------------------------------------------------------------------- */
 373 
 374 static inline void
 375 found_num_update (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 376 {
 377     label_set_textv (found_num_label, _ ("Found: %lu"), matches);
 378 }
 379 
 380 /* --------------------------------------------------------------------------------------------- */
 381 
 382 static void
 383 get_list_info (char **file, char **dir, gsize *start, gsize *end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 384 {
 385     find_match_location_t *location;
 386 
 387     listbox_get_current (find_list, file, (void **) &location);
 388     if (location != NULL)
 389     {
 390         if (dir != NULL)
 391             *dir = location->dir;
 392         if (start != NULL)
 393             *start = location->start;
 394         if (end != NULL)
 395             *end = location->end;
 396     }
 397     else
 398     {
 399         if (dir != NULL)
 400             *dir = NULL;
 401     }
 402 }
 403 
 404 /* --------------------------------------------------------------------------------------------- */
 405 /** check regular expression */
 406 
 407 static gboolean
 408 find_check_regexp (const char *r)
     /* [previous][next][first][last][top][bottom][index][help]  */
 409 {
 410     mc_search_t *search;
 411     gboolean regexp_ok = FALSE;
 412 
 413     search = mc_search_new (r, NULL);
 414 
 415     if (search != NULL)
 416     {
 417         search->search_type = MC_SEARCH_T_REGEX;
 418         regexp_ok = mc_search_prepare (search);
 419         mc_search_free (search);
 420     }
 421 
 422     return regexp_ok;
 423 }
 424 
 425 /* --------------------------------------------------------------------------------------------- */
 426 
 427 static void
 428 find_toggle_enable_ignore_dirs (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 429 {
 430     widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
 431 }
 432 
 433 /* --------------------------------------------------------------------------------------------- */
 434 
 435 static void
 436 find_toggle_enable_params (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 437 {
 438     gboolean disable = input_is_empty (in_name);
 439 
 440     widget_disable (WIDGET (file_pattern_cbox), disable);
 441     widget_disable (WIDGET (file_case_sens_cbox), disable);
 442     widget_disable (WIDGET (file_all_charsets_cbox), disable);
 443 }
 444 
 445 /* --------------------------------------------------------------------------------------------- */
 446 
 447 static void
 448 find_toggle_enable_content (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 449 {
 450     widget_disable (WIDGET (content_regexp_cbox), content_is_empty);
 451     widget_disable (WIDGET (content_case_sens_cbox), content_is_empty);
 452     widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty);
 453     widget_disable (WIDGET (content_whole_words_cbox), content_is_empty);
 454     widget_disable (WIDGET (content_first_hit_cbox), content_is_empty);
 455 }
 456 
 457 /* --------------------------------------------------------------------------------------------- */
 458 /**
 459  * Callback for the parameter dialog.
 460  * Validate regex, prevent closing the dialog if it's invalid.
 461  */
 462 
 463 static cb_ret_t
 464 find_parm_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 465 {
 466     /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies.
 467      * Use this time moment to check input field content. We can't do that in MSG_INIT
 468      * because history is not loaded yet.
 469      * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or
 470      * we could name it MSG_POST_INIT.
 471      *
 472      * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar
 473      * purpose. We should remember to fix those places too when we introduce the new
 474      * message.
 475      */
 476     static gboolean first_draw = TRUE;
 477 
 478     WDialog *h = DIALOG (w);
 479 
 480     switch (msg)
 481     {
 482     case MSG_INIT:
 483         group_default_callback (w, NULL, MSG_INIT, 0, NULL);
 484         first_draw = TRUE;
 485         return MSG_HANDLED;
 486 
 487     case MSG_NOTIFY:
 488         if (sender == WIDGET (ignore_dirs_cbox))
 489         {
 490             find_toggle_enable_ignore_dirs ();
 491             return MSG_HANDLED;
 492         }
 493 
 494         return MSG_NOT_HANDLED;
 495 
 496     case MSG_VALIDATE:
 497         if (h->ret_value != B_ENTER)
 498             return MSG_HANDLED;
 499 
 500         // check filename regexp
 501         if (!file_pattern_cbox->state && !input_is_empty (in_name)
 502             && !find_check_regexp (input_get_ctext (in_name)))
 503         {
 504             // Don't stop the dialog
 505             widget_set_state (w, WST_ACTIVE, TRUE);
 506             message (D_ERROR, MSG_ERROR, _ ("Malformed regular expression"));
 507             widget_select (WIDGET (in_name));
 508             return MSG_HANDLED;
 509         }
 510 
 511         // check content regexp
 512         if (content_regexp_cbox->state && !content_is_empty
 513             && !find_check_regexp (input_get_ctext (in_with)))
 514         {
 515             // Don't stop the dialog
 516             widget_set_state (w, WST_ACTIVE, TRUE);
 517             message (D_ERROR, MSG_ERROR, _ ("Malformed regular expression"));
 518             widget_select (WIDGET (in_with));
 519             return MSG_HANDLED;
 520         }
 521 
 522         return MSG_HANDLED;
 523 
 524     case MSG_POST_KEY:
 525         if (GROUP (h)->current->data == in_name)
 526             find_toggle_enable_params ();
 527         else if (GROUP (h)->current->data == in_with)
 528         {
 529             content_is_empty = input_is_empty (in_with);
 530             find_toggle_enable_content ();
 531         }
 532         return MSG_HANDLED;
 533 
 534     case MSG_DRAW:
 535         if (first_draw)
 536         {
 537             find_toggle_enable_ignore_dirs ();
 538             find_toggle_enable_params ();
 539             find_toggle_enable_content ();
 540         }
 541 
 542         first_draw = FALSE;
 543         MC_FALLTHROUGH;  // to call MSG_DRAW default handler
 544 
 545     default:
 546         return dlg_default_callback (w, sender, msg, parm, data);
 547     }
 548 }
 549 
 550 /* --------------------------------------------------------------------------------------------- */
 551 /**
 552  * find_parameters: gets information from the user
 553  *
 554  * If the return value is TRUE, then the following holds:
 555  *
 556  * start_dir, ignore_dirs, pattern and content contain the information provided by the user.
 557  * They are newly allocated strings and must be freed when unneeded.
 558  *
 559  * start_dir_len is -1 when user entered an absolute path, otherwise it is a length
 560  * of start_dir (which is absolute). It is used to get a relative pats of find results.
 561  */
 562 
 563 static gboolean
 564 find_parameters (WPanel *panel, char **start_dir, ssize_t *start_dir_len, char **ignore_dirs,
     /* [previous][next][first][last][top][bottom][index][help]  */
 565                  char **pattern, char **content)
 566 {
 567     WGroup *g;
 568 
 569     // Size of the find parameters window
 570     const int lines = 19;
 571     int cols = 68;
 572 
 573     gboolean return_value;
 574 
 575     // file name
 576     const char *file_name_label = _ ("File name:");
 577     const char *file_recurs_label = _ ("&Find recursively");
 578     const char *file_follow_symlinks = _ ("Follow s&ymlinks");
 579     const char *file_pattern_label = _ ("&Using shell patterns");
 580     const char *file_all_charsets_label = _ ("&All charsets");
 581     const char *file_case_label = _ ("Cas&e sensitive");
 582     const char *file_skip_hidden_label = _ ("S&kip hidden");
 583 
 584     // file content
 585     const char *content_content_label = _ ("Content:");
 586     const char *content_use_label = _ ("Sea&rch for content");
 587     const char *content_regexp_label = _ ("Re&gular expression");
 588     const char *content_case_label = _ ("Case sens&itive");
 589     const char *content_all_charsets_label = _ ("A&ll charsets");
 590     const char *content_whole_words_label = _ ("&Whole words");
 591     const char *content_first_hit_label = _ ("Fir&st hit");
 592 
 593     const char *buts[] = {
 594         _ ("&Tree"),
 595         _ ("&OK"),
 596         _ ("&Cancel"),
 597     };
 598 
 599     // button lengths
 600     int b0, b1, b2, b12;
 601     int y1, y2, x1, x2;
 602     // column width
 603     int cw;
 604 
 605     // calculate dialog width
 606 
 607     // widget widths
 608     cw = str_term_width1 (file_name_label);
 609     cw = max (cw, str_term_width1 (file_recurs_label) + 4);
 610     cw = max (cw, str_term_width1 (file_follow_symlinks) + 4);
 611     cw = max (cw, str_term_width1 (file_pattern_label) + 4);
 612     cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
 613     cw = max (cw, str_term_width1 (file_case_label) + 4);
 614     cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
 615 
 616     cw = max (cw, str_term_width1 (content_content_label) + 4);
 617     cw = max (cw, str_term_width1 (content_use_label) + 4);
 618     cw = max (cw, str_term_width1 (content_regexp_label) + 4);
 619     cw = max (cw, str_term_width1 (content_case_label) + 4);
 620     cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
 621     cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
 622     cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
 623 
 624     // button width
 625     b0 = str_term_width1 (buts[0]) + 3;
 626     b1 = str_term_width1 (buts[1]) + 5;  // default button
 627     b2 = str_term_width1 (buts[2]) + 3;
 628     b12 = b1 + b2 + 1;
 629 
 630     cols = max (cols, max (b12, cw * 2 + 1) + 6);
 631 
 632     find_load_options ();
 633 
 634     if (in_start_dir == NULL)
 635         in_start_dir = g_strdup (".");
 636 
 637     find_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
 638                            find_parm_callback, NULL, "[Find File]", _ ("Find File"));
 639     g = GROUP (find_dlg);
 640 
 641     x1 = 3;
 642     x2 = cols / 2 + 1;
 643     cw = (cols - 7) / 2;
 644     y1 = 2;
 645 
 646     group_add_widget (g, label_new (y1++, x1, _ ("Start at:")));
 647     in_start = input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
 648                           INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 649     group_add_widget (g, in_start);
 650 
 651     group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
 652 
 653     ignore_dirs_cbox =
 654         check_new (y1++, x1, options.ignore_dirs_enable, _ ("Ena&ble ignore directories:"));
 655     group_add_widget (g, ignore_dirs_cbox);
 656 
 657     in_ignore = input_new (y1++, x1, input_colors, cols - 6,
 658                            options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
 659                            INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 660     group_add_widget (g, in_ignore);
 661 
 662     group_add_widget (g, hline_new (y1++, -1, -1));
 663 
 664     y2 = y1;
 665 
 666     // Start 1st column
 667     group_add_widget (g, label_new (y1++, x1, file_name_label));
 668     in_name = input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
 669                          INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 670     group_add_widget (g, in_name);
 671 
 672     // Start 2nd column
 673     content_label = label_new (y2++, x2, content_content_label);
 674     group_add_widget (g, content_label);
 675     in_with = input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
 676                          MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
 677     in_with->label = content_label;
 678     group_add_widget (g, in_with);
 679 
 680     // Continue 1st column
 681     recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
 682     group_add_widget (g, recursively_cbox);
 683 
 684     follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks);
 685     group_add_widget (g, follow_sym_cbox);
 686 
 687     file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
 688     group_add_widget (g, file_pattern_cbox);
 689 
 690     file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
 691     group_add_widget (g, file_case_sens_cbox);
 692 
 693     file_all_charsets_cbox =
 694         check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
 695     group_add_widget (g, file_all_charsets_cbox);
 696 
 697     skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
 698     group_add_widget (g, skip_hidden_cbox);
 699 
 700     // Continue 2nd column
 701     content_whole_words_cbox =
 702         check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
 703     group_add_widget (g, content_whole_words_cbox);
 704 
 705     content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
 706     group_add_widget (g, content_regexp_cbox);
 707 
 708     content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
 709     group_add_widget (g, content_case_sens_cbox);
 710 
 711     content_all_charsets_cbox =
 712         check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
 713     group_add_widget (g, content_all_charsets_cbox);
 714 
 715     content_first_hit_cbox =
 716         check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
 717     group_add_widget (g, content_first_hit_cbox);
 718 
 719     // buttons
 720     y1 = max (y1, y2);
 721     x1 = (cols - b12) / 2;
 722     group_add_widget (g, hline_new (y1++, -1, -1));
 723     group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
 724     group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
 725 
 726 find_par_start:
 727     widget_select (WIDGET (in_name));
 728 
 729     switch (dlg_run (find_dlg))
 730     {
 731     case B_CANCEL:
 732         return_value = FALSE;
 733         break;
 734 
 735     case B_TREE:
 736     {
 737         const char *start_cstr;
 738         const char *temp_dir;
 739 
 740         start_cstr = input_get_ctext (in_start);
 741 
 742         if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr))
 743             temp_dir = vfs_path_as_str (panel->cwd_vpath);
 744         else
 745             temp_dir = start_cstr;
 746 
 747         if (in_start_dir != INPUT_LAST_TEXT)
 748             g_free (in_start_dir);
 749         in_start_dir = tree_box (temp_dir);
 750         if (in_start_dir == NULL)
 751             in_start_dir = g_strdup (temp_dir);
 752 
 753         input_assign_text (in_start, in_start_dir);
 754 
 755         // Warning: Dreadful goto
 756         goto find_par_start;
 757     }
 758 
 759     default:
 760     {
 761         char *s;
 762 
 763         options.content_all_charsets = content_all_charsets_cbox->state;
 764         options.content_case_sens = content_case_sens_cbox->state;
 765         options.content_regexp = content_regexp_cbox->state;
 766         options.content_first_hit = content_first_hit_cbox->state;
 767         options.content_whole_words = content_whole_words_cbox->state;
 768         options.find_recurs = recursively_cbox->state;
 769         options.follow_symlinks = follow_sym_cbox->state;
 770         options.file_pattern = file_pattern_cbox->state;
 771         options.file_case_sens = file_case_sens_cbox->state;
 772         options.file_all_charsets = file_all_charsets_cbox->state;
 773         options.skip_hidden = skip_hidden_cbox->state;
 774         options.ignore_dirs_enable = ignore_dirs_cbox->state;
 775         g_free (options.ignore_dirs);
 776         options.ignore_dirs = input_get_text (in_ignore);
 777 
 778         *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL;
 779         if (input_is_empty (in_name))
 780             *pattern = g_strdup (options.file_pattern ? "*" : ".*");
 781         else
 782             *pattern = input_get_text (in_name);
 783         *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : ".");
 784         if (in_start_dir != INPUT_LAST_TEXT)
 785             g_free (in_start_dir);
 786         in_start_dir = g_strdup (*start_dir);
 787 
 788         s = tilde_expand (*start_dir);
 789         canonicalize_pathname (s);
 790 
 791         if (DIR_IS_DOT (s))
 792         {
 793             *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath));
 794             // FIXME: is panel->cwd_vpath canonicalized?
 795             // relative paths will be used in panelization
 796             *start_dir_len = (ssize_t) strlen (*start_dir);
 797             g_free (s);
 798         }
 799         else if (g_path_is_absolute (s))
 800         {
 801             *start_dir = s;
 802             *start_dir_len = -1;
 803         }
 804         else
 805         {
 806             // relative paths will be used in panelization
 807             *start_dir = mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL);
 808             *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath);
 809             g_free (s);
 810         }
 811 
 812         if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
 813             || DIR_IS_DOT (input_get_ctext (in_ignore)))
 814             *ignore_dirs = NULL;
 815         else
 816             *ignore_dirs = input_get_text (in_ignore);
 817 
 818         find_save_options ();
 819 
 820         return_value = TRUE;
 821     }
 822     }
 823 
 824     widget_destroy (WIDGET (find_dlg));
 825 
 826     return return_value;
 827 }
 828 
 829 /* --------------------------------------------------------------------------------------------- */
 830 
 831 static inline void
 832 push_directory (vfs_path_t *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
 833 {
 834     g_queue_push_head (&dir_queue, (void *) dir);
 835 }
 836 
 837 /* --------------------------------------------------------------------------------------------- */
 838 
 839 static inline vfs_path_t *
 840 pop_directory (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 841 {
 842     return (vfs_path_t *) g_queue_pop_head (&dir_queue);
 843 }
 844 
 845 /* --------------------------------------------------------------------------------------------- */
 846 
 847 static void
 848 queue_dir_free (gpointer data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 849 {
 850     vfs_path_free ((vfs_path_t *) data, TRUE);
 851 }
 852 
 853 /* --------------------------------------------------------------------------------------------- */
 854 /** Remove all the items from the stack */
 855 
 856 static void
 857 clear_stack (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 858 {
 859     g_queue_clear_full (&dir_queue, queue_dir_free);
 860 }
 861 
 862 /* --------------------------------------------------------------------------------------------- */
 863 
 864 static void
 865 insert_file (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 866 {
 867     char *tmp_name;
 868     static char *dirname = NULL;
 869     find_match_location_t *location;
 870 
 871     while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
 872         dir++;
 873 
 874     if (old_dir != NULL)
 875     {
 876         if (strcmp (old_dir, dir) != 0)
 877         {
 878             g_free (old_dir);
 879             old_dir = g_strdup (dir);
 880             dirname = add_to_list (dir, NULL);
 881         }
 882     }
 883     else
 884     {
 885         old_dir = g_strdup (dir);
 886         dirname = add_to_list (dir, NULL);
 887     }
 888 
 889     tmp_name = g_strdup_printf ("    %s", file);
 890     location = g_malloc (sizeof (*location));
 891     location->dir = dirname;
 892     location->start = start;
 893     location->end = end;
 894     add_to_list_take (tmp_name, location);
 895 }
 896 
 897 /* --------------------------------------------------------------------------------------------- */
 898 
 899 static void
 900 find_add_match (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 901 {
 902     insert_file (dir, file, start, end);
 903 
 904     // Don't scroll
 905     if (matches == 0)
 906         listbox_select_first (find_list);
 907     widget_draw (WIDGET (find_list));
 908 
 909     matches++;
 910     found_num_update ();
 911 }
 912 
 913 /* --------------------------------------------------------------------------------------------- */
 914 
 915 static FindProgressStatus
 916 check_find_events (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 917 {
 918     Gpm_Event event;
 919     int c;
 920 
 921     event.x = -1;
 922     c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE);
 923     if (c != EV_NONE)
 924     {
 925         dlg_process_event (h, c, &event);
 926         if (h->ret_value == B_ENTER || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN
 927             || h->ret_value == B_PANELIZE)
 928         {
 929             // dialog terminated
 930             return FIND_ABORT;
 931         }
 932         if (!widget_get_state (WIDGET (h), WST_IDLE))
 933         {
 934             // searching suspended
 935             return FIND_SUSPEND;
 936         }
 937     }
 938 
 939     return FIND_CONT;
 940 }
 941 
 942 /* --------------------------------------------------------------------------------------------- */
 943 /**
 944  * search_content:
 945  *
 946  * Search the content_pattern string in the DIRECTORY/FILE.
 947  * It will add the found entries to the find listbox.
 948  *
 949  * returns FALSE if do_search should look for another file
 950  *         TRUE if do_search should exit and proceed to the event handler
 951  */
 952 
 953 static gboolean
 954 search_content (WDialog *h, const char *directory, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 955 {
 956     struct stat s;
 957     char buffer[BUF_4K] = "";  // raw input buffer
 958     int file_fd = -1;
 959     gboolean ret_val = FALSE;
 960     vfs_path_t *vpath;
 961     gint64 tv;
 962     gboolean status_updated = FALSE;
 963 
 964     vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
 965 
 966     if (mc_stat (vpath, &s) == 0 && S_ISREG (s.st_mode))
 967         file_fd = mc_open (vpath, O_RDONLY);
 968 
 969     vfs_path_free (vpath, TRUE);
 970 
 971     if (file_fd == -1)
 972         return FALSE;
 973 
 974     // get time elapsed from last refresh
 975     tv = g_get_monotonic_time ();
 976 
 977     if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL)
 978     {
 979         g_snprintf (buffer, sizeof (buffer), _ ("Grepping in %s"), filename);
 980         status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8));
 981         mc_refresh ();
 982         last_refresh = tv;
 983         status_updated = TRUE;
 984     }
 985 
 986     tty_enable_interrupt_key ();
 987     tty_got_interrupt ();
 988 
 989     {
 990         int line = 1;
 991         int pos = 0;
 992         int n_read = 0;
 993         off_t off = 0;  // file_fd's offset corresponding to strbuf[0]
 994         gboolean found = FALSE;
 995         char *strbuf = NULL;  // buffer for fetched string
 996         int strbuf_size = 0;
 997         int i = -1;  // compensate for a newline we'll add when we first enter the loop
 998 
 999         if (resuming)
1000         {
1001             // We've been previously suspended, start from the previous position
1002             resuming = FALSE;
1003             line = last_line;
1004             pos = last_pos;
1005             off = last_off;
1006             i = last_i;
1007         }
1008 
1009         while (!ret_val)
1010         {
1011             char ch = '\0';
1012             gsize found_len;
1013 
1014             off += i + 1;  // the previous line, plus a newline character
1015             i = 0;
1016 
1017             // read to buffer and get line from there
1018             while (TRUE)
1019             {
1020                 if (pos >= n_read)
1021                 {
1022                     pos = 0;
1023                     n_read = mc_read (file_fd, buffer, sizeof (buffer));
1024                     if (n_read <= 0)
1025                         break;
1026                 }
1027 
1028                 ch = buffer[pos++];
1029                 if (ch == '\0')
1030                 {
1031                     // skip possible leading zero(s)
1032                     if (i == 0)
1033                     {
1034                         off++;
1035                         continue;
1036                     }
1037                     break;
1038                 }
1039 
1040                 if (i >= strbuf_size - 1)
1041                 {
1042                     strbuf_size += 128;
1043                     strbuf = g_realloc (strbuf, strbuf_size);
1044                 }
1045 
1046                 // Strip newline
1047                 if (ch == '\n')
1048                     break;
1049 
1050                 strbuf[i++] = ch;
1051             }
1052 
1053             if (i == 0)
1054             {
1055                 if (ch == '\0')
1056                     break;
1057 
1058                 // if (ch == '\n'): do not search in empty strings
1059                 goto skip_search;
1060             }
1061 
1062             strbuf[i] = '\0';
1063 
1064             if (!found  // Search in binary line once
1065                 && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
1066             {
1067                 gsize found_start;
1068                 char result[BUF_MEDIUM];
1069 
1070                 if (!status_updated)
1071                 {
1072                     /* if we add results for a file, we have to ensure that
1073                        name of this file is shown in status bar */
1074                     g_snprintf (result, sizeof (result), _ ("Grepping in %s"), filename);
1075                     status_update (str_trunc (result, WIDGET (h)->rect.cols - 8));
1076                     mc_refresh ();
1077                     last_refresh = tv;
1078                     status_updated = TRUE;
1079                 }
1080 
1081                 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
1082                 found_start =
1083                     off + search_content_handle->normal_offset + 1;  // off by one: ticket 3280
1084                 find_add_match (directory, result, found_start, found_start + found_len);
1085                 found = TRUE;
1086             }
1087 
1088             if (found && options.content_first_hit)
1089                 break;
1090 
1091             if (ch == '\n')
1092             {
1093             skip_search:
1094                 found = FALSE;
1095                 line++;
1096             }
1097 
1098             if ((line & 0xff) == 0)
1099             {
1100                 FindProgressStatus res;
1101 
1102                 res = check_find_events (h);
1103                 switch (res)
1104                 {
1105                 case FIND_ABORT:
1106                     stop_idle (h);
1107                     ret_val = TRUE;
1108                     break;
1109                 case FIND_SUSPEND:
1110                     resuming = TRUE;
1111                     last_line = line;
1112                     last_pos = pos;
1113                     last_off = off;
1114                     last_i = i;
1115                     ret_val = TRUE;
1116                     break;
1117                 default:
1118                     break;
1119                 }
1120             }
1121         }
1122 
1123         g_free (strbuf);
1124     }
1125 
1126     tty_disable_interrupt_key ();
1127     mc_close (file_fd);
1128     return ret_val;
1129 }
1130 
1131 /* --------------------------------------------------------------------------------------------- */
1132 
1133 /**
1134   If dir is absolute, this means we're within dir and searching file here.
1135   If dir is relative, this means we're going to add dir to the directory stack.
1136 **/
1137 static gboolean
1138 find_ignore_dir_search (const char *dir, size_t len)
     /* [previous][next][first][last][top][bottom][index][help]  */
1139 {
1140     if (find_ignore_dirs != NULL)
1141     {
1142         const size_t dlen = len == (size_t) (-1) ? strlen (dir) : len;
1143         const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
1144 
1145         char **ignore_dir;
1146 
1147         for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
1148         {
1149             const size_t ilen = strlen (*ignore_dir);
1150             const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
1151 
1152             // ignore dir is too long -- skip it
1153             if (dlen < ilen)
1154                 continue;
1155 
1156             // handle absolute and relative paths
1157             switch (iabs | dabs)
1158             {
1159             case 0:  // both paths are relative
1160             case 3:  // both paths are absolute
1161                 // if ignore dir is not a path  of dir -- skip it
1162                 if (strncmp (dir, *ignore_dir, ilen) == 0)
1163                 {
1164                     /* be sure that ignore dir is not a part of dir like:
1165                        ignore dir is "h", dir is "home" */
1166                     if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
1167                         return TRUE;
1168                 }
1169                 break;
1170             case 1:  // dir is absolute, ignore_dir is relative
1171             {
1172                 char *d;
1173 
1174                 d = strstr (dir, *ignore_dir);
1175                 if (d != NULL && IS_PATH_SEP (d[-1]) && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1176                     return TRUE;
1177             }
1178             break;
1179             case 2:  // dir is relative, ignore_dir is absolute
1180                 // FIXME: skip this case
1181                 break;
1182             default:  // this cannot occurs
1183                 return FALSE;
1184             }
1185         }
1186     }
1187 
1188     return FALSE;
1189 }
1190 
1191 /* --------------------------------------------------------------------------------------------- */
1192 
1193 static void
1194 find_rotate_dash (const WDialog *h, gboolean show)
     /* [previous][next][first][last][top][bottom][index][help]  */
1195 {
1196     static size_t pos = 0;
1197     static const char rotating_dash[4] MC_NONSTRING = "|/-\\";
1198     const Widget *w = CONST_WIDGET (h);
1199     const int *colors;
1200 
1201     colors = widget_get_colors (w);
1202     tty_setcolor (colors[DLG_COLOR_NORMAL]);
1203     widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
1204     tty_print_char (show ? rotating_dash[pos] : ' ');
1205     pos = (pos + 1) % sizeof (rotating_dash);
1206     mc_refresh ();
1207 }
1208 
1209 /* --------------------------------------------------------------------------------------------- */
1210 
1211 static int
1212 do_search (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1213 {
1214     static struct vfs_dirent *dp = NULL;
1215     static DIR *dirp = NULL;
1216     static char *directory = NULL;
1217     static gboolean pop_start_dir = TRUE;
1218     struct stat tmp_stat;
1219     gsize bytes_found;
1220     unsigned short count;
1221 
1222     if (h == NULL)
1223     {  // someone forces me to close dirp
1224         if (dirp != NULL)
1225         {
1226             mc_closedir (dirp);
1227             dirp = NULL;
1228         }
1229         MC_PTR_FREE (directory);
1230         dp = NULL;
1231         pop_start_dir = TRUE;
1232         return 1;
1233     }
1234 
1235     for (count = 0; count < 32; count++)
1236     {
1237         while (dp == NULL)
1238         {
1239             if (dirp != NULL)
1240             {
1241                 mc_closedir (dirp);
1242                 dirp = NULL;
1243             }
1244 
1245             while (dirp == NULL)
1246             {
1247                 vfs_path_t *tmp_vpath = NULL;
1248 
1249                 tty_setcolor (REVERSE_COLOR);
1250 
1251                 while (TRUE)
1252                 {
1253                     tmp_vpath = pop_directory ();
1254                     if (tmp_vpath == NULL)
1255                     {
1256                         running = FALSE;
1257                         if (ignore_count == 0)
1258                             status_update (_ ("Finished"));
1259                         else
1260                         {
1261                             char msg[BUF_SMALL];
1262 
1263                             g_snprintf (msg, sizeof (msg),
1264                                         ngettext ("Finished (ignored %zu directory)",
1265                                                   "Finished (ignored %zu directories)",
1266                                                   ignore_count),
1267                                         ignore_count);
1268                             status_update (msg);
1269                         }
1270                         if (verbose)
1271                             find_rotate_dash (h, FALSE);
1272                         stop_idle (h);
1273                         return 0;
1274                     }
1275 
1276                     /* The start directory is the first one in the stack (see do_find() below).
1277                        Do not apply ignore_dir to it. */
1278                     if (pop_start_dir)
1279                     {
1280                         pop_start_dir = FALSE;
1281                         break;
1282                     }
1283 
1284                     pop_start_dir = FALSE;
1285 
1286                     // handle absolute ignore dirs here
1287                     if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath), -1))
1288                         break;
1289 
1290                     vfs_path_free (tmp_vpath, TRUE);
1291                     ignore_count++;
1292                 }
1293 
1294                 g_free (directory);
1295 
1296                 if (verbose)
1297                 {
1298                     char buffer[BUF_MEDIUM];
1299 
1300                     directory = (char *) vfs_path_as_str (tmp_vpath);
1301                     g_snprintf (buffer, sizeof (buffer), _ ("Searching %s"), directory);
1302                     status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
1303                 }
1304 
1305                 dirp = mc_opendir (tmp_vpath);
1306                 directory = vfs_path_free (tmp_vpath, FALSE);
1307             }  // while (!dirp)
1308 
1309             // skip invalid filenames
1310             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1311                 ;
1312         }  // while (!dp)
1313 
1314         if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1315         {
1316             // skip invalid filenames
1317             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1318                 ;
1319 
1320             return 1;
1321         }
1322 
1323         if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1324         {
1325             gboolean search_ok;
1326 
1327             if (options.find_recurs && (directory != NULL))
1328             {  // Can directory be NULL ?
1329                 // handle relative ignore dirs here
1330                 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name, dp->d_len))
1331                     ignore_count++;
1332                 else
1333                 {
1334                     vfs_path_t *tmp_vpath;
1335                     int stat_res;
1336                     gboolean descend = FALSE;
1337 
1338                     if (dp->d_type == DT_UNKNOWN || dp->d_type == DT_DIR
1339                         || (dp->d_type == DT_LNK && options.follow_symlinks))
1340                     {
1341                         tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1342 
1343                         if (dp->d_type == DT_DIR)
1344                             descend = TRUE;
1345                         else
1346                         {
1347                             if (options.follow_symlinks)
1348                                 stat_res = mc_stat (tmp_vpath, &tmp_stat);
1349                             else
1350                                 stat_res = mc_lstat (tmp_vpath, &tmp_stat);
1351 
1352                             if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
1353                                 descend = TRUE;
1354                         }
1355 
1356                         if (descend)
1357                             push_directory (tmp_vpath);
1358                         else
1359                             vfs_path_free (tmp_vpath, TRUE);
1360                     }
1361                 }
1362             }
1363 
1364             search_ok = mc_search_run (search_file_handle, dp->d_name, 0, dp->d_len, &bytes_found);
1365 
1366             if (search_ok)
1367             {
1368                 if (content_pattern == NULL)
1369                     find_add_match (directory, dp->d_name, 0, 0);
1370                 else if (search_content (h, directory, dp->d_name))
1371                     return 1;
1372             }
1373         }
1374 
1375         // skip invalid filenames
1376         while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1377             ;
1378     }  // for
1379 
1380     if (verbose)
1381         find_rotate_dash (h, TRUE);
1382 
1383     return 1;
1384 }
1385 
1386 /* --------------------------------------------------------------------------------------------- */
1387 
1388 static void
1389 init_find_vars (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1390 {
1391     MC_PTR_FREE (old_dir);
1392     matches = 0;
1393     ignore_count = 0;
1394 
1395     // Remove all the items from the stack
1396     clear_stack ();
1397 
1398     g_strfreev (find_ignore_dirs);
1399     find_ignore_dirs = NULL;
1400 }
1401 
1402 /* --------------------------------------------------------------------------------------------- */
1403 
1404 static void
1405 find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
     /* [previous][next][first][last][top][bottom][index][help]  */
1406                    off_t search_end)
1407 {
1408     const char *filename = NULL;
1409     int line;
1410     vfs_path_t *fullname_vpath;
1411 
1412     if (content_pattern != NULL)
1413     {
1414         filename = strchr (file + 4, ':') + 1;
1415         line = atoi (file + 4);
1416     }
1417     else
1418     {
1419         filename = file + 4;
1420         line = 0;
1421     }
1422 
1423     fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1424     if (edit)
1425         edit_file_at_line (fullname_vpath, use_internal_edit, line);
1426     else
1427         view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1428                            search_end);
1429     vfs_path_free (fullname_vpath, TRUE);
1430 }
1431 
1432 /* --------------------------------------------------------------------------------------------- */
1433 
1434 static cb_ret_t
1435 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
1436 {
1437     char *text = NULL;
1438     find_match_location_t *location;
1439 
1440     listbox_get_current (find_list, &text, (void **) &location);
1441 
1442     if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1443         return MSG_NOT_HANDLED;
1444 
1445     find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1446     return MSG_HANDLED;
1447 }
1448 
1449 /* --------------------------------------------------------------------------------------------- */
1450 
1451 static void
1452 find_calc_button_locations (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1453 {
1454     const int cols = CONST_WIDGET (h)->rect.cols;
1455 
1456     int l1, l2;
1457 
1458     l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1459     l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1460 
1461     fbuts[0].x = (cols - l1) / 2;
1462     fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1463     fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1464     fbuts[3].x = fbuts[2].x;
1465     fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1466 
1467     if (all_buttons)
1468     {
1469         fbuts[5].x = (cols - l2) / 2;
1470         fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1471         fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1472     }
1473 }
1474 
1475 /* --------------------------------------------------------------------------------------------- */
1476 
1477 static void
1478 find_adjust_header (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1479 {
1480     char title[BUF_MEDIUM];
1481     int title_len;
1482 
1483     if (content_pattern != NULL)
1484         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\". Content: \"%s\""), find_pattern,
1485                     content_pattern);
1486     else
1487         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\""), find_pattern);
1488 
1489     title_len = str_term_width1 (title);
1490     if (title_len > WIDGET (h)->rect.cols - 6)
1491     {
1492         // title is too wide, truncate it
1493         title_len = WIDGET (h)->rect.cols - 6;
1494         title_len = str_column_to_pos (title, title_len);
1495         title_len -= 3;  // reserve space for three dots
1496         title_len = str_offset_to_pos (title, title_len);
1497         // mark that title is truncated
1498         memmove (title + title_len, "...", 4);
1499     }
1500 
1501     frame_set_title (FRAME (h->bg), title);
1502 }
1503 
1504 /* --------------------------------------------------------------------------------------------- */
1505 
1506 static void
1507 find_relocate_buttons (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1508 {
1509     size_t i;
1510 
1511     find_calc_button_locations (h, all_buttons);
1512 
1513     for (i = 0; i < fbuts_num; i++)
1514         fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
1515 }
1516 
1517 /* --------------------------------------------------------------------------------------------- */
1518 
1519 static cb_ret_t
1520 find_resize (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1521 {
1522     Widget *w = WIDGET (h);
1523     WRect r = w->rect;
1524 
1525     r.lines = LINES - 4;
1526     r.cols = COLS - 16;
1527     dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
1528     find_adjust_header (h);
1529     find_relocate_buttons (h, TRUE);
1530 
1531     return MSG_HANDLED;
1532 }
1533 
1534 /* --------------------------------------------------------------------------------------------- */
1535 
1536 static cb_ret_t
1537 find_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1538 {
1539     WDialog *h = DIALOG (w);
1540 
1541     switch (msg)
1542     {
1543     case MSG_INIT:
1544         group_default_callback (w, NULL, MSG_INIT, 0, NULL);
1545         find_adjust_header (h);
1546         return MSG_HANDLED;
1547 
1548     case MSG_KEY:
1549         if (parm == KEY_F (3) || parm == KEY_F (13))
1550         {
1551             gboolean unparsed_view = (parm == KEY_F (13));
1552 
1553             return view_edit_currently_selected_file (unparsed_view, FALSE);
1554         }
1555         if (parm == KEY_F (4))
1556             return view_edit_currently_selected_file (FALSE, TRUE);
1557         return MSG_NOT_HANDLED;
1558 
1559     case MSG_RESIZE:
1560         return find_resize (h);
1561 
1562     case MSG_IDLE:
1563         do_search (h);
1564         return MSG_HANDLED;
1565 
1566     default:
1567         return dlg_default_callback (w, sender, msg, parm, data);
1568     }
1569 }
1570 
1571 /* --------------------------------------------------------------------------------------------- */
1572 /** Handles the Stop/Start button in the find window */
1573 
1574 static int
1575 start_stop (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1576 {
1577     Widget *w = WIDGET (button);
1578 
1579     (void) action;
1580 
1581     running = is_start;
1582     widget_idle (WIDGET (find_dlg), running);
1583     is_start = !is_start;
1584 
1585     status_update (is_start ? _ ("Stopped") : _ ("Searching"));
1586     button_set_text (button, fbuts[is_start ? 3 : 2].text);
1587 
1588     find_relocate_buttons (DIALOG (w->owner), FALSE);
1589     widget_draw (WIDGET (w->owner));
1590 
1591     return 0;
1592 }
1593 
1594 /* --------------------------------------------------------------------------------------------- */
1595 /** Handle view command, when invoked as a button */
1596 
1597 static int
1598 find_do_view_file (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1599 {
1600     (void) button;
1601     (void) action;
1602 
1603     view_edit_currently_selected_file (FALSE, FALSE);
1604     return 0;
1605 }
1606 
1607 /* --------------------------------------------------------------------------------------------- */
1608 /** Handle edit command, when invoked as a button */
1609 
1610 static int
1611 find_do_edit_file (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1612 {
1613     (void) button;
1614     (void) action;
1615 
1616     view_edit_currently_selected_file (FALSE, TRUE);
1617     return 0;
1618 }
1619 
1620 /* --------------------------------------------------------------------------------------------- */
1621 
1622 static void
1623 setup_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1624 {
1625     WGroup *g;
1626     size_t i;
1627     int lines, cols;
1628     int y;
1629 
1630     static gboolean i18n_flag = FALSE;
1631 
1632     if (!i18n_flag)
1633     {
1634         for (i = 0; i < fbuts_num; i++)
1635         {
1636 #ifdef ENABLE_NLS
1637             fbuts[i].text = _ (fbuts[i].text);
1638 #endif
1639             fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1640             if (fbuts[i].flags == DEFPUSH_BUTTON)
1641                 fbuts[i].len += 2;
1642         }
1643 
1644         i18n_flag = TRUE;
1645     }
1646 
1647     lines = LINES - 4;
1648     cols = COLS - 16;
1649 
1650     find_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
1651                            find_callback, NULL, "[Find File]", NULL);
1652     g = GROUP (find_dlg);
1653 
1654     find_calc_button_locations (find_dlg, TRUE);
1655 
1656     y = 2;
1657     find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1658     group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
1659     y += WIDGET (find_list)->rect.lines;
1660 
1661     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1662 
1663     found_num_label = label_new (y++, 4, NULL);
1664     group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1665 
1666     status_label = label_new (y++, 4, _ ("Searching"));
1667     group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
1668 
1669     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1670 
1671     for (i = 0; i < fbuts_num; i++)
1672     {
1673         if (i == 3)
1674             fbuts[3].button = fbuts[2].button;
1675         else
1676         {
1677             fbuts[i].button = WIDGET (button_new (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags,
1678                                                   fbuts[i].text, fbuts[i].callback));
1679             group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1680         }
1681 
1682         if (i == quit_button)
1683             y++;
1684     }
1685 
1686     widget_select (WIDGET (find_list));
1687 }
1688 
1689 /* --------------------------------------------------------------------------------------------- */
1690 
1691 static int
1692 run_process (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1693 {
1694     int ret;
1695 
1696     search_content_handle = mc_search_new (content_pattern, NULL);
1697     if (search_content_handle)
1698     {
1699         search_content_handle->search_type =
1700             options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1701         search_content_handle->is_case_sensitive = options.content_case_sens;
1702         search_content_handle->whole_words = options.content_whole_words;
1703         search_content_handle->is_all_charsets = options.content_all_charsets;
1704     }
1705     search_file_handle = mc_search_new (find_pattern, NULL);
1706     search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1707     search_file_handle->is_case_sensitive = options.file_case_sens;
1708     search_file_handle->is_all_charsets = options.file_all_charsets;
1709     search_file_handle->is_entire_line = options.file_pattern;
1710 
1711     resuming = FALSE;
1712 
1713     widget_idle (WIDGET (find_dlg), TRUE);
1714     ret = dlg_run (find_dlg);
1715 
1716     mc_search_free (search_file_handle);
1717     search_file_handle = NULL;
1718     mc_search_free (search_content_handle);
1719     search_content_handle = NULL;
1720 
1721     return ret;
1722 }
1723 
1724 /* --------------------------------------------------------------------------------------------- */
1725 
1726 static void
1727 kill_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1728 {
1729     Widget *w = WIDGET (find_dlg);
1730 
1731     widget_idle (w, FALSE);
1732     widget_destroy (w);
1733 }
1734 
1735 /* --------------------------------------------------------------------------------------------- */
1736 
1737 static int
1738 do_find (WPanel *panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
     /* [previous][next][first][last][top][bottom][index][help]  */
1739          char **dirname, char **filename)
1740 {
1741     int return_value;
1742     char *dir_tmp = NULL, *file_tmp = NULL;
1743 
1744     setup_gui ();
1745 
1746     init_find_vars ();
1747     parse_ignore_dirs (ignore_dirs);
1748     push_directory (vfs_path_from_str (start_dir));
1749 
1750     return_value = run_process ();
1751 
1752     // Clear variables
1753     init_find_vars ();
1754 
1755     get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1756 
1757     if (dir_tmp != NULL)
1758         *dirname = g_strdup (dir_tmp);
1759     if (file_tmp != NULL)
1760         *filename = g_strdup (file_tmp);
1761 
1762     if (return_value == B_PANELIZE && *filename != NULL)
1763     {
1764         struct stat st;
1765         GList *entry;
1766         dir_list *list = &panel->dir;
1767         char *name = NULL;
1768         gboolean ok = TRUE;
1769 
1770         panel_clean_dir (panel);
1771         dir_list_init (list);
1772 
1773         for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
1774              entry = g_list_next (entry))
1775         {
1776             const char *lc_filename;
1777             WLEntry *le = LENTRY (entry->data);
1778             find_match_location_t *location = le->data;
1779             char *p;
1780             gboolean link_to_dir, stale_link;
1781 
1782             if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1783                 continue;
1784 
1785             if (!content_is_empty)
1786                 lc_filename = strchr (le->text + 4, ':') + 1;
1787             else
1788                 lc_filename = le->text + 4;
1789 
1790             name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1791             // skip initial start dir
1792             p = name;
1793             if (start_dir_len > 0)
1794                 p += (size_t) start_dir_len;
1795             if (IS_PATH_SEP (*p))
1796                 p++;
1797 
1798             if (!handle_path (p, &st, &link_to_dir, &stale_link))
1799             {
1800                 g_free (name);
1801                 continue;
1802             }
1803 
1804             // don't add files more than once to the panel
1805             if (!content_is_empty && list->len != 0
1806                 && strcmp (list->list[list->len - 1].fname->str, p) == 0)
1807             {
1808                 g_free (name);
1809                 continue;
1810             }
1811 
1812             ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
1813 
1814             g_free (name);
1815 
1816             if ((list->len & 15) == 0)
1817                 rotate_dash (TRUE);
1818         }
1819 
1820         panel->is_panelized = TRUE;
1821         panel_panelize_absolutize_if_needed (panel);
1822         panel_panelize_save (panel);
1823     }
1824 
1825     kill_gui ();
1826     do_search (NULL);  // force do_search to release resources
1827     MC_PTR_FREE (old_dir);
1828     rotate_dash (FALSE);
1829 
1830     return return_value;
1831 }
1832 
1833 /* --------------------------------------------------------------------------------------------- */
1834 /*** public functions ****************************************************************************/
1835 /* --------------------------------------------------------------------------------------------- */
1836 
1837 void
1838 find_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1839 {
1840     char *start_dir = NULL, *ignore_dirs = NULL;
1841     ssize_t start_dir_len;
1842 
1843     find_pattern = NULL;
1844     content_pattern = NULL;
1845 
1846     while (find_parameters (panel, &start_dir, &start_dir_len, &ignore_dirs, &find_pattern,
1847                             &content_pattern))
1848     {
1849         char *filename = NULL, *dirname = NULL;
1850         int v = B_CANCEL;
1851 
1852         content_is_empty = content_pattern == NULL;
1853 
1854         if (find_pattern[0] != '\0')
1855         {
1856             last_refresh = 0;
1857 
1858             is_start = FALSE;
1859 
1860             if (!content_is_empty && !str_is_valid_string (content_pattern))
1861                 MC_PTR_FREE (content_pattern);
1862 
1863             v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1864         }
1865 
1866         g_free (start_dir);
1867         g_free (ignore_dirs);
1868         MC_PTR_FREE (find_pattern);
1869 
1870         if (v == B_ENTER)
1871         {
1872             if (dirname != NULL)
1873             {
1874                 vfs_path_t *dirname_vpath;
1875 
1876                 dirname_vpath = vfs_path_from_str (dirname);
1877                 panel_cd (panel, dirname_vpath, cd_exact);
1878                 vfs_path_free (dirname_vpath, TRUE);
1879 
1880                 if (filename != NULL)
1881                 {
1882                     size_t offset;
1883 
1884                     if (content_pattern == NULL)
1885                         offset = 4;
1886                     else
1887                         offset = strchr (filename + 4, ':') - filename + 1;
1888 
1889                     panel_set_current_by_name (panel, filename + offset);
1890                 }
1891             }
1892             else if (filename != NULL)
1893             {
1894                 vfs_path_t *filename_vpath;
1895 
1896                 filename_vpath = vfs_path_from_str (filename);
1897                 panel_cd (panel, filename_vpath, cd_exact);
1898                 vfs_path_free (filename_vpath, TRUE);
1899             }
1900         }
1901 
1902         MC_PTR_FREE (content_pattern);
1903         g_free (dirname);
1904         g_free (filename);
1905 
1906         if (v == B_ENTER || v == B_CANCEL)
1907             break;
1908 
1909         if (v == B_PANELIZE)
1910         {
1911             panel_re_sort (panel);
1912             panel_set_current_by_name (panel, NULL);
1913             break;
1914         }
1915     }
1916 }
1917 
1918 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */