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 = N_ ("File name:");
 577     const char *file_recurs_label = N_ ("&Find recursively");
 578     const char *file_follow_symlinks = N_ ("Follow s&ymlinks");
 579     const char *file_pattern_label = N_ ("&Using shell patterns");
 580     const char *file_all_charsets_label = N_ ("&All charsets");
 581     const char *file_case_label = N_ ("Cas&e sensitive");
 582     const char *file_skip_hidden_label = N_ ("S&kip hidden");
 583 
 584     // file content
 585     const char *content_content_label = N_ ("Content:");
 586     const char *content_use_label = N_ ("Sea&rch for content");
 587     const char *content_regexp_label = N_ ("Re&gular expression");
 588     const char *content_case_label = N_ ("Case sens&itive");
 589     const char *content_all_charsets_label = N_ ("A&ll charsets");
 590     const char *content_whole_words_label = N_ ("&Whole words");
 591     const char *content_first_hit_label = N_ ("Fir&st hit");
 592 
 593     const char *buts[] = {
 594         N_ ("&Tree"),
 595         N_ ("&OK"),
 596         N_ ("&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 #ifdef ENABLE_NLS
 606     {
 607         size_t i;
 608 
 609         file_name_label = _ (file_name_label);
 610         file_recurs_label = _ (file_recurs_label);
 611         file_follow_symlinks = _ (file_follow_symlinks);
 612         file_pattern_label = _ (file_pattern_label);
 613         file_all_charsets_label = _ (file_all_charsets_label);
 614         file_case_label = _ (file_case_label);
 615         file_skip_hidden_label = _ (file_skip_hidden_label);
 616 
 617         // file content
 618         content_content_label = _ (content_content_label);
 619         content_use_label = _ (content_use_label);
 620         content_regexp_label = _ (content_regexp_label);
 621         content_case_label = _ (content_case_label);
 622         content_all_charsets_label = _ (content_all_charsets_label);
 623         content_whole_words_label = _ (content_whole_words_label);
 624         content_first_hit_label = _ (content_first_hit_label);
 625 
 626         for (i = 0; i < G_N_ELEMENTS (buts); i++)
 627             buts[i] = _ (buts[i]);
 628     }
 629 #endif
 630 
 631     // calculate dialog width
 632 
 633     // widget widths
 634     cw = str_term_width1 (file_name_label);
 635     cw = max (cw, str_term_width1 (file_recurs_label) + 4);
 636     cw = max (cw, str_term_width1 (file_follow_symlinks) + 4);
 637     cw = max (cw, str_term_width1 (file_pattern_label) + 4);
 638     cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
 639     cw = max (cw, str_term_width1 (file_case_label) + 4);
 640     cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
 641 
 642     cw = max (cw, str_term_width1 (content_content_label) + 4);
 643     cw = max (cw, str_term_width1 (content_use_label) + 4);
 644     cw = max (cw, str_term_width1 (content_regexp_label) + 4);
 645     cw = max (cw, str_term_width1 (content_case_label) + 4);
 646     cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
 647     cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
 648     cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
 649 
 650     // button width
 651     b0 = str_term_width1 (buts[0]) + 3;
 652     b1 = str_term_width1 (buts[1]) + 5;  // default button
 653     b2 = str_term_width1 (buts[2]) + 3;
 654     b12 = b1 + b2 + 1;
 655 
 656     cols = max (cols, max (b12, cw * 2 + 1) + 6);
 657 
 658     find_load_options ();
 659 
 660     if (in_start_dir == NULL)
 661         in_start_dir = g_strdup (".");
 662 
 663     find_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
 664                            find_parm_callback, NULL, "[Find File]", _ ("Find File"));
 665     g = GROUP (find_dlg);
 666 
 667     x1 = 3;
 668     x2 = cols / 2 + 1;
 669     cw = (cols - 7) / 2;
 670     y1 = 2;
 671 
 672     group_add_widget (g, label_new (y1++, x1, _ ("Start at:")));
 673     in_start = input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
 674                           INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 675     group_add_widget (g, in_start);
 676 
 677     group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
 678 
 679     ignore_dirs_cbox =
 680         check_new (y1++, x1, options.ignore_dirs_enable, _ ("Ena&ble ignore directories:"));
 681     group_add_widget (g, ignore_dirs_cbox);
 682 
 683     in_ignore = input_new (y1++, x1, input_colors, cols - 6,
 684                            options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
 685                            INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
 686     group_add_widget (g, in_ignore);
 687 
 688     group_add_widget (g, hline_new (y1++, -1, -1));
 689 
 690     y2 = y1;
 691 
 692     // Start 1st column
 693     group_add_widget (g, label_new (y1++, x1, file_name_label));
 694     in_name = input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
 695                          INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 696     group_add_widget (g, in_name);
 697 
 698     // Start 2nd column
 699     content_label = label_new (y2++, x2, content_content_label);
 700     group_add_widget (g, content_label);
 701     in_with = input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
 702                          MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
 703     in_with->label = content_label;
 704     group_add_widget (g, in_with);
 705 
 706     // Continue 1st column
 707     recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
 708     group_add_widget (g, recursively_cbox);
 709 
 710     follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks);
 711     group_add_widget (g, follow_sym_cbox);
 712 
 713     file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
 714     group_add_widget (g, file_pattern_cbox);
 715 
 716     file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
 717     group_add_widget (g, file_case_sens_cbox);
 718 
 719     file_all_charsets_cbox =
 720         check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
 721     group_add_widget (g, file_all_charsets_cbox);
 722 
 723     skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
 724     group_add_widget (g, skip_hidden_cbox);
 725 
 726     // Continue 2nd column
 727     content_whole_words_cbox =
 728         check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
 729     group_add_widget (g, content_whole_words_cbox);
 730 
 731     content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
 732     group_add_widget (g, content_regexp_cbox);
 733 
 734     content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
 735     group_add_widget (g, content_case_sens_cbox);
 736 
 737     content_all_charsets_cbox =
 738         check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
 739     group_add_widget (g, content_all_charsets_cbox);
 740 
 741     content_first_hit_cbox =
 742         check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
 743     group_add_widget (g, content_first_hit_cbox);
 744 
 745     // buttons
 746     y1 = max (y1, y2);
 747     x1 = (cols - b12) / 2;
 748     group_add_widget (g, hline_new (y1++, -1, -1));
 749     group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
 750     group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
 751 
 752 find_par_start:
 753     widget_select (WIDGET (in_name));
 754 
 755     switch (dlg_run (find_dlg))
 756     {
 757     case B_CANCEL:
 758         return_value = FALSE;
 759         break;
 760 
 761     case B_TREE:
 762     {
 763         const char *start_cstr;
 764         const char *temp_dir;
 765 
 766         start_cstr = input_get_ctext (in_start);
 767 
 768         if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr))
 769             temp_dir = vfs_path_as_str (panel->cwd_vpath);
 770         else
 771             temp_dir = start_cstr;
 772 
 773         if (in_start_dir != INPUT_LAST_TEXT)
 774             g_free (in_start_dir);
 775         in_start_dir = tree_box (temp_dir);
 776         if (in_start_dir == NULL)
 777             in_start_dir = g_strdup (temp_dir);
 778 
 779         input_assign_text (in_start, in_start_dir);
 780 
 781         // Warning: Dreadful goto
 782         goto find_par_start;
 783     }
 784 
 785     default:
 786     {
 787         char *s;
 788 
 789         options.content_all_charsets = content_all_charsets_cbox->state;
 790         options.content_case_sens = content_case_sens_cbox->state;
 791         options.content_regexp = content_regexp_cbox->state;
 792         options.content_first_hit = content_first_hit_cbox->state;
 793         options.content_whole_words = content_whole_words_cbox->state;
 794         options.find_recurs = recursively_cbox->state;
 795         options.follow_symlinks = follow_sym_cbox->state;
 796         options.file_pattern = file_pattern_cbox->state;
 797         options.file_case_sens = file_case_sens_cbox->state;
 798         options.file_all_charsets = file_all_charsets_cbox->state;
 799         options.skip_hidden = skip_hidden_cbox->state;
 800         options.ignore_dirs_enable = ignore_dirs_cbox->state;
 801         g_free (options.ignore_dirs);
 802         options.ignore_dirs = input_get_text (in_ignore);
 803 
 804         *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL;
 805         if (input_is_empty (in_name))
 806             *pattern = g_strdup (options.file_pattern ? "*" : ".*");
 807         else
 808             *pattern = input_get_text (in_name);
 809         *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : ".");
 810         if (in_start_dir != INPUT_LAST_TEXT)
 811             g_free (in_start_dir);
 812         in_start_dir = g_strdup (*start_dir);
 813 
 814         s = tilde_expand (*start_dir);
 815         canonicalize_pathname (s);
 816 
 817         if (DIR_IS_DOT (s))
 818         {
 819             *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath));
 820             // FIXME: is panel->cwd_vpath canonicalized?
 821             // relative paths will be used in panelization
 822             *start_dir_len = (ssize_t) strlen (*start_dir);
 823             g_free (s);
 824         }
 825         else if (g_path_is_absolute (s))
 826         {
 827             *start_dir = s;
 828             *start_dir_len = -1;
 829         }
 830         else
 831         {
 832             // relative paths will be used in panelization
 833             *start_dir = mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL);
 834             *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath);
 835             g_free (s);
 836         }
 837 
 838         if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
 839             || DIR_IS_DOT (input_get_ctext (in_ignore)))
 840             *ignore_dirs = NULL;
 841         else
 842             *ignore_dirs = input_get_text (in_ignore);
 843 
 844         find_save_options ();
 845 
 846         return_value = TRUE;
 847     }
 848     }
 849 
 850     widget_destroy (WIDGET (find_dlg));
 851 
 852     return return_value;
 853 }
 854 
 855 /* --------------------------------------------------------------------------------------------- */
 856 
 857 static inline void
 858 push_directory (vfs_path_t *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
 859 {
 860     g_queue_push_head (&dir_queue, (void *) dir);
 861 }
 862 
 863 /* --------------------------------------------------------------------------------------------- */
 864 
 865 static inline vfs_path_t *
 866 pop_directory (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 867 {
 868     return (vfs_path_t *) g_queue_pop_head (&dir_queue);
 869 }
 870 
 871 /* --------------------------------------------------------------------------------------------- */
 872 
 873 static void
 874 queue_dir_free (gpointer data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 875 {
 876     vfs_path_free ((vfs_path_t *) data, TRUE);
 877 }
 878 
 879 /* --------------------------------------------------------------------------------------------- */
 880 /** Remove all the items from the stack */
 881 
 882 static void
 883 clear_stack (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 884 {
 885     g_queue_clear_full (&dir_queue, queue_dir_free);
 886 }
 887 
 888 /* --------------------------------------------------------------------------------------------- */
 889 
 890 static void
 891 insert_file (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 892 {
 893     char *tmp_name;
 894     static char *dirname = NULL;
 895     find_match_location_t *location;
 896 
 897     while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
 898         dir++;
 899 
 900     if (old_dir != NULL)
 901     {
 902         if (strcmp (old_dir, dir) != 0)
 903         {
 904             g_free (old_dir);
 905             old_dir = g_strdup (dir);
 906             dirname = add_to_list (dir, NULL);
 907         }
 908     }
 909     else
 910     {
 911         old_dir = g_strdup (dir);
 912         dirname = add_to_list (dir, NULL);
 913     }
 914 
 915     tmp_name = g_strdup_printf ("    %s", file);
 916     location = g_malloc (sizeof (*location));
 917     location->dir = dirname;
 918     location->start = start;
 919     location->end = end;
 920     add_to_list_take (tmp_name, location);
 921 }
 922 
 923 /* --------------------------------------------------------------------------------------------- */
 924 
 925 static void
 926 find_add_match (const char *dir, const char *file, gsize start, gsize end)
     /* [previous][next][first][last][top][bottom][index][help]  */
 927 {
 928     insert_file (dir, file, start, end);
 929 
 930     // Don't scroll
 931     if (matches == 0)
 932         listbox_select_first (find_list);
 933     widget_draw (WIDGET (find_list));
 934 
 935     matches++;
 936     found_num_update ();
 937 }
 938 
 939 /* --------------------------------------------------------------------------------------------- */
 940 
 941 static FindProgressStatus
 942 check_find_events (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 943 {
 944     Gpm_Event event;
 945     int c;
 946 
 947     event.x = -1;
 948     c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE);
 949     if (c != EV_NONE)
 950     {
 951         dlg_process_event (h, c, &event);
 952         if (h->ret_value == B_ENTER || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN
 953             || h->ret_value == B_PANELIZE)
 954         {
 955             // dialog terminated
 956             return FIND_ABORT;
 957         }
 958         if (!widget_get_state (WIDGET (h), WST_IDLE))
 959         {
 960             // searching suspended
 961             return FIND_SUSPEND;
 962         }
 963     }
 964 
 965     return FIND_CONT;
 966 }
 967 
 968 /* --------------------------------------------------------------------------------------------- */
 969 /**
 970  * search_content:
 971  *
 972  * Search the content_pattern string in the DIRECTORY/FILE.
 973  * It will add the found entries to the find listbox.
 974  *
 975  * returns FALSE if do_search should look for another file
 976  *         TRUE if do_search should exit and proceed to the event handler
 977  */
 978 
 979 static gboolean
 980 search_content (WDialog *h, const char *directory, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help]  */
 981 {
 982     struct stat s;
 983     char buffer[BUF_4K] = "";  // raw input buffer
 984     int file_fd = -1;
 985     gboolean ret_val = FALSE;
 986     vfs_path_t *vpath;
 987     gint64 tv;
 988     gboolean status_updated = FALSE;
 989 
 990     vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
 991 
 992     if (mc_stat (vpath, &s) == 0 && S_ISREG (s.st_mode))
 993         file_fd = mc_open (vpath, O_RDONLY);
 994 
 995     vfs_path_free (vpath, TRUE);
 996 
 997     if (file_fd == -1)
 998         return FALSE;
 999 
1000     // get time elapsed from last refresh
1001     tv = g_get_monotonic_time ();
1002 
1003     if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL)
1004     {
1005         g_snprintf (buffer, sizeof (buffer), _ ("Grepping in %s"), filename);
1006         status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8));
1007         mc_refresh ();
1008         last_refresh = tv;
1009         status_updated = TRUE;
1010     }
1011 
1012     tty_enable_interrupt_key ();
1013     tty_got_interrupt ();
1014 
1015     {
1016         int line = 1;
1017         int pos = 0;
1018         int n_read = 0;
1019         off_t off = 0;  // file_fd's offset corresponding to strbuf[0]
1020         gboolean found = FALSE;
1021         char *strbuf = NULL;  // buffer for fetched string
1022         int strbuf_size = 0;
1023         int i = -1;  // compensate for a newline we'll add when we first enter the loop
1024 
1025         if (resuming)
1026         {
1027             // We've been previously suspended, start from the previous position
1028             resuming = FALSE;
1029             line = last_line;
1030             pos = last_pos;
1031             off = last_off;
1032             i = last_i;
1033         }
1034 
1035         while (!ret_val)
1036         {
1037             char ch = '\0';
1038             gsize found_len;
1039 
1040             off += i + 1;  // the previous line, plus a newline character
1041             i = 0;
1042 
1043             // read to buffer and get line from there
1044             while (TRUE)
1045             {
1046                 if (pos >= n_read)
1047                 {
1048                     pos = 0;
1049                     n_read = mc_read (file_fd, buffer, sizeof (buffer));
1050                     if (n_read <= 0)
1051                         break;
1052                 }
1053 
1054                 ch = buffer[pos++];
1055                 if (ch == '\0')
1056                 {
1057                     // skip possible leading zero(s)
1058                     if (i == 0)
1059                     {
1060                         off++;
1061                         continue;
1062                     }
1063                     break;
1064                 }
1065 
1066                 if (i >= strbuf_size - 1)
1067                 {
1068                     strbuf_size += 128;
1069                     strbuf = g_realloc (strbuf, strbuf_size);
1070                 }
1071 
1072                 // Strip newline
1073                 if (ch == '\n')
1074                     break;
1075 
1076                 strbuf[i++] = ch;
1077             }
1078 
1079             if (i == 0)
1080             {
1081                 if (ch == '\0')
1082                     break;
1083 
1084                 // if (ch == '\n'): do not search in empty strings
1085                 goto skip_search;
1086             }
1087 
1088             strbuf[i] = '\0';
1089 
1090             if (!found  // Search in binary line once
1091                 && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
1092             {
1093                 gsize found_start;
1094                 char result[BUF_MEDIUM];
1095 
1096                 if (!status_updated)
1097                 {
1098                     /* if we add results for a file, we have to ensure that
1099                        name of this file is shown in status bar */
1100                     g_snprintf (result, sizeof (result), _ ("Grepping in %s"), filename);
1101                     status_update (str_trunc (result, WIDGET (h)->rect.cols - 8));
1102                     mc_refresh ();
1103                     last_refresh = tv;
1104                     status_updated = TRUE;
1105                 }
1106 
1107                 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
1108                 found_start =
1109                     off + search_content_handle->normal_offset + 1;  // off by one: ticket 3280
1110                 find_add_match (directory, result, found_start, found_start + found_len);
1111                 found = TRUE;
1112             }
1113 
1114             if (found && options.content_first_hit)
1115                 break;
1116 
1117             if (ch == '\n')
1118             {
1119             skip_search:
1120                 found = FALSE;
1121                 line++;
1122             }
1123 
1124             if ((line & 0xff) == 0)
1125             {
1126                 FindProgressStatus res;
1127 
1128                 res = check_find_events (h);
1129                 switch (res)
1130                 {
1131                 case FIND_ABORT:
1132                     stop_idle (h);
1133                     ret_val = TRUE;
1134                     break;
1135                 case FIND_SUSPEND:
1136                     resuming = TRUE;
1137                     last_line = line;
1138                     last_pos = pos;
1139                     last_off = off;
1140                     last_i = i;
1141                     ret_val = TRUE;
1142                     break;
1143                 default:
1144                     break;
1145                 }
1146             }
1147         }
1148 
1149         g_free (strbuf);
1150     }
1151 
1152     tty_disable_interrupt_key ();
1153     mc_close (file_fd);
1154     return ret_val;
1155 }
1156 
1157 /* --------------------------------------------------------------------------------------------- */
1158 
1159 /**
1160   If dir is absolute, this means we're within dir and searching file here.
1161   If dir is relative, this means we're going to add dir to the directory stack.
1162 **/
1163 static gboolean
1164 find_ignore_dir_search (const char *dir, size_t len)
     /* [previous][next][first][last][top][bottom][index][help]  */
1165 {
1166     if (find_ignore_dirs != NULL)
1167     {
1168         const size_t dlen = len == (size_t) (-1) ? strlen (dir) : len;
1169         const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
1170 
1171         char **ignore_dir;
1172 
1173         for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
1174         {
1175             const size_t ilen = strlen (*ignore_dir);
1176             const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
1177 
1178             // ignore dir is too long -- skip it
1179             if (dlen < ilen)
1180                 continue;
1181 
1182             // handle absolute and relative paths
1183             switch (iabs | dabs)
1184             {
1185             case 0:  // both paths are relative
1186             case 3:  // both paths are absolute
1187                 // if ignore dir is not a path  of dir -- skip it
1188                 if (strncmp (dir, *ignore_dir, ilen) == 0)
1189                 {
1190                     /* be sure that ignore dir is not a part of dir like:
1191                        ignore dir is "h", dir is "home" */
1192                     if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
1193                         return TRUE;
1194                 }
1195                 break;
1196             case 1:  // dir is absolute, ignore_dir is relative
1197             {
1198                 char *d;
1199 
1200                 d = strstr (dir, *ignore_dir);
1201                 if (d != NULL && IS_PATH_SEP (d[-1]) && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1202                     return TRUE;
1203             }
1204             break;
1205             case 2:  // dir is relative, ignore_dir is absolute
1206                 // FIXME: skip this case
1207                 break;
1208             default:  // this cannot occurs
1209                 return FALSE;
1210             }
1211         }
1212     }
1213 
1214     return FALSE;
1215 }
1216 
1217 /* --------------------------------------------------------------------------------------------- */
1218 
1219 static void
1220 find_rotate_dash (const WDialog *h, gboolean show)
     /* [previous][next][first][last][top][bottom][index][help]  */
1221 {
1222     static size_t pos = 0;
1223     static const char rotating_dash[4] = "|/-\\";
1224     const Widget *w = CONST_WIDGET (h);
1225     const int *colors;
1226 
1227     colors = widget_get_colors (w);
1228     tty_setcolor (colors[DLG_COLOR_NORMAL]);
1229     widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
1230     tty_print_char (show ? rotating_dash[pos] : ' ');
1231     pos = (pos + 1) % sizeof (rotating_dash);
1232     mc_refresh ();
1233 }
1234 
1235 /* --------------------------------------------------------------------------------------------- */
1236 
1237 static int
1238 do_search (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1239 {
1240     static struct vfs_dirent *dp = NULL;
1241     static DIR *dirp = NULL;
1242     static char *directory = NULL;
1243     static gboolean pop_start_dir = TRUE;
1244     struct stat tmp_stat;
1245     gsize bytes_found;
1246     unsigned short count;
1247 
1248     if (h == NULL)
1249     {  // someone forces me to close dirp
1250         if (dirp != NULL)
1251         {
1252             mc_closedir (dirp);
1253             dirp = NULL;
1254         }
1255         MC_PTR_FREE (directory);
1256         dp = NULL;
1257         pop_start_dir = TRUE;
1258         return 1;
1259     }
1260 
1261     for (count = 0; count < 32; count++)
1262     {
1263         while (dp == NULL)
1264         {
1265             if (dirp != NULL)
1266             {
1267                 mc_closedir (dirp);
1268                 dirp = NULL;
1269             }
1270 
1271             while (dirp == NULL)
1272             {
1273                 vfs_path_t *tmp_vpath = NULL;
1274 
1275                 tty_setcolor (REVERSE_COLOR);
1276 
1277                 while (TRUE)
1278                 {
1279                     tmp_vpath = pop_directory ();
1280                     if (tmp_vpath == NULL)
1281                     {
1282                         running = FALSE;
1283                         if (ignore_count == 0)
1284                             status_update (_ ("Finished"));
1285                         else
1286                         {
1287                             char msg[BUF_SMALL];
1288 
1289                             g_snprintf (msg, sizeof (msg),
1290                                         ngettext ("Finished (ignored %zu directory)",
1291                                                   "Finished (ignored %zu directories)",
1292                                                   ignore_count),
1293                                         ignore_count);
1294                             status_update (msg);
1295                         }
1296                         if (verbose)
1297                             find_rotate_dash (h, FALSE);
1298                         stop_idle (h);
1299                         return 0;
1300                     }
1301 
1302                     /* The start directory is the first one in the stack (see do_find() below).
1303                        Do not apply ignore_dir to it. */
1304                     if (pop_start_dir)
1305                     {
1306                         pop_start_dir = FALSE;
1307                         break;
1308                     }
1309 
1310                     pop_start_dir = FALSE;
1311 
1312                     // handle absolute ignore dirs here
1313                     if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath), -1))
1314                         break;
1315 
1316                     vfs_path_free (tmp_vpath, TRUE);
1317                     ignore_count++;
1318                 }
1319 
1320                 g_free (directory);
1321 
1322                 if (verbose)
1323                 {
1324                     char buffer[BUF_MEDIUM];
1325 
1326                     directory = (char *) vfs_path_as_str (tmp_vpath);
1327                     g_snprintf (buffer, sizeof (buffer), _ ("Searching %s"), directory);
1328                     status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
1329                 }
1330 
1331                 dirp = mc_opendir (tmp_vpath);
1332                 directory = vfs_path_free (tmp_vpath, FALSE);
1333             }  // while (!dirp)
1334 
1335             // skip invalid filenames
1336             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1337                 ;
1338         }  // while (!dp)
1339 
1340         if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1341         {
1342             // skip invalid filenames
1343             while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1344                 ;
1345 
1346             return 1;
1347         }
1348 
1349         if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1350         {
1351             gboolean search_ok;
1352 
1353             if (options.find_recurs && (directory != NULL))
1354             {  // Can directory be NULL ?
1355                 // handle relative ignore dirs here
1356                 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name, dp->d_len))
1357                     ignore_count++;
1358                 else
1359                 {
1360                     vfs_path_t *tmp_vpath;
1361                     int stat_res;
1362 
1363                     tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1364 
1365                     if (options.follow_symlinks)
1366                         stat_res = mc_stat (tmp_vpath, &tmp_stat);
1367                     else
1368                         stat_res = mc_lstat (tmp_vpath, &tmp_stat);
1369 
1370                     if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
1371                         push_directory (tmp_vpath);
1372                     else
1373                         vfs_path_free (tmp_vpath, TRUE);
1374                 }
1375             }
1376 
1377             search_ok = mc_search_run (search_file_handle, dp->d_name, 0, dp->d_len, &bytes_found);
1378 
1379             if (search_ok)
1380             {
1381                 if (content_pattern == NULL)
1382                     find_add_match (directory, dp->d_name, 0, 0);
1383                 else if (search_content (h, directory, dp->d_name))
1384                     return 1;
1385             }
1386         }
1387 
1388         // skip invalid filenames
1389         while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1390             ;
1391     }  // for
1392 
1393     if (verbose)
1394         find_rotate_dash (h, TRUE);
1395 
1396     return 1;
1397 }
1398 
1399 /* --------------------------------------------------------------------------------------------- */
1400 
1401 static void
1402 init_find_vars (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1403 {
1404     MC_PTR_FREE (old_dir);
1405     matches = 0;
1406     ignore_count = 0;
1407 
1408     // Remove all the items from the stack
1409     clear_stack ();
1410 
1411     g_strfreev (find_ignore_dirs);
1412     find_ignore_dirs = NULL;
1413 }
1414 
1415 /* --------------------------------------------------------------------------------------------- */
1416 
1417 static void
1418 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]  */
1419                    off_t search_end)
1420 {
1421     const char *filename = NULL;
1422     int line;
1423     vfs_path_t *fullname_vpath;
1424 
1425     if (content_pattern != NULL)
1426     {
1427         filename = strchr (file + 4, ':') + 1;
1428         line = atoi (file + 4);
1429     }
1430     else
1431     {
1432         filename = file + 4;
1433         line = 0;
1434     }
1435 
1436     fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1437     if (edit)
1438         edit_file_at_line (fullname_vpath, use_internal_edit, line);
1439     else
1440         view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1441                            search_end);
1442     vfs_path_free (fullname_vpath, TRUE);
1443 }
1444 
1445 /* --------------------------------------------------------------------------------------------- */
1446 
1447 static cb_ret_t
1448 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
1449 {
1450     char *text = NULL;
1451     find_match_location_t *location;
1452 
1453     listbox_get_current (find_list, &text, (void **) &location);
1454 
1455     if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1456         return MSG_NOT_HANDLED;
1457 
1458     find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1459     return MSG_HANDLED;
1460 }
1461 
1462 /* --------------------------------------------------------------------------------------------- */
1463 
1464 static void
1465 find_calc_button_locations (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1466 {
1467     const int cols = CONST_WIDGET (h)->rect.cols;
1468 
1469     int l1, l2;
1470 
1471     l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1472     l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1473 
1474     fbuts[0].x = (cols - l1) / 2;
1475     fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1476     fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1477     fbuts[3].x = fbuts[2].x;
1478     fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1479 
1480     if (all_buttons)
1481     {
1482         fbuts[5].x = (cols - l2) / 2;
1483         fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1484         fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1485     }
1486 }
1487 
1488 /* --------------------------------------------------------------------------------------------- */
1489 
1490 static void
1491 find_adjust_header (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1492 {
1493     char title[BUF_MEDIUM];
1494     int title_len;
1495 
1496     if (content_pattern != NULL)
1497         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\". Content: \"%s\""), find_pattern,
1498                     content_pattern);
1499     else
1500         g_snprintf (title, sizeof (title), _ ("Find File: \"%s\""), find_pattern);
1501 
1502     title_len = str_term_width1 (title);
1503     if (title_len > WIDGET (h)->rect.cols - 6)
1504     {
1505         // title is too wide, truncate it
1506         title_len = WIDGET (h)->rect.cols - 6;
1507         title_len = str_column_to_pos (title, title_len);
1508         title_len -= 3;  // reserve space for three dots
1509         title_len = str_offset_to_pos (title, title_len);
1510         // mark that title is truncated
1511         memmove (title + title_len, "...", 4);
1512     }
1513 
1514     frame_set_title (FRAME (h->bg), title);
1515 }
1516 
1517 /* --------------------------------------------------------------------------------------------- */
1518 
1519 static void
1520 find_relocate_buttons (const WDialog *h, gboolean all_buttons)
     /* [previous][next][first][last][top][bottom][index][help]  */
1521 {
1522     size_t i;
1523 
1524     find_calc_button_locations (h, all_buttons);
1525 
1526     for (i = 0; i < fbuts_num; i++)
1527         fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
1528 }
1529 
1530 /* --------------------------------------------------------------------------------------------- */
1531 
1532 static cb_ret_t
1533 find_resize (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1534 {
1535     Widget *w = WIDGET (h);
1536     WRect r = w->rect;
1537 
1538     r.lines = LINES - 4;
1539     r.cols = COLS - 16;
1540     dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
1541     find_adjust_header (h);
1542     find_relocate_buttons (h, TRUE);
1543 
1544     return MSG_HANDLED;
1545 }
1546 
1547 /* --------------------------------------------------------------------------------------------- */
1548 
1549 static cb_ret_t
1550 find_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1551 {
1552     WDialog *h = DIALOG (w);
1553 
1554     switch (msg)
1555     {
1556     case MSG_INIT:
1557         group_default_callback (w, NULL, MSG_INIT, 0, NULL);
1558         find_adjust_header (h);
1559         return MSG_HANDLED;
1560 
1561     case MSG_KEY:
1562         if (parm == KEY_F (3) || parm == KEY_F (13))
1563         {
1564             gboolean unparsed_view = (parm == KEY_F (13));
1565 
1566             return view_edit_currently_selected_file (unparsed_view, FALSE);
1567         }
1568         if (parm == KEY_F (4))
1569             return view_edit_currently_selected_file (FALSE, TRUE);
1570         return MSG_NOT_HANDLED;
1571 
1572     case MSG_RESIZE:
1573         return find_resize (h);
1574 
1575     case MSG_IDLE:
1576         do_search (h);
1577         return MSG_HANDLED;
1578 
1579     default:
1580         return dlg_default_callback (w, sender, msg, parm, data);
1581     }
1582 }
1583 
1584 /* --------------------------------------------------------------------------------------------- */
1585 /** Handles the Stop/Start button in the find window */
1586 
1587 static int
1588 start_stop (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1589 {
1590     Widget *w = WIDGET (button);
1591 
1592     (void) action;
1593 
1594     running = is_start;
1595     widget_idle (WIDGET (find_dlg), running);
1596     is_start = !is_start;
1597 
1598     status_update (is_start ? _ ("Stopped") : _ ("Searching"));
1599     button_set_text (button, fbuts[is_start ? 3 : 2].text);
1600 
1601     find_relocate_buttons (DIALOG (w->owner), FALSE);
1602     widget_draw (WIDGET (w->owner));
1603 
1604     return 0;
1605 }
1606 
1607 /* --------------------------------------------------------------------------------------------- */
1608 /** Handle view command, when invoked as a button */
1609 
1610 static int
1611 find_do_view_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, FALSE);
1617     return 0;
1618 }
1619 
1620 /* --------------------------------------------------------------------------------------------- */
1621 /** Handle edit command, when invoked as a button */
1622 
1623 static int
1624 find_do_edit_file (WButton *button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
1625 {
1626     (void) button;
1627     (void) action;
1628 
1629     view_edit_currently_selected_file (FALSE, TRUE);
1630     return 0;
1631 }
1632 
1633 /* --------------------------------------------------------------------------------------------- */
1634 
1635 static void
1636 setup_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1637 {
1638     WGroup *g;
1639     size_t i;
1640     int lines, cols;
1641     int y;
1642 
1643     static gboolean i18n_flag = FALSE;
1644 
1645     if (!i18n_flag)
1646     {
1647         for (i = 0; i < fbuts_num; i++)
1648         {
1649 #ifdef ENABLE_NLS
1650             fbuts[i].text = _ (fbuts[i].text);
1651 #endif
1652             fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1653             if (fbuts[i].flags == DEFPUSH_BUTTON)
1654                 fbuts[i].len += 2;
1655         }
1656 
1657         i18n_flag = TRUE;
1658     }
1659 
1660     lines = LINES - 4;
1661     cols = COLS - 16;
1662 
1663     find_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
1664                            find_callback, NULL, "[Find File]", NULL);
1665     g = GROUP (find_dlg);
1666 
1667     find_calc_button_locations (find_dlg, TRUE);
1668 
1669     y = 2;
1670     find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1671     group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
1672     y += WIDGET (find_list)->rect.lines;
1673 
1674     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1675 
1676     found_num_label = label_new (y++, 4, NULL);
1677     group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1678 
1679     status_label = label_new (y++, 4, _ ("Searching"));
1680     group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
1681 
1682     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1683 
1684     for (i = 0; i < fbuts_num; i++)
1685     {
1686         if (i == 3)
1687             fbuts[3].button = fbuts[2].button;
1688         else
1689         {
1690             fbuts[i].button = WIDGET (button_new (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags,
1691                                                   fbuts[i].text, fbuts[i].callback));
1692             group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1693         }
1694 
1695         if (i == quit_button)
1696             y++;
1697     }
1698 
1699     widget_select (WIDGET (find_list));
1700 }
1701 
1702 /* --------------------------------------------------------------------------------------------- */
1703 
1704 static int
1705 run_process (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1706 {
1707     int ret;
1708 
1709     search_content_handle = mc_search_new (content_pattern, NULL);
1710     if (search_content_handle)
1711     {
1712         search_content_handle->search_type =
1713             options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1714         search_content_handle->is_case_sensitive = options.content_case_sens;
1715         search_content_handle->whole_words = options.content_whole_words;
1716         search_content_handle->is_all_charsets = options.content_all_charsets;
1717     }
1718     search_file_handle = mc_search_new (find_pattern, NULL);
1719     search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1720     search_file_handle->is_case_sensitive = options.file_case_sens;
1721     search_file_handle->is_all_charsets = options.file_all_charsets;
1722     search_file_handle->is_entire_line = options.file_pattern;
1723 
1724     resuming = FALSE;
1725 
1726     widget_idle (WIDGET (find_dlg), TRUE);
1727     ret = dlg_run (find_dlg);
1728 
1729     mc_search_free (search_file_handle);
1730     search_file_handle = NULL;
1731     mc_search_free (search_content_handle);
1732     search_content_handle = NULL;
1733 
1734     return ret;
1735 }
1736 
1737 /* --------------------------------------------------------------------------------------------- */
1738 
1739 static void
1740 kill_gui (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1741 {
1742     Widget *w = WIDGET (find_dlg);
1743 
1744     widget_idle (w, FALSE);
1745     widget_destroy (w);
1746 }
1747 
1748 /* --------------------------------------------------------------------------------------------- */
1749 
1750 static int
1751 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]  */
1752          char **dirname, char **filename)
1753 {
1754     int return_value;
1755     char *dir_tmp = NULL, *file_tmp = NULL;
1756 
1757     setup_gui ();
1758 
1759     init_find_vars ();
1760     parse_ignore_dirs (ignore_dirs);
1761     push_directory (vfs_path_from_str (start_dir));
1762 
1763     return_value = run_process ();
1764 
1765     // Clear variables
1766     init_find_vars ();
1767 
1768     get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1769 
1770     if (dir_tmp != NULL)
1771         *dirname = g_strdup (dir_tmp);
1772     if (file_tmp != NULL)
1773         *filename = g_strdup (file_tmp);
1774 
1775     if (return_value == B_PANELIZE && *filename != NULL)
1776     {
1777         struct stat st;
1778         GList *entry;
1779         dir_list *list = &panel->dir;
1780         char *name = NULL;
1781         gboolean ok = TRUE;
1782 
1783         panel_clean_dir (panel);
1784         dir_list_init (list);
1785 
1786         for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
1787              entry = g_list_next (entry))
1788         {
1789             const char *lc_filename;
1790             WLEntry *le = LENTRY (entry->data);
1791             find_match_location_t *location = le->data;
1792             char *p;
1793             gboolean link_to_dir, stale_link;
1794 
1795             if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1796                 continue;
1797 
1798             if (!content_is_empty)
1799                 lc_filename = strchr (le->text + 4, ':') + 1;
1800             else
1801                 lc_filename = le->text + 4;
1802 
1803             name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1804             // skip initial start dir
1805             p = name;
1806             if (start_dir_len > 0)
1807                 p += (size_t) start_dir_len;
1808             if (IS_PATH_SEP (*p))
1809                 p++;
1810 
1811             if (!handle_path (p, &st, &link_to_dir, &stale_link))
1812             {
1813                 g_free (name);
1814                 continue;
1815             }
1816 
1817             // don't add files more than once to the panel
1818             if (!content_is_empty && list->len != 0
1819                 && strcmp (list->list[list->len - 1].fname->str, p) == 0)
1820             {
1821                 g_free (name);
1822                 continue;
1823             }
1824 
1825             ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
1826 
1827             g_free (name);
1828 
1829             if ((list->len & 15) == 0)
1830                 rotate_dash (TRUE);
1831         }
1832 
1833         panel->is_panelized = TRUE;
1834         panel_panelize_absolutize_if_needed (panel);
1835         panel_panelize_save (panel);
1836     }
1837 
1838     kill_gui ();
1839     do_search (NULL);  // force do_search to release resources
1840     MC_PTR_FREE (old_dir);
1841     rotate_dash (FALSE);
1842 
1843     return return_value;
1844 }
1845 
1846 /* --------------------------------------------------------------------------------------------- */
1847 /*** public functions ****************************************************************************/
1848 /* --------------------------------------------------------------------------------------------- */
1849 
1850 void
1851 find_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1852 {
1853     char *start_dir = NULL, *ignore_dirs = NULL;
1854     ssize_t start_dir_len;
1855 
1856     find_pattern = NULL;
1857     content_pattern = NULL;
1858 
1859     while (find_parameters (panel, &start_dir, &start_dir_len, &ignore_dirs, &find_pattern,
1860                             &content_pattern))
1861     {
1862         char *filename = NULL, *dirname = NULL;
1863         int v = B_CANCEL;
1864 
1865         content_is_empty = content_pattern == NULL;
1866 
1867         if (find_pattern[0] != '\0')
1868         {
1869             last_refresh = 0;
1870 
1871             is_start = FALSE;
1872 
1873             if (!content_is_empty && !str_is_valid_string (content_pattern))
1874                 MC_PTR_FREE (content_pattern);
1875 
1876             v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1877         }
1878 
1879         g_free (start_dir);
1880         g_free (ignore_dirs);
1881         MC_PTR_FREE (find_pattern);
1882 
1883         if (v == B_ENTER)
1884         {
1885             if (dirname != NULL)
1886             {
1887                 vfs_path_t *dirname_vpath;
1888 
1889                 dirname_vpath = vfs_path_from_str (dirname);
1890                 panel_cd (panel, dirname_vpath, cd_exact);
1891                 vfs_path_free (dirname_vpath, TRUE);
1892 
1893                 if (filename != NULL)
1894                 {
1895                     size_t offset;
1896 
1897                     if (content_pattern == NULL)
1898                         offset = 4;
1899                     else
1900                         offset = strchr (filename + 4, ':') - filename + 1;
1901 
1902                     panel_set_current_by_name (panel, filename + offset);
1903                 }
1904             }
1905             else if (filename != NULL)
1906             {
1907                 vfs_path_t *filename_vpath;
1908 
1909                 filename_vpath = vfs_path_from_str (filename);
1910                 panel_cd (panel, filename_vpath, cd_exact);
1911                 vfs_path_free (filename_vpath, TRUE);
1912             }
1913         }
1914 
1915         MC_PTR_FREE (content_pattern);
1916         g_free (dirname);
1917         g_free (filename);
1918 
1919         if (v == B_ENTER || v == B_CANCEL)
1920             break;
1921 
1922         if (v == B_PANELIZE)
1923         {
1924             panel_re_sort (panel);
1925             panel_set_current_by_name (panel, NULL);
1926             break;
1927         }
1928     }
1929 }
1930 
1931 /* --------------------------------------------------------------------------------------------- */

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