Merge branch '4549_subshell_execl_argv0'
[midnight-commander.git] / src / filemanager / find.c
blobb5d148af36a34ecb28f155cdd53a59950787cc22
1 /*
2 Find file command for the Midnight Commander
4 Copyright (C) 1995-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Miguel de Icaza, 1995
9 Slava Zanko <slavazanko@gmail.com>, 2013
10 Andrew Borodin <aborodin@vmail.ru>, 2013-2022
12 This file is part of the Midnight Commander.
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.
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.
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/>.
28 /** \file find.c
29 * \brief Source: Find file command
32 #include <config.h>
34 #include <ctype.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/stat.h>
40 #include "lib/global.h"
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() */
52 #include "src/setup.h" /* verbose */
53 #include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
55 #include "dir.h"
56 #include "cmd.h" /* find_cmd(), view_file_at_line() */
57 #include "boxes.h"
58 #include "panelize.h"
60 /*** global variables ****************************************************************************/
62 /*** file scope macro definitions ****************************************************************/
64 #define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */
65 #define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */
67 /*** file scope type declarations ****************************************************************/
69 /* A couple of extra messages we need */
70 enum
72 B_STOP = B_USER + 1,
73 B_AGAIN,
74 B_PANELIZE,
75 B_TREE,
76 B_VIEW
79 typedef enum
81 FIND_CONT = 0,
82 FIND_SUSPEND,
83 FIND_ABORT
84 } FindProgressStatus;
86 /* find file options */
87 typedef struct
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;
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;
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;
110 typedef struct
112 char *dir;
113 gsize start;
114 gsize end;
115 } find_match_location_t;
117 /*** forward declarations (file scope functions) *************************************************/
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);
124 /*** file scope variables ************************************************************************/
126 /* Parsed ignore dirs */
127 static char **find_ignore_dirs = NULL;
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 #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;
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;
160 static gint64 last_refresh;
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;
169 static size_t ignore_count = 0;
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 */
175 /* This keeps track of the directory stack */
176 static GQueue dir_queue = G_QUEUE_INIT;
178 /* *INDENT-OFF* */
179 static struct
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[] =
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 },
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 }
200 /* *INDENT-ON* */
202 static const size_t fbuts_num = G_N_ELEMENTS (fbuts);
203 static const size_t quit_button = 4; /* index of "Quit" button */
205 static WListbox *find_list; /* Listbox with the file list */
207 static find_file_options_t options = {
208 TRUE, TRUE, TRUE, FALSE, FALSE, FALSE,
209 TRUE, FALSE, FALSE, FALSE, FALSE,
210 FALSE, NULL
213 static char *in_start_dir = INPUT_LAST_TEXT;
215 static mc_search_t *search_file_handle = NULL;
216 static mc_search_t *search_content_handle = NULL;
218 /* --------------------------------------------------------------------------------------------- */
219 /*** file scope functions ************************************************************************/
220 /* --------------------------------------------------------------------------------------------- */
222 /* don't use max macro to avoid double str_term_width1() call in widget length calculation */
223 #undef max
225 static int
226 max (int a, int b)
228 return (a > b ? a : b);
231 /* --------------------------------------------------------------------------------------------- */
233 static void
234 parse_ignore_dirs (const char *ignore_dirs)
236 size_t r = 0, w = 0; /* read and write iterators */
238 if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0')
239 return;
241 find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
243 /* Values like '/foo::/bar: produce holes in list.
244 * Find and remove them */
245 for (; find_ignore_dirs[r] != NULL; r++)
247 if (find_ignore_dirs[r][0] == '\0')
249 /* empty entry -- skip it */
250 MC_PTR_FREE (find_ignore_dirs[r]);
251 continue;
254 if (r != w)
256 /* copy entry to the previous free array cell */
257 find_ignore_dirs[w] = find_ignore_dirs[r];
258 find_ignore_dirs[r] = NULL;
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]);
268 if (find_ignore_dirs[0] == NULL)
270 g_strfreev (find_ignore_dirs);
271 find_ignore_dirs = NULL;
275 /* --------------------------------------------------------------------------------------------- */
277 static void
278 find_load_options (void)
280 static gboolean loaded = FALSE;
282 if (loaded)
283 return;
285 loaded = TRUE;
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.follow_symlinks =
294 mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE);
295 options.skip_hidden =
296 mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE);
297 options.file_all_charsets =
298 mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE);
299 options.content_case_sens =
300 mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE);
301 options.content_regexp =
302 mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE);
303 options.content_first_hit =
304 mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE);
305 options.content_whole_words =
306 mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE);
307 options.content_all_charsets =
308 mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE);
309 options.ignore_dirs_enable =
310 mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE);
311 options.ignore_dirs =
312 mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", "");
314 if (options.ignore_dirs[0] == '\0')
315 MC_PTR_FREE (options.ignore_dirs);
318 /* --------------------------------------------------------------------------------------------- */
320 static void
321 find_save_options (void)
323 mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens",
324 options.file_case_sens);
325 mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern",
326 options.file_pattern);
327 mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs);
328 mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks",
329 options.follow_symlinks);
330 mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
331 mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets",
332 options.file_all_charsets);
333 mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens",
334 options.content_case_sens);
335 mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp",
336 options.content_regexp);
337 mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit",
338 options.content_first_hit);
339 mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words",
340 options.content_whole_words);
341 mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets",
342 options.content_all_charsets);
343 mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable",
344 options.ignore_dirs_enable);
345 mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs);
348 /* --------------------------------------------------------------------------------------------- */
350 static inline char *
351 add_to_list (const char *text, void *data)
353 return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
356 /* --------------------------------------------------------------------------------------------- */
358 static inline char *
359 add_to_list_take (char *text, void *data)
361 return listbox_add_item_take (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
364 /* --------------------------------------------------------------------------------------------- */
366 static inline void
367 stop_idle (void *data)
369 widget_idle (WIDGET (data), FALSE);
372 /* --------------------------------------------------------------------------------------------- */
374 static inline void
375 status_update (const char *text)
377 label_set_text (status_label, text);
380 /* --------------------------------------------------------------------------------------------- */
382 static inline void
383 found_num_update (void)
385 label_set_textv (found_num_label, _("Found: %lu"), matches);
388 /* --------------------------------------------------------------------------------------------- */
390 static void
391 get_list_info (char **file, char **dir, gsize *start, gsize *end)
393 find_match_location_t *location;
395 listbox_get_current (find_list, file, (void **) &location);
396 if (location != NULL)
398 if (dir != NULL)
399 *dir = location->dir;
400 if (start != NULL)
401 *start = location->start;
402 if (end != NULL)
403 *end = location->end;
405 else
407 if (dir != NULL)
408 *dir = NULL;
412 /* --------------------------------------------------------------------------------------------- */
413 /** check regular expression */
415 static gboolean
416 find_check_regexp (const char *r)
418 mc_search_t *search;
419 gboolean regexp_ok = FALSE;
421 search = mc_search_new (r, NULL);
423 if (search != NULL)
425 search->search_type = MC_SEARCH_T_REGEX;
426 regexp_ok = mc_search_prepare (search);
427 mc_search_free (search);
430 return regexp_ok;
433 /* --------------------------------------------------------------------------------------------- */
435 static void
436 find_toggle_enable_ignore_dirs (void)
438 widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
441 /* --------------------------------------------------------------------------------------------- */
443 static void
444 find_toggle_enable_params (void)
446 gboolean disable = input_is_empty (in_name);
448 widget_disable (WIDGET (file_pattern_cbox), disable);
449 widget_disable (WIDGET (file_case_sens_cbox), disable);
450 #ifdef HAVE_CHARSET
451 widget_disable (WIDGET (file_all_charsets_cbox), disable);
452 #endif
455 /* --------------------------------------------------------------------------------------------- */
457 static void
458 find_toggle_enable_content (void)
460 widget_disable (WIDGET (content_regexp_cbox), content_is_empty);
461 widget_disable (WIDGET (content_case_sens_cbox), content_is_empty);
462 #ifdef HAVE_CHARSET
463 widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty);
464 #endif
465 widget_disable (WIDGET (content_whole_words_cbox), content_is_empty);
466 widget_disable (WIDGET (content_first_hit_cbox), content_is_empty);
469 /* --------------------------------------------------------------------------------------------- */
471 * Callback for the parameter dialog.
472 * Validate regex, prevent closing the dialog if it's invalid.
475 static cb_ret_t
476 find_parm_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
478 /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies.
479 * Use this time moment to check input field content. We can't do that in MSG_INIT
480 * because history is not loaded yet.
481 * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or
482 * we could name it MSG_POST_INIT.
484 * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar
485 * purpose. We should remember to fix those places too when we introduce the new
486 * message.
488 static gboolean first_draw = TRUE;
490 WDialog *h = DIALOG (w);
492 switch (msg)
494 case MSG_INIT:
495 group_default_callback (w, NULL, MSG_INIT, 0, NULL);
496 first_draw = TRUE;
497 return MSG_HANDLED;
499 case MSG_NOTIFY:
500 if (sender == WIDGET (ignore_dirs_cbox))
502 find_toggle_enable_ignore_dirs ();
503 return MSG_HANDLED;
506 return MSG_NOT_HANDLED;
508 case MSG_VALIDATE:
509 if (h->ret_value != B_ENTER)
510 return MSG_HANDLED;
512 /* check filename regexp */
513 if (!file_pattern_cbox->state && !input_is_empty (in_name)
514 && !find_check_regexp (input_get_ctext (in_name)))
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_name));
520 return MSG_HANDLED;
523 /* check content regexp */
524 if (content_regexp_cbox->state && !content_is_empty
525 && !find_check_regexp (input_get_ctext (in_with)))
527 /* Don't stop the dialog */
528 widget_set_state (w, WST_ACTIVE, TRUE);
529 message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
530 widget_select (WIDGET (in_with));
531 return MSG_HANDLED;
534 return MSG_HANDLED;
536 case MSG_POST_KEY:
537 if (GROUP (h)->current->data == in_name)
538 find_toggle_enable_params ();
539 else if (GROUP (h)->current->data == in_with)
541 content_is_empty = input_is_empty (in_with);
542 find_toggle_enable_content ();
544 return MSG_HANDLED;
546 case MSG_DRAW:
547 if (first_draw)
549 find_toggle_enable_ignore_dirs ();
550 find_toggle_enable_params ();
551 find_toggle_enable_content ();
554 first_draw = FALSE;
555 MC_FALLTHROUGH; /* to call MSG_DRAW default handler */
557 default:
558 return dlg_default_callback (w, sender, msg, parm, data);
562 /* --------------------------------------------------------------------------------------------- */
564 * find_parameters: gets information from the user
566 * If the return value is TRUE, then the following holds:
568 * start_dir, ignore_dirs, pattern and content contain the information provided by the user.
569 * They are newly allocated strings and must be freed when unneeded.
571 * start_dir_len is -1 when user entered an absolute path, otherwise it is a length
572 * of start_dir (which is absolute). It is used to get a relative pats of find results.
575 static gboolean
576 find_parameters (WPanel *panel, char **start_dir, ssize_t *start_dir_len,
577 char **ignore_dirs, char **pattern, char **content)
579 WGroup *g;
581 /* Size of the find parameters window */
582 #ifdef HAVE_CHARSET
583 const int lines = 19;
584 #else
585 const int lines = 18;
586 #endif
587 int cols = 68;
589 gboolean return_value;
591 /* file name */
592 const char *file_name_label = N_("File name:");
593 const char *file_recurs_label = N_("&Find recursively");
594 const char *file_follow_symlinks = N_("Follow s&ymlinks");
595 const char *file_pattern_label = N_("&Using shell patterns");
596 #ifdef HAVE_CHARSET
597 const char *file_all_charsets_label = N_("&All charsets");
598 #endif
599 const char *file_case_label = N_("Cas&e sensitive");
600 const char *file_skip_hidden_label = N_("S&kip hidden");
602 /* file content */
603 const char *content_content_label = N_("Content:");
604 const char *content_use_label = N_("Sea&rch for content");
605 const char *content_regexp_label = N_("Re&gular expression");
606 const char *content_case_label = N_("Case sens&itive");
607 #ifdef HAVE_CHARSET
608 const char *content_all_charsets_label = N_("A&ll charsets");
609 #endif
610 const char *content_whole_words_label = N_("&Whole words");
611 const char *content_first_hit_label = N_("Fir&st hit");
613 const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") };
615 /* button lengths */
616 int b0, b1, b2, b12;
617 int y1, y2, x1, x2;
618 /* column width */
619 int cw;
621 #ifdef ENABLE_NLS
623 size_t i;
625 file_name_label = _(file_name_label);
626 file_recurs_label = _(file_recurs_label);
627 file_follow_symlinks = _(file_follow_symlinks);
628 file_pattern_label = _(file_pattern_label);
629 #ifdef HAVE_CHARSET
630 file_all_charsets_label = _(file_all_charsets_label);
631 #endif
632 file_case_label = _(file_case_label);
633 file_skip_hidden_label = _(file_skip_hidden_label);
635 /* file content */
636 content_content_label = _(content_content_label);
637 content_use_label = _(content_use_label);
638 content_regexp_label = _(content_regexp_label);
639 content_case_label = _(content_case_label);
640 #ifdef HAVE_CHARSET
641 content_all_charsets_label = _(content_all_charsets_label);
642 #endif
643 content_whole_words_label = _(content_whole_words_label);
644 content_first_hit_label = _(content_first_hit_label);
646 for (i = 0; i < G_N_ELEMENTS (buts); i++)
647 buts[i] = _(buts[i]);
649 #endif /* ENABLE_NLS */
651 /* calculate dialog width */
653 /* widget widths */
654 cw = str_term_width1 (file_name_label);
655 cw = max (cw, str_term_width1 (file_recurs_label) + 4);
656 cw = max (cw, str_term_width1 (file_follow_symlinks) + 4);
657 cw = max (cw, str_term_width1 (file_pattern_label) + 4);
658 #ifdef HAVE_CHARSET
659 cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
660 #endif
661 cw = max (cw, str_term_width1 (file_case_label) + 4);
662 cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
664 cw = max (cw, str_term_width1 (content_content_label) + 4);
665 cw = max (cw, str_term_width1 (content_use_label) + 4);
666 cw = max (cw, str_term_width1 (content_regexp_label) + 4);
667 cw = max (cw, str_term_width1 (content_case_label) + 4);
668 #ifdef HAVE_CHARSET
669 cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
670 #endif
671 cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
672 cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
674 /* button width */
675 b0 = str_term_width1 (buts[0]) + 3;
676 b1 = str_term_width1 (buts[1]) + 5; /* default button */
677 b2 = str_term_width1 (buts[2]) + 3;
678 b12 = b1 + b2 + 1;
680 cols = max (cols, max (b12, cw * 2 + 1) + 6);
682 find_load_options ();
684 if (in_start_dir == NULL)
685 in_start_dir = g_strdup (".");
687 find_dlg =
688 dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback,
689 NULL, "[Find File]", _("Find File"));
690 g = GROUP (find_dlg);
692 x1 = 3;
693 x2 = cols / 2 + 1;
694 cw = (cols - 7) / 2;
695 y1 = 2;
697 group_add_widget (g, label_new (y1++, x1, _("Start at:")));
698 in_start =
699 input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
700 INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
701 group_add_widget (g, in_start);
703 group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
705 ignore_dirs_cbox =
706 check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:"));
707 group_add_widget (g, ignore_dirs_cbox);
709 in_ignore =
710 input_new (y1++, x1, input_colors, cols - 6,
711 options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
712 INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
713 group_add_widget (g, in_ignore);
715 group_add_widget (g, hline_new (y1++, -1, -1));
717 y2 = y1;
719 /* Start 1st column */
720 group_add_widget (g, label_new (y1++, x1, file_name_label));
721 in_name =
722 input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
723 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
724 group_add_widget (g, in_name);
726 /* Start 2nd column */
727 content_label = label_new (y2++, x2, content_content_label);
728 group_add_widget (g, content_label);
729 in_with =
730 input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
731 MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
732 in_with->label = content_label;
733 group_add_widget (g, in_with);
735 /* Continue 1st column */
736 recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
737 group_add_widget (g, recursively_cbox);
739 follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks);
740 group_add_widget (g, follow_sym_cbox);
742 file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
743 group_add_widget (g, file_pattern_cbox);
745 file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
746 group_add_widget (g, file_case_sens_cbox);
748 #ifdef HAVE_CHARSET
749 file_all_charsets_cbox =
750 check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
751 group_add_widget (g, file_all_charsets_cbox);
752 #endif
754 skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
755 group_add_widget (g, skip_hidden_cbox);
757 /* Continue 2nd column */
758 content_whole_words_cbox =
759 check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
760 group_add_widget (g, content_whole_words_cbox);
762 content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
763 group_add_widget (g, content_regexp_cbox);
765 content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
766 group_add_widget (g, content_case_sens_cbox);
768 #ifdef HAVE_CHARSET
769 content_all_charsets_cbox =
770 check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
771 group_add_widget (g, content_all_charsets_cbox);
772 #endif
774 content_first_hit_cbox =
775 check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
776 group_add_widget (g, content_first_hit_cbox);
778 /* buttons */
779 y1 = max (y1, y2);
780 x1 = (cols - b12) / 2;
781 group_add_widget (g, hline_new (y1++, -1, -1));
782 group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
783 group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
785 find_par_start:
786 widget_select (WIDGET (in_name));
788 switch (dlg_run (find_dlg))
790 case B_CANCEL:
791 return_value = FALSE;
792 break;
794 case B_TREE:
796 const char *start_cstr;
797 const char *temp_dir;
799 start_cstr = input_get_ctext (in_start);
801 if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr))
802 temp_dir = vfs_path_as_str (panel->cwd_vpath);
803 else
804 temp_dir = start_cstr;
806 if (in_start_dir != INPUT_LAST_TEXT)
807 g_free (in_start_dir);
808 in_start_dir = tree_box (temp_dir);
809 if (in_start_dir == NULL)
810 in_start_dir = g_strdup (temp_dir);
812 input_assign_text (in_start, in_start_dir);
814 /* Warning: Dreadful goto */
815 goto find_par_start;
818 default:
820 char *s;
822 #ifdef HAVE_CHARSET
823 options.file_all_charsets = file_all_charsets_cbox->state;
824 options.content_all_charsets = content_all_charsets_cbox->state;
825 #endif
826 options.content_case_sens = content_case_sens_cbox->state;
827 options.content_regexp = content_regexp_cbox->state;
828 options.content_first_hit = content_first_hit_cbox->state;
829 options.content_whole_words = content_whole_words_cbox->state;
830 options.find_recurs = recursively_cbox->state;
831 options.follow_symlinks = follow_sym_cbox->state;
832 options.file_pattern = file_pattern_cbox->state;
833 options.file_case_sens = file_case_sens_cbox->state;
834 options.skip_hidden = skip_hidden_cbox->state;
835 options.ignore_dirs_enable = ignore_dirs_cbox->state;
836 g_free (options.ignore_dirs);
837 options.ignore_dirs = input_get_text (in_ignore);
839 *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL;
840 if (input_is_empty (in_name))
841 *pattern = g_strdup (options.file_pattern ? "*" : ".*");
842 else
843 *pattern = input_get_text (in_name);
844 *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : ".");
845 if (in_start_dir != INPUT_LAST_TEXT)
846 g_free (in_start_dir);
847 in_start_dir = g_strdup (*start_dir);
849 s = tilde_expand (*start_dir);
850 canonicalize_pathname (s);
852 if (DIR_IS_DOT (s))
854 *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath));
855 /* FIXME: is panel->cwd_vpath canonicalized? */
856 /* relative paths will be used in panelization */
857 *start_dir_len = (ssize_t) strlen (*start_dir);
858 g_free (s);
860 else if (g_path_is_absolute (s))
862 *start_dir = s;
863 *start_dir_len = -1;
865 else
867 /* relative paths will be used in panelization */
868 *start_dir =
869 mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL);
870 *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath);
871 g_free (s);
874 if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
875 || DIR_IS_DOT (input_get_ctext (in_ignore)))
876 *ignore_dirs = NULL;
877 else
878 *ignore_dirs = input_get_text (in_ignore);
880 find_save_options ();
882 return_value = TRUE;
886 widget_destroy (WIDGET (find_dlg));
888 return return_value;
891 /* --------------------------------------------------------------------------------------------- */
893 static inline void
894 push_directory (vfs_path_t *dir)
896 g_queue_push_head (&dir_queue, (void *) dir);
899 /* --------------------------------------------------------------------------------------------- */
901 static inline vfs_path_t *
902 pop_directory (void)
904 return (vfs_path_t *) g_queue_pop_head (&dir_queue);
907 /* --------------------------------------------------------------------------------------------- */
909 static void
910 queue_dir_free (gpointer data)
912 vfs_path_free ((vfs_path_t *) data, TRUE);
915 /* --------------------------------------------------------------------------------------------- */
916 /** Remove all the items from the stack */
918 static void
919 clear_stack (void)
921 g_queue_clear_full (&dir_queue, queue_dir_free);
924 /* --------------------------------------------------------------------------------------------- */
926 static void
927 insert_file (const char *dir, const char *file, gsize start, gsize end)
929 char *tmp_name;
930 static char *dirname = NULL;
931 find_match_location_t *location;
933 while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
934 dir++;
936 if (old_dir != NULL)
938 if (strcmp (old_dir, dir) != 0)
940 g_free (old_dir);
941 old_dir = g_strdup (dir);
942 dirname = add_to_list (dir, NULL);
945 else
947 old_dir = g_strdup (dir);
948 dirname = add_to_list (dir, NULL);
951 tmp_name = g_strdup_printf (" %s", file);
952 location = g_malloc (sizeof (*location));
953 location->dir = dirname;
954 location->start = start;
955 location->end = end;
956 add_to_list_take (tmp_name, location);
959 /* --------------------------------------------------------------------------------------------- */
961 static void
962 find_add_match (const char *dir, const char *file, gsize start, gsize end)
964 insert_file (dir, file, start, end);
966 /* Don't scroll */
967 if (matches == 0)
968 listbox_select_first (find_list);
969 widget_draw (WIDGET (find_list));
971 matches++;
972 found_num_update ();
975 /* --------------------------------------------------------------------------------------------- */
977 static FindProgressStatus
978 check_find_events (WDialog *h)
980 Gpm_Event event;
981 int c;
983 event.x = -1;
984 c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE);
985 if (c != EV_NONE)
987 dlg_process_event (h, c, &event);
988 if (h->ret_value == B_ENTER
989 || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
991 /* dialog terminated */
992 return FIND_ABORT;
994 if (!widget_get_state (WIDGET (h), WST_IDLE))
996 /* searching suspended */
997 return FIND_SUSPEND;
1001 return FIND_CONT;
1004 /* --------------------------------------------------------------------------------------------- */
1006 * search_content:
1008 * Search the content_pattern string in the DIRECTORY/FILE.
1009 * It will add the found entries to the find listbox.
1011 * returns FALSE if do_search should look for another file
1012 * TRUE if do_search should exit and proceed to the event handler
1015 static gboolean
1016 search_content (WDialog *h, const char *directory, const char *filename)
1018 struct stat s;
1019 char buffer[BUF_4K] = ""; /* raw input buffer */
1020 int file_fd = -1;
1021 gboolean ret_val = FALSE;
1022 vfs_path_t *vpath;
1023 gint64 tv;
1024 gboolean status_updated = FALSE;
1026 vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
1028 if (mc_stat (vpath, &s) == 0 && S_ISREG (s.st_mode))
1029 file_fd = mc_open (vpath, O_RDONLY);
1031 vfs_path_free (vpath, TRUE);
1033 if (file_fd == -1)
1034 return FALSE;
1036 /* get time elapsed from last refresh */
1037 tv = g_get_monotonic_time ();
1039 if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL)
1041 g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename);
1042 status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8));
1043 mc_refresh ();
1044 last_refresh = tv;
1045 status_updated = TRUE;
1048 tty_enable_interrupt_key ();
1049 tty_got_interrupt ();
1052 int line = 1;
1053 int pos = 0;
1054 int n_read = 0;
1055 off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */
1056 gboolean found = FALSE;
1057 char *strbuf = NULL; /* buffer for fetched string */
1058 int strbuf_size = 0;
1059 int i = -1; /* compensate for a newline we'll add when we first enter the loop */
1061 if (resuming)
1063 /* We've been previously suspended, start from the previous position */
1064 resuming = FALSE;
1065 line = last_line;
1066 pos = last_pos;
1067 off = last_off;
1068 i = last_i;
1071 while (!ret_val)
1073 char ch = '\0';
1074 gsize found_len;
1076 off += i + 1; /* the previous line, plus a newline character */
1077 i = 0;
1079 /* read to buffer and get line from there */
1080 while (TRUE)
1082 if (pos >= n_read)
1084 pos = 0;
1085 n_read = mc_read (file_fd, buffer, sizeof (buffer));
1086 if (n_read <= 0)
1087 break;
1090 ch = buffer[pos++];
1091 if (ch == '\0')
1093 /* skip possible leading zero(s) */
1094 if (i == 0)
1096 off++;
1097 continue;
1099 break;
1102 if (i >= strbuf_size - 1)
1104 strbuf_size += 128;
1105 strbuf = g_realloc (strbuf, strbuf_size);
1108 /* Strip newline */
1109 if (ch == '\n')
1110 break;
1112 strbuf[i++] = ch;
1115 if (i == 0)
1117 if (ch == '\0')
1118 break;
1120 /* if (ch == '\n'): do not search in empty strings */
1121 goto skip_search;
1124 strbuf[i] = '\0';
1126 if (!found /* Search in binary line once */
1127 && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
1129 gsize found_start;
1130 char result[BUF_MEDIUM];
1132 if (!status_updated)
1134 /* if we add results for a file, we have to ensure that
1135 name of this file is shown in status bar */
1136 g_snprintf (result, sizeof (result), _("Grepping in %s"), filename);
1137 status_update (str_trunc (result, WIDGET (h)->rect.cols - 8));
1138 mc_refresh ();
1139 last_refresh = tv;
1140 status_updated = TRUE;
1143 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
1144 found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */
1145 find_add_match (directory, result, found_start, found_start + found_len);
1146 found = TRUE;
1149 if (found && options.content_first_hit)
1150 break;
1152 if (ch == '\n')
1154 skip_search:
1155 found = FALSE;
1156 line++;
1159 if ((line & 0xff) == 0)
1161 FindProgressStatus res;
1163 res = check_find_events (h);
1164 switch (res)
1166 case FIND_ABORT:
1167 stop_idle (h);
1168 ret_val = TRUE;
1169 break;
1170 case FIND_SUSPEND:
1171 resuming = TRUE;
1172 last_line = line;
1173 last_pos = pos;
1174 last_off = off;
1175 last_i = i;
1176 ret_val = TRUE;
1177 break;
1178 default:
1179 break;
1184 g_free (strbuf);
1187 tty_disable_interrupt_key ();
1188 mc_close (file_fd);
1189 return ret_val;
1192 /* --------------------------------------------------------------------------------------------- */
1195 If dir is absolute, this means we're within dir and searching file here.
1196 If dir is relative, this means we're going to add dir to the directory stack.
1198 static gboolean
1199 find_ignore_dir_search (const char *dir, size_t len)
1201 if (find_ignore_dirs != NULL)
1203 const size_t dlen = len == (size_t) (-1) ? strlen (dir) : len;
1204 const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
1206 char **ignore_dir;
1208 for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
1210 const size_t ilen = strlen (*ignore_dir);
1211 const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
1213 /* ignore dir is too long -- skip it */
1214 if (dlen < ilen)
1215 continue;
1217 /* handle absolute and relative paths */
1218 switch (iabs | dabs)
1220 case 0: /* both paths are relative */
1221 case 3: /* both paths are absolute */
1222 /* if ignore dir is not a path of dir -- skip it */
1223 if (strncmp (dir, *ignore_dir, ilen) == 0)
1225 /* be sure that ignore dir is not a part of dir like:
1226 ignore dir is "h", dir is "home" */
1227 if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
1228 return TRUE;
1230 break;
1231 case 1: /* dir is absolute, ignore_dir is relative */
1233 char *d;
1235 d = strstr (dir, *ignore_dir);
1236 if (d != NULL && IS_PATH_SEP (d[-1])
1237 && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1238 return TRUE;
1240 break;
1241 case 2: /* dir is relative, ignore_dir is absolute */
1242 /* FIXME: skip this case */
1243 break;
1244 default: /* this cannot occurs */
1245 return FALSE;
1250 return FALSE;
1253 /* --------------------------------------------------------------------------------------------- */
1255 static void
1256 find_rotate_dash (const WDialog *h, gboolean show)
1258 static size_t pos = 0;
1259 static const char rotating_dash[4] = "|/-\\";
1260 const Widget *w = CONST_WIDGET (h);
1261 const int *colors;
1263 colors = widget_get_colors (w);
1264 tty_setcolor (colors[DLG_COLOR_NORMAL]);
1265 widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
1266 tty_print_char (show ? rotating_dash[pos] : ' ');
1267 pos = (pos + 1) % sizeof (rotating_dash);
1268 mc_refresh ();
1271 /* --------------------------------------------------------------------------------------------- */
1273 static int
1274 do_search (WDialog *h)
1276 static struct vfs_dirent *dp = NULL;
1277 static DIR *dirp = NULL;
1278 static char *directory = NULL;
1279 static gboolean pop_start_dir = TRUE;
1280 struct stat tmp_stat;
1281 gsize bytes_found;
1282 unsigned short count;
1284 if (h == NULL)
1285 { /* someone forces me to close dirp */
1286 if (dirp != NULL)
1288 mc_closedir (dirp);
1289 dirp = NULL;
1291 MC_PTR_FREE (directory);
1292 dp = NULL;
1293 pop_start_dir = TRUE;
1294 return 1;
1297 for (count = 0; count < 32; count++)
1299 while (dp == NULL)
1301 if (dirp != NULL)
1303 mc_closedir (dirp);
1304 dirp = NULL;
1307 while (dirp == NULL)
1309 vfs_path_t *tmp_vpath = NULL;
1311 tty_setcolor (REVERSE_COLOR);
1313 while (TRUE)
1315 tmp_vpath = pop_directory ();
1316 if (tmp_vpath == NULL)
1318 running = FALSE;
1319 if (ignore_count == 0)
1320 status_update (_("Finished"));
1321 else
1323 char msg[BUF_SMALL];
1325 g_snprintf (msg, sizeof (msg),
1326 ngettext ("Finished (ignored %zu directory)",
1327 "Finished (ignored %zu directories)",
1328 ignore_count), ignore_count);
1329 status_update (msg);
1331 if (verbose)
1332 find_rotate_dash (h, FALSE);
1333 stop_idle (h);
1334 return 0;
1337 /* The start directory is the first one in the stack (see do_find() below).
1338 Do not apply ignore_dir to it. */
1339 if (pop_start_dir)
1341 pop_start_dir = FALSE;
1342 break;
1345 pop_start_dir = FALSE;
1347 /* handle absolute ignore dirs here */
1348 if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath), -1))
1349 break;
1351 vfs_path_free (tmp_vpath, TRUE);
1352 ignore_count++;
1355 g_free (directory);
1357 if (verbose)
1359 char buffer[BUF_MEDIUM];
1361 directory = (char *) vfs_path_as_str (tmp_vpath);
1362 g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory);
1363 status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
1366 dirp = mc_opendir (tmp_vpath);
1367 directory = vfs_path_free (tmp_vpath, FALSE);
1368 } /* while (!dirp) */
1370 /* skip invalid filenames */
1371 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1373 } /* while (!dp) */
1375 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1377 /* skip invalid filenames */
1378 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1381 return 1;
1384 if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1386 gboolean search_ok;
1388 if (options.find_recurs && (directory != NULL))
1389 { /* Can directory be NULL ? */
1390 /* handle relative ignore dirs here */
1391 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name, dp->d_len))
1392 ignore_count++;
1393 else
1395 vfs_path_t *tmp_vpath;
1396 int stat_res;
1398 tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1400 if (options.follow_symlinks)
1401 stat_res = mc_stat (tmp_vpath, &tmp_stat);
1402 else
1403 stat_res = mc_lstat (tmp_vpath, &tmp_stat);
1405 if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
1406 push_directory (tmp_vpath);
1407 else
1408 vfs_path_free (tmp_vpath, TRUE);
1412 search_ok = mc_search_run (search_file_handle, dp->d_name, 0, dp->d_len, &bytes_found);
1414 if (search_ok)
1416 if (content_pattern == NULL)
1417 find_add_match (directory, dp->d_name, 0, 0);
1418 else if (search_content (h, directory, dp->d_name))
1419 return 1;
1423 /* skip invalid filenames */
1424 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1426 } /* for */
1428 if (verbose)
1429 find_rotate_dash (h, TRUE);
1431 return 1;
1434 /* --------------------------------------------------------------------------------------------- */
1436 static void
1437 init_find_vars (void)
1439 MC_PTR_FREE (old_dir);
1440 matches = 0;
1441 ignore_count = 0;
1443 /* Remove all the items from the stack */
1444 clear_stack ();
1446 g_strfreev (find_ignore_dirs);
1447 find_ignore_dirs = NULL;
1450 /* --------------------------------------------------------------------------------------------- */
1452 static void
1453 find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
1454 off_t search_end)
1456 const char *filename = NULL;
1457 int line;
1458 vfs_path_t *fullname_vpath;
1460 if (content_pattern != NULL)
1462 filename = strchr (file + 4, ':') + 1;
1463 line = atoi (file + 4);
1465 else
1467 filename = file + 4;
1468 line = 0;
1471 fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1472 if (edit)
1473 edit_file_at_line (fullname_vpath, use_internal_edit, line);
1474 else
1475 view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1476 search_end);
1477 vfs_path_free (fullname_vpath, TRUE);
1480 /* --------------------------------------------------------------------------------------------- */
1482 static cb_ret_t
1483 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
1485 char *text = NULL;
1486 find_match_location_t *location;
1488 listbox_get_current (find_list, &text, (void **) &location);
1490 if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1491 return MSG_NOT_HANDLED;
1493 find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1494 return MSG_HANDLED;
1497 /* --------------------------------------------------------------------------------------------- */
1499 static void
1500 find_calc_button_locations (const WDialog *h, gboolean all_buttons)
1502 const int cols = CONST_WIDGET (h)->rect.cols;
1504 int l1, l2;
1506 l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1507 l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1509 fbuts[0].x = (cols - l1) / 2;
1510 fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1511 fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1512 fbuts[3].x = fbuts[2].x;
1513 fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1515 if (all_buttons)
1517 fbuts[5].x = (cols - l2) / 2;
1518 fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1519 fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1523 /* --------------------------------------------------------------------------------------------- */
1525 static void
1526 find_adjust_header (WDialog *h)
1528 char title[BUF_MEDIUM];
1529 int title_len;
1531 if (content_pattern != NULL)
1532 g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern,
1533 content_pattern);
1534 else
1535 g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern);
1537 title_len = str_term_width1 (title);
1538 if (title_len > WIDGET (h)->rect.cols - 6)
1540 /* title is too wide, truncate it */
1541 title_len = WIDGET (h)->rect.cols - 6;
1542 title_len = str_column_to_pos (title, title_len);
1543 title_len -= 3; /* reserve space for three dots */
1544 title_len = str_offset_to_pos (title, title_len);
1545 /* mark that title is truncated */
1546 memmove (title + title_len, "...", 4);
1549 frame_set_title (FRAME (h->bg), title);
1552 /* --------------------------------------------------------------------------------------------- */
1554 static void
1555 find_relocate_buttons (const WDialog *h, gboolean all_buttons)
1557 size_t i;
1559 find_calc_button_locations (h, all_buttons);
1561 for (i = 0; i < fbuts_num; i++)
1562 fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
1565 /* --------------------------------------------------------------------------------------------- */
1567 static cb_ret_t
1568 find_resize (WDialog *h)
1570 Widget *w = WIDGET (h);
1571 WRect r = w->rect;
1573 r.lines = LINES - 4;
1574 r.cols = COLS - 16;
1575 dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
1576 find_adjust_header (h);
1577 find_relocate_buttons (h, TRUE);
1579 return MSG_HANDLED;
1582 /* --------------------------------------------------------------------------------------------- */
1584 static cb_ret_t
1585 find_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
1587 WDialog *h = DIALOG (w);
1589 switch (msg)
1591 case MSG_INIT:
1592 group_default_callback (w, NULL, MSG_INIT, 0, NULL);
1593 find_adjust_header (h);
1594 return MSG_HANDLED;
1596 case MSG_KEY:
1597 if (parm == KEY_F (3) || parm == KEY_F (13))
1599 gboolean unparsed_view = (parm == KEY_F (13));
1601 return view_edit_currently_selected_file (unparsed_view, FALSE);
1603 if (parm == KEY_F (4))
1604 return view_edit_currently_selected_file (FALSE, TRUE);
1605 return MSG_NOT_HANDLED;
1607 case MSG_RESIZE:
1608 return find_resize (h);
1610 case MSG_IDLE:
1611 do_search (h);
1612 return MSG_HANDLED;
1614 default:
1615 return dlg_default_callback (w, sender, msg, parm, data);
1619 /* --------------------------------------------------------------------------------------------- */
1620 /** Handles the Stop/Start button in the find window */
1622 static int
1623 start_stop (WButton *button, int action)
1625 Widget *w = WIDGET (button);
1627 (void) action;
1629 running = is_start;
1630 widget_idle (WIDGET (find_dlg), running);
1631 is_start = !is_start;
1633 status_update (is_start ? _("Stopped") : _("Searching"));
1634 button_set_text (button, fbuts[is_start ? 3 : 2].text);
1636 find_relocate_buttons (DIALOG (w->owner), FALSE);
1637 widget_draw (WIDGET (w->owner));
1639 return 0;
1642 /* --------------------------------------------------------------------------------------------- */
1643 /** Handle view command, when invoked as a button */
1645 static int
1646 find_do_view_file (WButton *button, int action)
1648 (void) button;
1649 (void) action;
1651 view_edit_currently_selected_file (FALSE, FALSE);
1652 return 0;
1655 /* --------------------------------------------------------------------------------------------- */
1656 /** Handle edit command, when invoked as a button */
1658 static int
1659 find_do_edit_file (WButton *button, int action)
1661 (void) button;
1662 (void) action;
1664 view_edit_currently_selected_file (FALSE, TRUE);
1665 return 0;
1668 /* --------------------------------------------------------------------------------------------- */
1670 static void
1671 setup_gui (void)
1673 WGroup *g;
1674 size_t i;
1675 int lines, cols;
1676 int y;
1678 static gboolean i18n_flag = FALSE;
1680 if (!i18n_flag)
1682 for (i = 0; i < fbuts_num; i++)
1684 #ifdef ENABLE_NLS
1685 fbuts[i].text = _(fbuts[i].text);
1686 #endif /* ENABLE_NLS */
1687 fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1688 if (fbuts[i].flags == DEFPUSH_BUTTON)
1689 fbuts[i].len += 2;
1692 i18n_flag = TRUE;
1695 lines = LINES - 4;
1696 cols = COLS - 16;
1698 find_dlg =
1699 dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL,
1700 "[Find File]", NULL);
1701 g = GROUP (find_dlg);
1703 find_calc_button_locations (find_dlg, TRUE);
1705 y = 2;
1706 find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1707 group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
1708 y += WIDGET (find_list)->rect.lines;
1710 group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1712 found_num_label = label_new (y++, 4, NULL);
1713 group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1715 status_label = label_new (y++, 4, _("Searching"));
1716 group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
1718 group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1720 for (i = 0; i < fbuts_num; i++)
1722 if (i == 3)
1723 fbuts[3].button = fbuts[2].button;
1724 else
1726 fbuts[i].button =
1727 WIDGET (button_new
1728 (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text,
1729 fbuts[i].callback));
1730 group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1733 if (i == quit_button)
1734 y++;
1737 widget_select (WIDGET (find_list));
1740 /* --------------------------------------------------------------------------------------------- */
1742 static int
1743 run_process (void)
1745 int ret;
1747 search_content_handle = mc_search_new (content_pattern, NULL);
1748 if (search_content_handle)
1750 search_content_handle->search_type =
1751 options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1752 search_content_handle->is_case_sensitive = options.content_case_sens;
1753 search_content_handle->whole_words = options.content_whole_words;
1754 #ifdef HAVE_CHARSET
1755 search_content_handle->is_all_charsets = options.content_all_charsets;
1756 #endif
1758 search_file_handle = mc_search_new (find_pattern, NULL);
1759 search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1760 search_file_handle->is_case_sensitive = options.file_case_sens;
1761 #ifdef HAVE_CHARSET
1762 search_file_handle->is_all_charsets = options.file_all_charsets;
1763 #endif
1764 search_file_handle->is_entire_line = options.file_pattern;
1766 resuming = FALSE;
1768 widget_idle (WIDGET (find_dlg), TRUE);
1769 ret = dlg_run (find_dlg);
1771 mc_search_free (search_file_handle);
1772 search_file_handle = NULL;
1773 mc_search_free (search_content_handle);
1774 search_content_handle = NULL;
1776 return ret;
1779 /* --------------------------------------------------------------------------------------------- */
1781 static void
1782 kill_gui (void)
1784 Widget *w = WIDGET (find_dlg);
1786 widget_idle (w, FALSE);
1787 widget_destroy (w);
1790 /* --------------------------------------------------------------------------------------------- */
1792 static int
1793 do_find (WPanel *panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
1794 char **dirname, char **filename)
1796 int return_value;
1797 char *dir_tmp = NULL, *file_tmp = NULL;
1799 setup_gui ();
1801 init_find_vars ();
1802 parse_ignore_dirs (ignore_dirs);
1803 push_directory (vfs_path_from_str (start_dir));
1805 return_value = run_process ();
1807 /* Clear variables */
1808 init_find_vars ();
1810 get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1812 if (dir_tmp != NULL)
1813 *dirname = g_strdup (dir_tmp);
1814 if (file_tmp != NULL)
1815 *filename = g_strdup (file_tmp);
1817 if (return_value == B_PANELIZE && *filename != NULL)
1819 struct stat st;
1820 GList *entry;
1821 dir_list *list = &panel->dir;
1822 char *name = NULL;
1823 gboolean ok = TRUE;
1825 panel_clean_dir (panel);
1826 dir_list_init (list);
1828 for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
1829 entry = g_list_next (entry))
1831 const char *lc_filename;
1832 WLEntry *le = LENTRY (entry->data);
1833 find_match_location_t *location = le->data;
1834 char *p;
1835 gboolean link_to_dir, stale_link;
1837 if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1838 continue;
1840 if (!content_is_empty)
1841 lc_filename = strchr (le->text + 4, ':') + 1;
1842 else
1843 lc_filename = le->text + 4;
1845 name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1846 /* skip initial start dir */
1847 p = name;
1848 if (start_dir_len > 0)
1849 p += (size_t) start_dir_len;
1850 if (IS_PATH_SEP (*p))
1851 p++;
1853 if (!handle_path (p, &st, &link_to_dir, &stale_link))
1855 g_free (name);
1856 continue;
1859 /* don't add files more than once to the panel */
1860 if (!content_is_empty && list->len != 0
1861 && strcmp (list->list[list->len - 1].fname->str, p) == 0)
1863 g_free (name);
1864 continue;
1867 ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
1869 g_free (name);
1871 if ((list->len & 15) == 0)
1872 rotate_dash (TRUE);
1875 panel->is_panelized = TRUE;
1876 panel_panelize_absolutize_if_needed (panel);
1877 panel_panelize_save (panel);
1880 kill_gui ();
1881 do_search (NULL); /* force do_search to release resources */
1882 MC_PTR_FREE (old_dir);
1883 rotate_dash (FALSE);
1885 return return_value;
1888 /* --------------------------------------------------------------------------------------------- */
1889 /*** public functions ****************************************************************************/
1890 /* --------------------------------------------------------------------------------------------- */
1892 void
1893 find_cmd (WPanel *panel)
1895 char *start_dir = NULL, *ignore_dirs = NULL;
1896 ssize_t start_dir_len;
1898 find_pattern = NULL;
1899 content_pattern = NULL;
1901 while (find_parameters (panel, &start_dir, &start_dir_len,
1902 &ignore_dirs, &find_pattern, &content_pattern))
1904 char *filename = NULL, *dirname = NULL;
1905 int v = B_CANCEL;
1907 content_is_empty = content_pattern == NULL;
1909 if (find_pattern[0] != '\0')
1911 last_refresh = 0;
1913 is_start = FALSE;
1915 if (!content_is_empty && !str_is_valid_string (content_pattern))
1916 MC_PTR_FREE (content_pattern);
1918 v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1921 g_free (start_dir);
1922 g_free (ignore_dirs);
1923 MC_PTR_FREE (find_pattern);
1925 if (v == B_ENTER)
1927 if (dirname != NULL)
1929 vfs_path_t *dirname_vpath;
1931 dirname_vpath = vfs_path_from_str (dirname);
1932 panel_cd (panel, dirname_vpath, cd_exact);
1933 vfs_path_free (dirname_vpath, TRUE);
1935 if (filename != NULL)
1937 size_t offset;
1939 if (content_pattern == NULL)
1940 offset = 4;
1941 else
1942 offset = strchr (filename + 4, ':') - filename + 1;
1944 panel_set_current_by_name (panel, filename + offset);
1947 else if (filename != NULL)
1949 vfs_path_t *filename_vpath;
1951 filename_vpath = vfs_path_from_str (filename);
1952 panel_cd (panel, filename_vpath, cd_exact);
1953 vfs_path_free (filename_vpath, TRUE);
1957 MC_PTR_FREE (content_pattern);
1958 g_free (dirname);
1959 g_free (filename);
1961 if (v == B_ENTER || v == B_CANCEL)
1962 break;
1964 if (v == B_PANELIZE)
1966 panel_re_sort (panel);
1967 panel_set_current_by_name (panel, NULL);
1968 break;
1973 /* --------------------------------------------------------------------------------------------- */