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. stop_idle
  7. status_update
  8. found_num_update
  9. get_list_info
  10. find_check_regexp
  11. find_toggle_enable_ignore_dirs
  12. find_toggle_enable_params
  13. find_toggle_enable_content
  14. find_parm_callback
  15. find_parameters
  16. push_directory
  17. pop_directory
  18. clear_stack
  19. insert_file
  20. find_add_match
  21. check_find_events
  22. search_content
  23. find_ignore_dir_search
  24. find_rotate_dash
  25. do_search
  26. init_find_vars
  27. find_do_view_edit
  28. view_edit_currently_selected_file
  29. find_calc_button_locations
  30. find_adjust_header
  31. find_relocate_buttons
  32. find_callback
  33. start_stop
  34. find_do_view_file
  35. find_do_edit_file
  36. setup_gui
  37. run_process
  38. kill_gui
  39. do_find
  40. find_file

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

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