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-2026
   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                 const char *d = strstr (dir, *ignore_dir);
1173                 if (d != NULL && IS_PATH_SEP (d[-1]) && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1174                     return TRUE;
1175             }
1176             break;
1177             case 2:  // dir is relative, ignore_dir is absolute
1178                 // FIXME: skip this case
1179                 break;
1180             default:  // this cannot occur
1181                 return FALSE;
1182             }
1183         }
1184     }
1185 
1186     return FALSE;
1187 }
1188 
1189 /* --------------------------------------------------------------------------------------------- */
1190 
1191 static void
1192 find_rotate_dash (const WDialog *h, gboolean show)
     /* [previous][next][first][last][top][bottom][index][help]  */
1193 {
1194     static size_t pos = 0;
1195     static const char rotating_dash[4] MC_NONSTRING = "|/-\\";
1196     const Widget *w = CONST_WIDGET (h);
1197     const int *colors;
1198 
1199     colors = widget_get_colors (w);
1200     tty_setcolor (colors[DLG_COLOR_NORMAL]);
1201     widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
1202     tty_print_char (show ? rotating_dash[pos] : ' ');
1203     pos = (pos + 1) % sizeof (rotating_dash);
1204     mc_refresh ();
1205 }
1206 
1207 /* --------------------------------------------------------------------------------------------- */
1208 
1209 static int
1210 do_search (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1211 {
1212     static struct vfs_dirent *dp = NULL;
1213     static DIR *dirp = NULL;
1214     static char *directory = NULL;
1215     static gboolean pop_start_dir = TRUE;
1216     struct stat tmp_stat;
1217     gsize bytes_found;
1218     unsigned short count;
1219 
1220     if (h == NULL)
1221     {  // someone forces me to close dirp
1222         if (dirp != NULL)
1223         {
1224             mc_closedir (dirp);
1225             dirp = NULL;
1226         }
1227         MC_PTR_FREE (directory);
1228         dp = NULL;
1229         pop_start_dir = TRUE;
1230         return 1;
1231     }
1232 
1233     for (count = 0; count < 32; count++)
1234     {
1235         while (dp == NULL)
1236         {
1237             if (dirp != NULL)
1238             {
1239                 mc_closedir (dirp);
1240                 dirp = NULL;
1241             }
1242 
1243             while (dirp == NULL)
1244             {
1245                 vfs_path_t *tmp_vpath = NULL;
1246 
1247                 tty_setcolor (CORE_REVERSE_COLOR);
1248 
1249                 while (TRUE)
1250                 {
1251                     tmp_vpath = pop_directory ();
1252                     if (tmp_vpath == NULL)
1253                     {
1254                         running = FALSE;
1255                         if (ignore_count == 0)
1256                             status_update (_ ("Finished"));
1257                         else
1258                         {
1259                             char msg[BUF_SMALL];
1260 
1261                             g_snprintf (msg, sizeof (msg),
1262                                         ngettext ("Finished (ignored %zu directory)",
1263                                                   "Finished (ignored %zu directories)",
1264                                                   ignore_count),
1265                                         ignore_count);
1266                             status_update (msg);
1267                         }
1268                         if (verbose)
1269                             find_rotate_dash (h, FALSE);
1270                         stop_idle (h);
1271                         return 0;
1272                     }
1273 
1274                     /* The start directory is the first one in the stack (see do_find() below).
1275                        Do not apply ignore_dir to it. */
1276                     if (pop_start_dir)
1277                     {
1278                         pop_start_dir = FALSE;
1279                         break;
1280                     }
1281 
1282                     pop_start_dir = FALSE;
1283 
1284                     // handle absolute ignore dirs here
1285                     if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath), -1))
1286                         break;
1287 
1288                     vfs_path_free (tmp_vpath, TRUE);
1289                     ignore_count++;
1290                 }
1291 
1292                 g_free (directory);
1293 
1294                 if (verbose)
1295                 {
1296                     char buffer[BUF_MEDIUM];
1297 
1298                     directory = (char *) vfs_path_as_str (tmp_vpath);
1299                     g_snprintf (buffer, sizeof (buffer), _ ("Searching %s"), directory);
1300                     status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
1301                 }
1302 
1303                 dirp = mc_opendir (tmp_vpath);
1304                 directory = vfs_path_free (tmp_vpath, FALSE);
1305             }  // while (!dirp)
1306 
1307             // skip invalid filenames
1308             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1309                 ;
1310         }  // while (!dp)
1311 
1312         if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1313         {
1314             // skip invalid filenames
1315             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1316                 ;
1317 
1318             return 1;
1319         }
1320 
1321         if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1322         {
1323             gboolean search_ok;
1324 
1325             if (options.find_recurs && (directory != NULL))
1326             {  // Can directory be NULL ?
1327                 // handle relative ignore dirs here
1328                 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name, dp->d_len))
1329                     ignore_count++;
1330                 else
1331                 {
1332                     vfs_path_t *tmp_vpath;
1333                     int stat_res;
1334                     gboolean descend = FALSE;
1335 
1336                     if (dp->d_type == DT_UNKNOWN || dp->d_type == DT_DIR
1337                         || (dp->d_type == DT_LNK && options.follow_symlinks))
1338                     {
1339                         tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1340 
1341                         if (dp->d_type == DT_DIR)
1342                             descend = TRUE;
1343                         else
1344                         {
1345                             if (options.follow_symlinks)
1346                                 stat_res = mc_stat (tmp_vpath, &tmp_stat);
1347                             else
1348                                 stat_res = mc_lstat (tmp_vpath, &tmp_stat);
1349 
1350                             if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
1351                                 descend = TRUE;
1352                         }
1353 
1354                         if (descend)
1355                             push_directory (tmp_vpath);
1356                         else
1357                             vfs_path_free (tmp_vpath, TRUE);
1358                     }
1359                 }
1360             }
1361 
1362             search_ok = mc_search_run (search_file_handle, dp->d_name, 0, dp->d_len, &bytes_found);
1363 
1364             if (search_ok)
1365             {
1366                 if (content_pattern == NULL)
1367                     find_add_match (directory, dp->d_name, 0, 0);
1368                 else if (search_content (h, directory, dp->d_name))
1369                     return 1;
1370             }
1371         }
1372 
1373         // skip invalid filenames
1374         while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1375             ;
1376     }  // for
1377 
1378     if (verbose)
1379         find_rotate_dash (h, TRUE);
1380 
1381     return 1;
1382 }
1383 
1384 /* --------------------------------------------------------------------------------------------- */
1385 
1386 static void
1387 init_find_vars (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1388 {
1389     MC_PTR_FREE (old_dir);
1390     matches = 0;
1391     ignore_count = 0;
1392 
1393     // Remove all the items from the stack
1394     clear_stack ();
1395 
1396     g_strfreev (find_ignore_dirs);
1397     find_ignore_dirs = NULL;
1398 }
1399 
1400 /* --------------------------------------------------------------------------------------------- */
1401 
1402 static void
1403 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]  */
1404                    off_t search_end)
1405 {
1406     const char *filename = NULL;
1407     int line;
1408     vfs_path_t *fullname_vpath;
1409 
1410     if (content_pattern != NULL)
1411     {
1412         filename = strchr (file + 4, ':') + 1;
1413         line = atoi (file + 4);
1414     }
1415     else
1416     {
1417         filename = file + 4;
1418         line = 0;
1419     }
1420 
1421     fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1422     if (edit)
1423         edit_file_at_line (fullname_vpath, use_internal_edit, line);
1424     else
1425         view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1426                            search_end);
1427     vfs_path_free (fullname_vpath, TRUE);
1428 }
1429 
1430 /* --------------------------------------------------------------------------------------------- */
1431 
1432 static cb_ret_t
1433 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
1434 {
1435     char *text = NULL;
1436     find_match_location_t *location;
1437 
1438     listbox_get_current (find_list, &text, (void **) &location);
1439 
1440     if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1441         return MSG_NOT_HANDLED;
1442 
1443     find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1444     return MSG_HANDLED;
1445 }
1446 
1447 /* --------------------------------------------------------------------------------------------- */
1448 
1449 static void
1450 find_calc_button_locations (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1451 {
1452     const int cols = CONST_WIDGET (h)->rect.cols;
1453 
1454     int l1, l2;
1455 
1456     l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1457     l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1458 
1459     fbuts[0].x = (cols - l1) / 2;
1460     fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1461     fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1462     fbuts[3].x = fbuts[2].x;
1463     fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1464 
1465     if (all_buttons)
1466     {
1467         fbuts[5].x = (cols - l2) / 2;
1468         fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1469         fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1470     }
1471 }
1472 
1473 /* --------------------------------------------------------------------------------------------- */
1474 
1475 static void
1476 find_adjust_header (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1477 {
1478     char title[BUF_MEDIUM];
1479     int title_len;
1480 
1481     if (content_pattern != NULL)
1482         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\". Content: \"%s\""), find_pattern,
1483                     content_pattern);
1484     else
1485         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\""), find_pattern);
1486 
1487     title_len = str_term_width1 (title);
1488     if (title_len > WIDGET (h)->rect.cols - 6)
1489     {
1490         // title is too wide, truncate it
1491         title_len = WIDGET (h)->rect.cols - 6;
1492         title_len = str_column_to_pos (title, title_len);
1493         title_len -= 3;  // reserve space for three dots
1494         title_len = str_offset_to_pos (title, title_len);
1495         // mark that title is truncated
1496         memmove (title + title_len, "...", 4);
1497     }
1498 
1499     frame_set_title (FRAME (h->bg), title);
1500 }
1501 
1502 /* --------------------------------------------------------------------------------------------- */
1503 
1504 static void
1505 find_relocate_buttons (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1506 {
1507     size_t i;
1508 
1509     find_calc_button_locations (h, all_buttons);
1510 
1511     for (i = 0; i < fbuts_num; i++)
1512         fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
1513 }
1514 
1515 /* --------------------------------------------------------------------------------------------- */
1516 
1517 static cb_ret_t
1518 find_resize (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1519 {
1520     Widget *w = WIDGET (h);
1521     WRect r = w->rect;
1522 
1523     r.lines = LINES - 4;
1524     r.cols = COLS - 16;
1525     dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
1526     find_adjust_header (h);
1527     find_relocate_buttons (h, TRUE);
1528 
1529     return MSG_HANDLED;
1530 }
1531 
1532 /* --------------------------------------------------------------------------------------------- */
1533 
1534 static cb_ret_t
1535 find_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1536 {
1537     WDialog *h = DIALOG (w);
1538 
1539     switch (msg)
1540     {
1541     case MSG_INIT:
1542         group_default_callback (w, NULL, MSG_INIT, 0, NULL);
1543         find_adjust_header (h);
1544         return MSG_HANDLED;
1545 
1546     case MSG_KEY:
1547         if (parm == KEY_F (3) || parm == KEY_F (13))
1548         {
1549             gboolean unparsed_view = (parm == KEY_F (13));
1550 
1551             return view_edit_currently_selected_file (unparsed_view, FALSE);
1552         }
1553         if (parm == KEY_F (4))
1554             return view_edit_currently_selected_file (FALSE, TRUE);
1555         return MSG_NOT_HANDLED;
1556 
1557     case MSG_RESIZE:
1558         return find_resize (h);
1559 
1560     case MSG_IDLE:
1561         do_search (h);
1562         return MSG_HANDLED;
1563 
1564     default:
1565         return dlg_default_callback (w, sender, msg, parm, data);
1566     }
1567 }
1568 
1569 /* --------------------------------------------------------------------------------------------- */
1570 /** Handles the Stop/Start button in the find window */
1571 
1572 static int
1573 start_stop (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1574 {
1575     Widget *w = WIDGET (button);
1576 
1577     (void) action;
1578 
1579     running = is_start;
1580     widget_idle (WIDGET (find_dlg), running);
1581     is_start = !is_start;
1582 
1583     status_update (is_start ? _ ("Stopped") : _ ("Searching"));
1584     button_set_text (button, fbuts[is_start ? 3 : 2].text);
1585 
1586     find_relocate_buttons (DIALOG (w->owner), FALSE);
1587     widget_draw (WIDGET (w->owner));
1588 
1589     return 0;
1590 }
1591 
1592 /* --------------------------------------------------------------------------------------------- */
1593 /** Handle view command, when invoked as a button */
1594 
1595 static int
1596 find_do_view_file (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1597 {
1598     (void) button;
1599     (void) action;
1600 
1601     view_edit_currently_selected_file (FALSE, FALSE);
1602     return 0;
1603 }
1604 
1605 /* --------------------------------------------------------------------------------------------- */
1606 /** Handle edit command, when invoked as a button */
1607 
1608 static int
1609 find_do_edit_file (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1610 {
1611     (void) button;
1612     (void) action;
1613 
1614     view_edit_currently_selected_file (FALSE, TRUE);
1615     return 0;
1616 }
1617 
1618 /* --------------------------------------------------------------------------------------------- */
1619 
1620 static void
1621 setup_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1622 {
1623     WGroup *g;
1624     size_t i;
1625     int lines, cols;
1626     int y;
1627 
1628     static gboolean i18n_flag = FALSE;
1629 
1630     if (!i18n_flag)
1631     {
1632         for (i = 0; i < fbuts_num; i++)
1633         {
1634 #ifdef ENABLE_NLS
1635             fbuts[i].text = _ (fbuts[i].text);
1636 #endif
1637             fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1638             if (fbuts[i].flags == DEFPUSH_BUTTON)
1639                 fbuts[i].len += 2;
1640         }
1641 
1642         i18n_flag = TRUE;
1643     }
1644 
1645     lines = LINES - 4;
1646     cols = COLS - 16;
1647 
1648     find_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
1649                            find_callback, NULL, "[Find File]", NULL);
1650     g = GROUP (find_dlg);
1651 
1652     find_calc_button_locations (find_dlg, TRUE);
1653 
1654     y = 2;
1655     find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1656     group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
1657     y += WIDGET (find_list)->rect.lines;
1658 
1659     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1660 
1661     found_num_label = label_new (y++, 4, NULL);
1662     group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1663 
1664     status_label = label_new (y++, 4, _ ("Searching"));
1665     group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
1666 
1667     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1668 
1669     for (i = 0; i < fbuts_num; i++)
1670     {
1671         if (i == 3)
1672             fbuts[3].button = fbuts[2].button;
1673         else
1674         {
1675             fbuts[i].button = WIDGET (button_new (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags,
1676                                                   fbuts[i].text, fbuts[i].callback));
1677             group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1678         }
1679 
1680         if (i == quit_button)
1681             y++;
1682     }
1683 
1684     widget_select (WIDGET (find_list));
1685 }
1686 
1687 /* --------------------------------------------------------------------------------------------- */
1688 
1689 static int
1690 run_process (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1691 {
1692     int ret;
1693 
1694     search_content_handle = mc_search_new (content_pattern, NULL);
1695     if (search_content_handle)
1696     {
1697         search_content_handle->search_type =
1698             options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1699         search_content_handle->is_case_sensitive = options.content_case_sens;
1700         search_content_handle->whole_words = options.content_whole_words;
1701         search_content_handle->is_all_charsets = options.content_all_charsets;
1702     }
1703     search_file_handle = mc_search_new (find_pattern, NULL);
1704     search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1705     search_file_handle->is_case_sensitive = options.file_case_sens;
1706     search_file_handle->is_all_charsets = options.file_all_charsets;
1707     search_file_handle->is_entire_line = options.file_pattern;
1708 
1709     resuming = FALSE;
1710 
1711     widget_idle (WIDGET (find_dlg), TRUE);
1712     ret = dlg_run (find_dlg);
1713 
1714     mc_search_free (search_file_handle);
1715     search_file_handle = NULL;
1716     mc_search_free (search_content_handle);
1717     search_content_handle = NULL;
1718 
1719     return ret;
1720 }
1721 
1722 /* --------------------------------------------------------------------------------------------- */
1723 
1724 static void
1725 kill_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1726 {
1727     Widget *w = WIDGET (find_dlg);
1728 
1729     widget_idle (w, FALSE);
1730     widget_destroy (w);
1731 }
1732 
1733 /* --------------------------------------------------------------------------------------------- */
1734 
1735 static int
1736 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]  */
1737          char **dirname, char **filename)
1738 {
1739     int return_value;
1740     char *dir_tmp = NULL, *file_tmp = NULL;
1741 
1742     setup_gui ();
1743 
1744     init_find_vars ();
1745     parse_ignore_dirs (ignore_dirs);
1746     push_directory (vfs_path_from_str (start_dir));
1747 
1748     return_value = run_process ();
1749 
1750     // Clear variables
1751     init_find_vars ();
1752 
1753     get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1754 
1755     if (dir_tmp != NULL)
1756         *dirname = g_strdup (dir_tmp);
1757     if (file_tmp != NULL)
1758         *filename = g_strdup (file_tmp);
1759 
1760     if (return_value == B_PANELIZE && *filename != NULL)
1761     {
1762         struct stat st;
1763         GList *entry;
1764         dir_list *list = &panel->dir;
1765         char *name = NULL;
1766         gboolean ok = TRUE;
1767 
1768         panel_clean_dir (panel);
1769         dir_list_init (list);
1770 
1771         for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
1772              entry = g_list_next (entry))
1773         {
1774             const char *lc_filename;
1775             WLEntry *le = LENTRY (entry->data);
1776             find_match_location_t *location = le->data;
1777             char *p;
1778             gboolean link_to_dir, stale_link;
1779 
1780             if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1781                 continue;
1782 
1783             if (!content_is_empty)
1784                 lc_filename = strchr (le->text + 4, ':') + 1;
1785             else
1786                 lc_filename = le->text + 4;
1787 
1788             name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1789             // skip initial start dir
1790             p = name;
1791             if (start_dir_len > 0)
1792                 p += (size_t) start_dir_len;
1793             if (IS_PATH_SEP (*p))
1794                 p++;
1795 
1796             if (!handle_path (p, &st, &link_to_dir, &stale_link))
1797             {
1798                 g_free (name);
1799                 continue;
1800             }
1801 
1802             // don't add files more than once to the panel
1803             if (!content_is_empty && list->len != 0
1804                 && strcmp (list->list[list->len - 1].fname->str, p) == 0)
1805             {
1806                 g_free (name);
1807                 continue;
1808             }
1809 
1810             ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
1811 
1812             g_free (name);
1813 
1814             if ((list->len & 15) == 0)
1815                 rotate_dash (TRUE);
1816         }
1817 
1818         panel->is_panelized = TRUE;
1819         panel_panelize_absolutize_if_needed (panel);
1820         panel_panelize_save (panel);
1821     }
1822 
1823     kill_gui ();
1824     do_search (NULL);  // force do_search to release resources
1825     MC_PTR_FREE (old_dir);
1826     rotate_dash (FALSE);
1827 
1828     return return_value;
1829 }
1830 
1831 /* --------------------------------------------------------------------------------------------- */
1832 /*** public functions ****************************************************************************/
1833 /* --------------------------------------------------------------------------------------------- */
1834 
1835 void
1836 find_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1837 {
1838     char *start_dir = NULL, *ignore_dirs = NULL;
1839     ssize_t start_dir_len;
1840 
1841     find_pattern = NULL;
1842     content_pattern = NULL;
1843 
1844     while (find_parameters (panel, &start_dir, &start_dir_len, &ignore_dirs, &find_pattern,
1845                             &content_pattern))
1846     {
1847         char *filename = NULL, *dirname = NULL;
1848         int v = B_CANCEL;
1849 
1850         content_is_empty = content_pattern == NULL;
1851 
1852         if (find_pattern[0] != '\0')
1853         {
1854             last_refresh = 0;
1855 
1856             is_start = FALSE;
1857 
1858             if (!content_is_empty && !str_is_valid_string (content_pattern))
1859                 MC_PTR_FREE (content_pattern);
1860 
1861             v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1862         }
1863 
1864         g_free (start_dir);
1865         g_free (ignore_dirs);
1866         MC_PTR_FREE (find_pattern);
1867 
1868         if (v == B_ENTER)
1869         {
1870             if (dirname != NULL)
1871             {
1872                 vfs_path_t *dirname_vpath;
1873 
1874                 dirname_vpath = vfs_path_from_str (dirname);
1875                 panel_cd (panel, dirname_vpath, cd_exact);
1876                 vfs_path_free (dirname_vpath, TRUE);
1877 
1878                 if (filename != NULL)
1879                 {
1880                     size_t offset;
1881 
1882                     if (content_pattern == NULL)
1883                         offset = 4;
1884                     else
1885                         offset = strchr (filename + 4, ':') - filename + 1;
1886 
1887                     panel_set_current_by_name (panel, filename + offset);
1888                 }
1889             }
1890             else if (filename != NULL)
1891             {
1892                 vfs_path_t *filename_vpath;
1893 
1894                 filename_vpath = vfs_path_from_str (filename);
1895                 panel_cd (panel, filename_vpath, cd_exact);
1896                 vfs_path_free (filename_vpath, TRUE);
1897             }
1898         }
1899 
1900         MC_PTR_FREE (content_pattern);
1901         g_free (dirname);
1902         g_free (filename);
1903 
1904         if (v == B_ENTER || v == B_CANCEL)
1905             break;
1906 
1907         if (v == B_PANELIZE)
1908         {
1909             panel_re_sort (panel);
1910             panel_set_current_by_name (panel, NULL);
1911             break;
1912         }
1913     }
1914 }
1915 
1916 /* --------------------------------------------------------------------------------------------- */

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