Move widget add/del API from WDialog to WGroup.
[midnight-commander.git] / src / filemanager / find.c
blob2c162acd35eb27a39dd5b9ae08001bf87a2f2875
1 /*
2 Find file command for the Midnight Commander
4 Copyright (C) 1995-2020
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
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>
39 #include <sys/time.h>
41 #include "lib/global.h"
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() */
53 #include "src/setup.h" /* verbose */
54 #include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
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"
62 #include "find.h"
64 /*** global variables ****************************************************************************/
66 /*** file scope macro definitions ****************************************************************/
68 #define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */
69 #define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */
71 /*** file scope type declarations ****************************************************************/
73 /* A couple of extra messages we need */
74 enum
76 B_STOP = B_USER + 1,
77 B_AGAIN,
78 B_PANELIZE,
79 B_TREE,
80 B_VIEW
83 typedef enum
85 FIND_CONT = 0,
86 FIND_SUSPEND,
87 FIND_ABORT
88 } FindProgressStatus;
90 /* find file options */
91 typedef struct
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;
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;
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;
113 typedef struct
115 char *dir;
116 gsize start;
117 gsize end;
118 } find_match_location_t;
120 /*** file scope variables ************************************************************************/
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);
127 /* Parsed ignore dirs */
128 static char **find_ignore_dirs = NULL;
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;
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 struct timeval 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,
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 caclulation */
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.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", "");
312 if (options.ignore_dirs[0] == '\0')
313 MC_PTR_FREE (options.ignore_dirs);
316 /* --------------------------------------------------------------------------------------------- */
318 static void
319 find_save_options (void)
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);
344 /* --------------------------------------------------------------------------------------------- */
346 static inline char *
347 add_to_list (const char *text, void *data)
349 return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
352 /* --------------------------------------------------------------------------------------------- */
354 static inline void
355 stop_idle (void *data)
357 widget_idle (WIDGET (data), FALSE);
360 /* --------------------------------------------------------------------------------------------- */
362 static inline void
363 status_update (const char *text)
365 label_set_text (status_label, text);
368 /* --------------------------------------------------------------------------------------------- */
370 static void
371 found_num_update (void)
373 char buffer[BUF_TINY];
375 g_snprintf (buffer, sizeof (buffer), _("Found: %lu"), matches);
376 label_set_text (found_num_label, buffer);
379 /* --------------------------------------------------------------------------------------------- */
381 static void
382 get_list_info (char **file, char **dir, gsize * start, gsize * end)
384 find_match_location_t *location;
386 listbox_get_current (find_list, file, (void **) &location);
387 if (location != NULL)
389 if (dir != NULL)
390 *dir = location->dir;
391 if (start != NULL)
392 *start = location->start;
393 if (end != NULL)
394 *end = location->end;
396 else
398 if (dir != NULL)
399 *dir = NULL;
403 /* --------------------------------------------------------------------------------------------- */
404 /** check regular expression */
406 static gboolean
407 find_check_regexp (const char *r)
409 mc_search_t *search;
410 gboolean regexp_ok = FALSE;
412 search = mc_search_new (r, NULL);
414 if (search != NULL)
416 search->search_type = MC_SEARCH_T_REGEX;
417 regexp_ok = mc_search_prepare (search);
418 mc_search_free (search);
421 return regexp_ok;
424 /* --------------------------------------------------------------------------------------------- */
426 static void
427 find_toggle_enable_ignore_dirs (void)
429 widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
432 /* --------------------------------------------------------------------------------------------- */
434 static void
435 find_toggle_enable_params (void)
437 gboolean disable = input_is_empty (in_name);
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
446 /* --------------------------------------------------------------------------------------------- */
448 static void
449 find_toggle_enable_content (void)
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);
460 /* --------------------------------------------------------------------------------------------- */
462 * Callback for the parameter dialog.
463 * Validate regex, prevent closing the dialog if it's invalid.
466 static cb_ret_t
467 find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
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.
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.
479 static gboolean first_draw = TRUE;
481 WDialog *h = DIALOG (w);
483 switch (msg)
485 case MSG_INIT:
486 first_draw = TRUE;
487 return MSG_HANDLED;
489 case MSG_NOTIFY:
490 if (sender == WIDGET (ignore_dirs_cbox))
492 find_toggle_enable_ignore_dirs ();
493 return MSG_HANDLED;
496 return MSG_NOT_HANDLED;
498 case MSG_VALIDATE:
499 if (h->ret_value != B_ENTER)
500 return MSG_HANDLED;
502 /* check filename regexp */
503 if (!file_pattern_cbox->state && !input_is_empty (in_name)
504 && !find_check_regexp (in_name->buffer))
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;
513 /* check content regexp */
514 if (content_regexp_cbox->state && !content_is_empty && !find_check_regexp (in_with->buffer))
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;
523 return MSG_HANDLED;
525 case MSG_POST_KEY:
526 if (GROUP (h)->current->data == in_name)
527 find_toggle_enable_params ();
528 else if (GROUP (h)->current->data == in_with)
530 content_is_empty = input_is_empty (in_with);
531 find_toggle_enable_content ();
533 return MSG_HANDLED;
535 case MSG_DRAW:
536 if (first_draw)
538 find_toggle_enable_ignore_dirs ();
539 find_toggle_enable_params ();
540 find_toggle_enable_content ();
543 first_draw = FALSE;
544 MC_FALLTHROUGH; /* to call MSG_DRAW default handler */
546 default:
547 return dlg_default_callback (w, sender, msg, parm, data);
551 /* --------------------------------------------------------------------------------------------- */
553 * find_parameters: gets information from the user
555 * If the return value is TRUE, then the following holds:
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.
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.
564 static gboolean
565 find_parameters (char **start_dir, ssize_t * start_dir_len,
566 char **ignore_dirs, char **pattern, char **content)
568 WGroup *g;
570 /* Size of the find parameters window */
571 #ifdef HAVE_CHARSET
572 const int lines = 18;
573 #else
574 const int lines = 17;
575 #endif
576 int cols = 68;
578 gboolean return_value;
580 /* file name */
581 const char *file_name_label = N_("File name:");
582 const char *file_recurs_label = N_("&Find recursively");
583 const char *file_pattern_label = N_("&Using shell patterns");
584 #ifdef HAVE_CHARSET
585 const char *file_all_charsets_label = N_("&All charsets");
586 #endif
587 const char *file_case_label = N_("Cas&e sensitive");
588 const char *file_skip_hidden_label = N_("S&kip hidden");
590 /* file content */
591 const char *content_content_label = N_("Content:");
592 const char *content_use_label = N_("Sea&rch for content");
593 const char *content_regexp_label = N_("Re&gular expression");
594 const char *content_case_label = N_("Case sens&itive");
595 #ifdef HAVE_CHARSET
596 const char *content_all_charsets_label = N_("A&ll charsets");
597 #endif
598 const char *content_whole_words_label = N_("&Whole words");
599 const char *content_first_hit_label = N_("Fir&st hit");
601 const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") };
603 /* button lengths */
604 int b0, b1, b2, b12;
605 int y1, y2, x1, x2;
606 /* column width */
607 int cw;
609 #ifdef ENABLE_NLS
611 size_t i;
613 file_name_label = _(file_name_label);
614 file_recurs_label = _(file_recurs_label);
615 file_pattern_label = _(file_pattern_label);
616 #ifdef HAVE_CHARSET
617 file_all_charsets_label = _(file_all_charsets_label);
618 #endif
619 file_case_label = _(file_case_label);
620 file_skip_hidden_label = _(file_skip_hidden_label);
622 /* file content */
623 content_content_label = _(content_content_label);
624 content_use_label = _(content_use_label);
625 content_regexp_label = _(content_regexp_label);
626 content_case_label = _(content_case_label);
627 #ifdef HAVE_CHARSET
628 content_all_charsets_label = _(content_all_charsets_label);
629 #endif
630 content_whole_words_label = _(content_whole_words_label);
631 content_first_hit_label = _(content_first_hit_label);
633 for (i = 0; i < G_N_ELEMENTS (buts); i++)
634 buts[i] = _(buts[i]);
636 #endif /* ENABLE_NLS */
638 /* caclulate dialog width */
640 /* widget widths */
641 cw = str_term_width1 (file_name_label);
642 cw = max (cw, str_term_width1 (file_recurs_label) + 4);
643 cw = max (cw, str_term_width1 (file_pattern_label) + 4);
644 #ifdef HAVE_CHARSET
645 cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
646 #endif
647 cw = max (cw, str_term_width1 (file_case_label) + 4);
648 cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
650 cw = max (cw, str_term_width1 (content_content_label) + 4);
651 cw = max (cw, str_term_width1 (content_use_label) + 4);
652 cw = max (cw, str_term_width1 (content_regexp_label) + 4);
653 cw = max (cw, str_term_width1 (content_case_label) + 4);
654 #ifdef HAVE_CHARSET
655 cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
656 #endif
657 cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
658 cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
660 /* button width */
661 b0 = str_term_width1 (buts[0]) + 3;
662 b1 = str_term_width1 (buts[1]) + 5; /* default button */
663 b2 = str_term_width1 (buts[2]) + 3;
664 b12 = b1 + b2 + 1;
666 cols = max (cols, max (b12, cw * 2 + 1) + 6);
668 find_load_options ();
670 if (in_start_dir == NULL)
671 in_start_dir = g_strdup (".");
673 find_dlg =
674 dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback,
675 NULL, "[Find File]", _("Find File"));
676 g = GROUP (find_dlg);
678 x1 = 3;
679 x2 = cols / 2 + 1;
680 cw = (cols - 7) / 2;
681 y1 = 2;
683 group_add_widget (g, label_new (y1++, x1, _("Start at:")));
684 in_start =
685 input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
686 INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
687 group_add_widget (g, in_start);
689 group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
691 ignore_dirs_cbox =
692 check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:"));
693 group_add_widget (g, ignore_dirs_cbox);
695 in_ignore =
696 input_new (y1++, x1, input_colors, cols - 6,
697 options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
698 INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
699 group_add_widget (g, in_ignore);
701 group_add_widget (g, hline_new (y1++, -1, -1));
703 y2 = y1;
705 /* Start 1st column */
706 group_add_widget (g, label_new (y1++, x1, file_name_label));
707 in_name =
708 input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
709 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
710 group_add_widget (g, in_name);
712 /* Start 2nd column */
713 content_label = label_new (y2++, x2, content_content_label);
714 group_add_widget (g, content_label);
715 in_with =
716 input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
717 MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
718 in_with->label = content_label;
719 group_add_widget (g, in_with);
721 /* Continue 1st column */
722 recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
723 group_add_widget (g, recursively_cbox);
725 file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
726 group_add_widget (g, file_pattern_cbox);
728 file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
729 group_add_widget (g, file_case_sens_cbox);
731 #ifdef HAVE_CHARSET
732 file_all_charsets_cbox =
733 check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
734 group_add_widget (g, file_all_charsets_cbox);
735 #endif
737 skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
738 group_add_widget (g, skip_hidden_cbox);
740 /* Continue 2nd column */
741 content_whole_words_cbox =
742 check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
743 group_add_widget (g, content_whole_words_cbox);
745 content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
746 group_add_widget (g, content_regexp_cbox);
748 content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
749 group_add_widget (g, content_case_sens_cbox);
751 #ifdef HAVE_CHARSET
752 content_all_charsets_cbox =
753 check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
754 group_add_widget (g, content_all_charsets_cbox);
755 #endif
757 content_first_hit_cbox =
758 check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
759 group_add_widget (g, content_first_hit_cbox);
761 /* buttons */
762 y1 = max (y1, y2);
763 x1 = (cols - b12) / 2;
764 group_add_widget (g, hline_new (y1++, -1, -1));
765 group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
766 group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
768 find_par_start:
769 widget_select (WIDGET (in_name));
771 switch (dlg_run (find_dlg))
773 case B_CANCEL:
774 return_value = FALSE;
775 break;
777 case B_TREE:
779 char *temp_dir;
781 temp_dir = in_start->buffer;
782 if (*temp_dir == '\0' || DIR_IS_DOT (temp_dir))
783 temp_dir = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
784 else
785 temp_dir = g_strdup (temp_dir);
787 if (in_start_dir != INPUT_LAST_TEXT)
788 g_free (in_start_dir);
789 in_start_dir = tree_box (temp_dir);
790 if (in_start_dir == NULL)
791 in_start_dir = temp_dir;
792 else
793 g_free (temp_dir);
795 input_assign_text (in_start, in_start_dir);
797 /* Warning: Dreadful goto */
798 goto find_par_start;
801 default:
803 char *s;
805 #ifdef HAVE_CHARSET
806 options.file_all_charsets = file_all_charsets_cbox->state;
807 options.content_all_charsets = content_all_charsets_cbox->state;
808 #endif
809 options.content_case_sens = content_case_sens_cbox->state;
810 options.content_regexp = content_regexp_cbox->state;
811 options.content_first_hit = content_first_hit_cbox->state;
812 options.content_whole_words = content_whole_words_cbox->state;
813 options.find_recurs = recursively_cbox->state;
814 options.file_pattern = file_pattern_cbox->state;
815 options.file_case_sens = file_case_sens_cbox->state;
816 options.skip_hidden = skip_hidden_cbox->state;
817 options.ignore_dirs_enable = ignore_dirs_cbox->state;
818 g_free (options.ignore_dirs);
819 options.ignore_dirs = g_strdup (in_ignore->buffer);
821 *content = !input_is_empty (in_with) ? g_strdup (in_with->buffer) : NULL;
822 if (!input_is_empty (in_name))
823 *pattern = g_strdup (in_name->buffer);
824 else
825 *pattern = g_strdup (options.file_pattern ? "*" : ".*");
826 *start_dir = !input_is_empty (in_start) ? in_start->buffer : (char *) ".";
827 if (in_start_dir != INPUT_LAST_TEXT)
828 g_free (in_start_dir);
829 in_start_dir = g_strdup (*start_dir);
831 s = tilde_expand (*start_dir);
832 canonicalize_pathname (s);
834 if (DIR_IS_DOT (s))
836 *start_dir = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
837 /* FIXME: is current_panel->cwd_vpath canonicalized? */
838 /* relative paths will be used in panelization */
839 *start_dir_len = (ssize_t) strlen (*start_dir);
840 g_free (s);
842 else if (g_path_is_absolute (s))
844 *start_dir = s;
845 *start_dir_len = -1;
847 else
849 /* relative paths will be used in panelization */
850 *start_dir =
851 mc_build_filename (vfs_path_as_str (current_panel->cwd_vpath), s,
852 (char *) NULL);
853 *start_dir_len = (ssize_t) strlen (vfs_path_as_str (current_panel->cwd_vpath));
854 g_free (s);
857 if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
858 || DIR_IS_DOT (in_ignore->buffer))
859 *ignore_dirs = NULL;
860 else
861 *ignore_dirs = g_strdup (in_ignore->buffer);
863 find_save_options ();
865 return_value = TRUE;
869 dlg_destroy (find_dlg);
871 return return_value;
874 /* --------------------------------------------------------------------------------------------- */
876 static inline void
877 push_directory (vfs_path_t * dir)
879 g_queue_push_head (&dir_queue, (void *) dir);
882 /* --------------------------------------------------------------------------------------------- */
884 static inline vfs_path_t *
885 pop_directory (void)
887 return (vfs_path_t *) g_queue_pop_head (&dir_queue);
890 /* --------------------------------------------------------------------------------------------- */
891 /** Remove all the items from the stack */
893 static void
894 clear_stack (void)
896 g_queue_clear_full (&dir_queue, (GDestroyNotify) vfs_path_free);
899 /* --------------------------------------------------------------------------------------------- */
901 static void
902 insert_file (const char *dir, const char *file, gsize start, gsize end)
904 char *tmp_name = NULL;
905 static char *dirname = NULL;
906 find_match_location_t *location;
908 while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
909 dir++;
911 if (old_dir != NULL)
913 if (strcmp (old_dir, dir) != 0)
915 g_free (old_dir);
916 old_dir = g_strdup (dir);
917 dirname = add_to_list (dir, NULL);
920 else
922 old_dir = g_strdup (dir);
923 dirname = add_to_list (dir, NULL);
926 tmp_name = g_strdup_printf (" %s", file);
927 location = g_malloc (sizeof (*location));
928 location->dir = dirname;
929 location->start = start;
930 location->end = end;
931 add_to_list (tmp_name, location);
932 g_free (tmp_name);
935 /* --------------------------------------------------------------------------------------------- */
937 static void
938 find_add_match (const char *dir, const char *file, gsize start, gsize end)
940 insert_file (dir, file, start, end);
942 /* Don't scroll */
943 if (matches == 0)
944 listbox_select_first (find_list);
945 widget_draw (WIDGET (find_list));
947 matches++;
948 found_num_update ();
951 /* --------------------------------------------------------------------------------------------- */
953 static FindProgressStatus
954 check_find_events (WDialog * h)
956 Gpm_Event event;
957 int c;
959 event.x = -1;
960 c = tty_get_event (&event, h->mouse_status == MOU_REPEAT, FALSE);
961 if (c != EV_NONE)
963 dlg_process_event (h, c, &event);
964 if (h->ret_value == B_ENTER
965 || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
967 /* dialog terminated */
968 return FIND_ABORT;
970 if (!widget_get_state (WIDGET (h), WST_IDLE))
972 /* searching suspended */
973 return FIND_SUSPEND;
977 return FIND_CONT;
980 /* --------------------------------------------------------------------------------------------- */
982 * search_content:
984 * Search the content_pattern string in the DIRECTORY/FILE.
985 * It will add the found entries to the find listbox.
987 * returns FALSE if do_search should look for another file
988 * TRUE if do_search should exit and proceed to the event handler
991 static gboolean
992 search_content (WDialog * h, const char *directory, const char *filename)
994 struct stat s;
995 char buffer[BUF_4K]; /* raw input buffer */
996 int file_fd;
997 gboolean ret_val = FALSE;
998 vfs_path_t *vpath;
999 struct timeval tv;
1000 time_t seconds;
1001 suseconds_t useconds;
1002 gboolean status_updated = FALSE;
1004 vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
1006 if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode))
1008 vfs_path_free (vpath);
1009 return FALSE;
1012 file_fd = mc_open (vpath, O_RDONLY);
1013 vfs_path_free (vpath);
1015 if (file_fd == -1)
1016 return FALSE;
1018 /* get time elapsed from last refresh */
1019 if (gettimeofday (&tv, NULL) == -1)
1021 tv.tv_sec = 0;
1022 tv.tv_usec = 0;
1023 last_refresh = tv;
1025 seconds = tv.tv_sec - last_refresh.tv_sec;
1026 useconds = tv.tv_usec - last_refresh.tv_usec;
1027 if (useconds < 0)
1029 seconds--;
1030 useconds += G_USEC_PER_SEC;
1033 if (s.st_size >= MIN_REFRESH_FILE_SIZE || seconds > 0 || useconds > MAX_REFRESH_INTERVAL)
1035 g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename);
1036 status_update (str_trunc (buffer, WIDGET (h)->cols - 8));
1037 mc_refresh ();
1038 last_refresh = tv;
1039 status_updated = TRUE;
1042 tty_enable_interrupt_key ();
1043 tty_got_interrupt ();
1046 int line = 1;
1047 int pos = 0;
1048 int n_read = 0;
1049 off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */
1050 gboolean found = FALSE;
1051 gsize found_len;
1052 gsize found_start;
1053 char result[BUF_MEDIUM];
1054 char *strbuf = NULL; /* buffer for fetched string */
1055 int strbuf_size = 0;
1056 int i = -1; /* compensate for a newline we'll add when we first enter the loop */
1058 if (resuming)
1060 /* We've been previously suspended, start from the previous position */
1061 resuming = FALSE;
1062 line = last_line;
1063 pos = last_pos;
1064 off = last_off;
1065 i = last_i;
1068 while (!ret_val)
1070 char ch = '\0';
1072 off += i + 1; /* the previous line, plus a newline character */
1073 i = 0;
1075 /* read to buffer and get line from there */
1076 while (TRUE)
1078 if (pos >= n_read)
1080 pos = 0;
1081 n_read = mc_read (file_fd, buffer, sizeof (buffer));
1082 if (n_read <= 0)
1083 break;
1086 ch = buffer[pos++];
1087 if (ch == '\0')
1089 /* skip possible leading zero(s) */
1090 if (i == 0)
1092 off++;
1093 continue;
1095 break;
1098 if (i >= strbuf_size - 1)
1100 strbuf_size += 128;
1101 strbuf = g_realloc (strbuf, strbuf_size);
1104 /* Strip newline */
1105 if (ch == '\n')
1106 break;
1108 strbuf[i++] = ch;
1111 if (i == 0)
1113 if (ch == '\0')
1114 break;
1116 /* if (ch == '\n'): do not search in empty strings */
1117 goto skip_search;
1120 strbuf[i] = '\0';
1122 if (!found /* Search in binary line once */
1123 && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
1125 if (!status_updated)
1127 /* if we add results for a file, we have to ensure that
1128 name of this file is shown in status bar */
1129 g_snprintf (result, sizeof (result), _("Grepping in %s"), filename);
1130 status_update (str_trunc (result, WIDGET (h)->cols - 8));
1131 mc_refresh ();
1132 last_refresh = tv;
1133 status_updated = TRUE;
1136 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
1137 found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */
1138 find_add_match (directory, result, found_start, found_start + found_len);
1139 found = TRUE;
1142 if (found && options.content_first_hit)
1143 break;
1145 if (ch == '\n')
1147 skip_search:
1148 found = FALSE;
1149 line++;
1152 if ((line & 0xff) == 0)
1154 FindProgressStatus res;
1156 res = check_find_events (h);
1157 switch (res)
1159 case FIND_ABORT:
1160 stop_idle (h);
1161 ret_val = TRUE;
1162 break;
1163 case FIND_SUSPEND:
1164 resuming = TRUE;
1165 last_line = line;
1166 last_pos = pos;
1167 last_off = off;
1168 last_i = i;
1169 ret_val = TRUE;
1170 break;
1171 default:
1172 break;
1177 g_free (strbuf);
1180 tty_disable_interrupt_key ();
1181 mc_close (file_fd);
1182 return ret_val;
1185 /* --------------------------------------------------------------------------------------------- */
1188 If dir is absolute, this means we're within dir and searching file here.
1189 If dir is relative, this means we're going to add dir to the directory stack.
1191 static gboolean
1192 find_ignore_dir_search (const char *dir)
1194 if (find_ignore_dirs != NULL)
1196 const size_t dlen = strlen (dir);
1197 const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
1199 char **ignore_dir;
1201 for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
1203 const size_t ilen = strlen (*ignore_dir);
1204 const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
1206 /* ignore dir is too long -- skip it */
1207 if (dlen < ilen)
1208 continue;
1210 /* handle absolute and relative paths */
1211 switch (iabs | dabs)
1213 case 0: /* both paths are relative */
1214 case 3: /* both paths are abolute */
1215 /* if ignore dir is not a path of dir -- skip it */
1216 if (strncmp (dir, *ignore_dir, ilen) == 0)
1218 /* be sure that ignore dir is not a part of dir like:
1219 ignore dir is "h", dir is "home" */
1220 if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
1221 return TRUE;
1223 break;
1224 case 1: /* dir is absolute, ignore_dir is relative */
1226 char *d;
1228 d = strstr (dir, *ignore_dir);
1229 if (d != NULL && IS_PATH_SEP (d[-1])
1230 && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
1231 return TRUE;
1233 break;
1234 case 2: /* dir is relative, ignore_dir is absolute */
1235 /* FIXME: skip this case */
1236 break;
1237 default: /* this cannot occurs */
1238 return FALSE;
1243 return FALSE;
1246 /* --------------------------------------------------------------------------------------------- */
1248 static void
1249 find_rotate_dash (const WDialog * h, gboolean show)
1251 static size_t pos = 0;
1252 static const char rotating_dash[4] = "|/-\\";
1253 const Widget *w = CONST_WIDGET (h);
1255 tty_setcolor (h->color[DLG_COLOR_NORMAL]);
1256 widget_gotoyx (h, w->lines - 7, w->cols - 4);
1257 tty_print_char (show ? rotating_dash[pos] : ' ');
1258 pos = (pos + 1) % sizeof (rotating_dash);
1259 mc_refresh ();
1262 /* --------------------------------------------------------------------------------------------- */
1264 static int
1265 do_search (WDialog * h)
1267 static struct dirent *dp = NULL;
1268 static DIR *dirp = NULL;
1269 static char *directory = NULL;
1270 struct stat tmp_stat;
1271 gsize bytes_found;
1272 unsigned short count;
1274 if (h == NULL)
1275 { /* someone forces me to close dirp */
1276 if (dirp != NULL)
1278 mc_closedir (dirp);
1279 dirp = NULL;
1281 MC_PTR_FREE (directory);
1282 dp = NULL;
1283 return 1;
1286 for (count = 0; count < 32; count++)
1288 while (dp == NULL)
1290 if (dirp != NULL)
1292 mc_closedir (dirp);
1293 dirp = NULL;
1296 while (dirp == NULL)
1298 vfs_path_t *tmp_vpath = NULL;
1300 tty_setcolor (REVERSE_COLOR);
1302 while (TRUE)
1304 tmp_vpath = pop_directory ();
1305 if (tmp_vpath == NULL)
1307 running = FALSE;
1308 if (ignore_count == 0)
1309 status_update (_("Finished"));
1310 else
1312 char msg[BUF_SMALL];
1314 g_snprintf (msg, sizeof (msg),
1315 ngettext ("Finished (ignored %zu directory)",
1316 "Finished (ignored %zu directories)",
1317 ignore_count), ignore_count);
1318 status_update (msg);
1320 if (verbose)
1321 find_rotate_dash (h, FALSE);
1322 stop_idle (h);
1323 return 0;
1326 /* handle absolute ignore dirs here */
1328 gboolean ok;
1330 ok = find_ignore_dir_search (vfs_path_as_str (tmp_vpath));
1331 if (!ok)
1332 break;
1335 vfs_path_free (tmp_vpath);
1336 ignore_count++;
1339 g_free (directory);
1340 directory = g_strdup (vfs_path_as_str (tmp_vpath));
1342 if (verbose)
1344 char buffer[BUF_MEDIUM];
1346 g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory);
1347 status_update (str_trunc (directory, WIDGET (h)->cols - 8));
1350 dirp = mc_opendir (tmp_vpath);
1351 vfs_path_free (tmp_vpath);
1352 } /* while (!dirp) */
1354 /* skip invalid filenames */
1355 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1357 } /* while (!dp) */
1359 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
1361 /* skip invalid filenames */
1362 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1365 return 1;
1368 if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1370 gboolean search_ok;
1372 if (options.find_recurs && (directory != NULL))
1373 { /* Can directory be NULL ? */
1374 /* handle relative ignore dirs here */
1375 if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name))
1376 ignore_count++;
1377 else
1379 vfs_path_t *tmp_vpath;
1381 tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
1383 if (mc_lstat (tmp_vpath, &tmp_stat) == 0 && S_ISDIR (tmp_stat.st_mode))
1384 push_directory (tmp_vpath);
1385 else
1386 vfs_path_free (tmp_vpath);
1390 search_ok = mc_search_run (search_file_handle, dp->d_name,
1391 0, strlen (dp->d_name), &bytes_found);
1393 if (search_ok)
1395 if (content_pattern == NULL)
1396 find_add_match (directory, dp->d_name, 0, 0);
1397 else if (search_content (h, directory, dp->d_name))
1398 return 1;
1402 /* skip invalid filenames */
1403 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1405 } /* for */
1407 if (verbose)
1408 find_rotate_dash (h, TRUE);
1410 return 1;
1413 /* --------------------------------------------------------------------------------------------- */
1415 static void
1416 init_find_vars (void)
1418 MC_PTR_FREE (old_dir);
1419 matches = 0;
1420 ignore_count = 0;
1422 /* Remove all the items from the stack */
1423 clear_stack ();
1425 g_strfreev (find_ignore_dirs);
1426 find_ignore_dirs = NULL;
1429 /* --------------------------------------------------------------------------------------------- */
1431 static void
1432 find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
1433 off_t search_end)
1435 const char *filename = NULL;
1436 int line;
1437 vfs_path_t *fullname_vpath;
1439 if (content_pattern != NULL)
1441 filename = strchr (file + 4, ':') + 1;
1442 line = atoi (file + 4);
1444 else
1446 filename = file + 4;
1447 line = 0;
1450 fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
1451 if (edit)
1452 edit_file_at_line (fullname_vpath, use_internal_edit, line);
1453 else
1454 view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
1455 search_end);
1456 vfs_path_free (fullname_vpath);
1459 /* --------------------------------------------------------------------------------------------- */
1461 static cb_ret_t
1462 view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
1464 char *text = NULL;
1465 find_match_location_t *location;
1467 listbox_get_current (find_list, &text, (void **) &location);
1469 if ((text == NULL) || (location == NULL) || (location->dir == NULL))
1470 return MSG_NOT_HANDLED;
1472 find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
1473 return MSG_HANDLED;
1476 /* --------------------------------------------------------------------------------------------- */
1478 static void
1479 find_calc_button_locations (const WDialog * h, gboolean all_buttons)
1481 const int cols = CONST_WIDGET (h)->cols;
1483 int l1, l2;
1485 l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
1486 l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
1488 fbuts[0].x = (cols - l1) / 2;
1489 fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
1490 fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
1491 fbuts[3].x = fbuts[2].x;
1492 fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
1494 if (all_buttons)
1496 fbuts[5].x = (cols - l2) / 2;
1497 fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
1498 fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
1502 /* --------------------------------------------------------------------------------------------- */
1504 static void
1505 find_adjust_header (WDialog * h)
1507 char title[BUF_MEDIUM];
1508 int title_len;
1510 if (content_pattern != NULL)
1511 g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern,
1512 content_pattern);
1513 else
1514 g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern);
1516 title_len = str_term_width1 (title);
1517 if (title_len > WIDGET (h)->cols - 6)
1519 /* title is too wide, truncate it */
1520 title_len = WIDGET (h)->cols - 6;
1521 title_len = str_column_to_pos (title, title_len);
1522 title_len -= 3; /* reserve space for three dots */
1523 title_len = str_offset_to_pos (title, title_len);
1524 /* mark that title is truncated */
1525 memmove (title + title_len, "...", 4);
1528 dlg_set_title (h, title);
1531 /* --------------------------------------------------------------------------------------------- */
1533 static void
1534 find_relocate_buttons (const WDialog * h, gboolean all_buttons)
1536 size_t i;
1538 find_calc_button_locations (h, all_buttons);
1540 for (i = 0; i < fbuts_num; i++)
1541 fbuts[i].button->x = CONST_WIDGET (h)->x + fbuts[i].x;
1544 /* --------------------------------------------------------------------------------------------- */
1546 static cb_ret_t
1547 find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1549 WDialog *h = DIALOG (w);
1551 switch (msg)
1553 case MSG_INIT:
1554 find_adjust_header (h);
1555 return MSG_HANDLED;
1557 case MSG_KEY:
1558 if (parm == KEY_F (3) || parm == KEY_F (13))
1560 gboolean unparsed_view = (parm == KEY_F (13));
1562 return view_edit_currently_selected_file (unparsed_view, FALSE);
1564 if (parm == KEY_F (4))
1565 return view_edit_currently_selected_file (FALSE, TRUE);
1566 return MSG_NOT_HANDLED;
1568 case MSG_RESIZE:
1569 dlg_set_size (h, LINES - 4, COLS - 16);
1570 find_adjust_header (h);
1571 find_relocate_buttons (h, TRUE);
1572 return MSG_HANDLED;
1574 case MSG_IDLE:
1575 do_search (h);
1576 return MSG_HANDLED;
1578 default:
1579 return dlg_default_callback (w, sender, msg, parm, data);
1583 /* --------------------------------------------------------------------------------------------- */
1584 /** Handles the Stop/Start button in the find window */
1586 static int
1587 start_stop (WButton * button, int action)
1589 Widget *w = WIDGET (button);
1591 (void) action;
1593 running = is_start;
1594 widget_idle (WIDGET (find_dlg), running);
1595 is_start = !is_start;
1597 status_update (is_start ? _("Stopped") : _("Searching"));
1598 button_set_text (button, fbuts[is_start ? 3 : 2].text);
1600 find_relocate_buttons (DIALOG (w->owner), FALSE);
1601 dlg_draw (DIALOG (w->owner));
1603 return 0;
1606 /* --------------------------------------------------------------------------------------------- */
1607 /** Handle view command, when invoked as a button */
1609 static int
1610 find_do_view_file (WButton * button, int action)
1612 (void) button;
1613 (void) action;
1615 view_edit_currently_selected_file (FALSE, FALSE);
1616 return 0;
1619 /* --------------------------------------------------------------------------------------------- */
1620 /** Handle edit command, when invoked as a button */
1622 static int
1623 find_do_edit_file (WButton * button, int action)
1625 (void) button;
1626 (void) action;
1628 view_edit_currently_selected_file (FALSE, TRUE);
1629 return 0;
1632 /* --------------------------------------------------------------------------------------------- */
1634 static void
1635 setup_gui (void)
1637 WGroup *g;
1638 size_t i;
1639 int lines, cols;
1640 int y;
1642 static gboolean i18n_flag = FALSE;
1644 if (!i18n_flag)
1646 for (i = 0; i < fbuts_num; i++)
1648 #ifdef ENABLE_NLS
1649 fbuts[i].text = _(fbuts[i].text);
1650 #endif /* ENABLE_NLS */
1651 fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1652 if (fbuts[i].flags == DEFPUSH_BUTTON)
1653 fbuts[i].len += 2;
1656 i18n_flag = TRUE;
1659 lines = LINES - 4;
1660 cols = COLS - 16;
1662 find_dlg =
1663 dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL,
1664 "[Find File]", NULL);
1665 g = GROUP (find_dlg);
1667 find_calc_button_locations (find_dlg, TRUE);
1669 y = 2;
1670 find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
1671 group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
1672 y += WIDGET (find_list)->lines;
1674 group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1676 found_num_label = label_new (y++, 4, "");
1677 group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
1679 status_label = label_new (y++, 4, _("Searching"));
1680 group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
1682 group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
1684 for (i = 0; i < fbuts_num; i++)
1686 if (i == 3)
1687 fbuts[3].button = fbuts[2].button;
1688 else
1690 fbuts[i].button =
1691 WIDGET (button_new
1692 (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text,
1693 fbuts[i].callback));
1694 group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
1697 if (i == quit_button)
1698 y++;
1701 widget_select (WIDGET (find_list));
1704 /* --------------------------------------------------------------------------------------------- */
1706 static int
1707 run_process (void)
1709 int ret;
1711 search_content_handle = mc_search_new (content_pattern, NULL);
1712 if (search_content_handle)
1714 search_content_handle->search_type =
1715 options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1716 search_content_handle->is_case_sensitive = options.content_case_sens;
1717 search_content_handle->whole_words = options.content_whole_words;
1718 #ifdef HAVE_CHARSET
1719 search_content_handle->is_all_charsets = options.content_all_charsets;
1720 #endif
1722 search_file_handle = mc_search_new (find_pattern, NULL);
1723 search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1724 search_file_handle->is_case_sensitive = options.file_case_sens;
1725 #ifdef HAVE_CHARSET
1726 search_file_handle->is_all_charsets = options.file_all_charsets;
1727 #endif
1728 search_file_handle->is_entire_line = options.file_pattern;
1730 resuming = FALSE;
1732 widget_idle (WIDGET (find_dlg), TRUE);
1733 ret = dlg_run (find_dlg);
1735 mc_search_free (search_file_handle);
1736 search_file_handle = NULL;
1737 mc_search_free (search_content_handle);
1738 search_content_handle = NULL;
1740 return ret;
1743 /* --------------------------------------------------------------------------------------------- */
1745 static void
1746 kill_gui (void)
1748 widget_idle (WIDGET (find_dlg), FALSE);
1749 dlg_destroy (find_dlg);
1752 /* --------------------------------------------------------------------------------------------- */
1754 static int
1755 do_find (const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
1756 char **dirname, char **filename)
1758 int return_value = 0;
1759 char *dir_tmp = NULL, *file_tmp = NULL;
1761 setup_gui ();
1763 init_find_vars ();
1764 parse_ignore_dirs (ignore_dirs);
1765 push_directory (vfs_path_from_str (start_dir));
1767 return_value = run_process ();
1769 /* Clear variables */
1770 init_find_vars ();
1772 get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
1774 if (dir_tmp)
1775 *dirname = g_strdup (dir_tmp);
1776 if (file_tmp)
1777 *filename = g_strdup (file_tmp);
1779 if (return_value == B_PANELIZE && *filename)
1781 int i;
1782 struct stat st;
1783 GList *entry;
1784 dir_list *list = &current_panel->dir;
1785 char *name = NULL;
1787 panel_clean_dir (current_panel);
1788 dir_list_init (list);
1790 for (i = 0, entry = listbox_get_first_link (find_list); entry != NULL;
1791 i++, entry = g_list_next (entry))
1793 const char *lc_filename = NULL;
1794 WLEntry *le = LENTRY (entry->data);
1795 find_match_location_t *location = le->data;
1796 char *p;
1797 gboolean link_to_dir, stale_link;
1799 if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
1800 continue;
1802 if (!content_is_empty)
1803 lc_filename = strchr (le->text + 4, ':') + 1;
1804 else
1805 lc_filename = le->text + 4;
1807 name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
1808 /* skip initial start dir */
1809 if (start_dir_len < 0)
1810 p = name;
1811 else
1813 p = name + (size_t) start_dir_len;
1814 if (IS_PATH_SEP (*p))
1815 p++;
1818 if (!handle_path (p, &st, &link_to_dir, &stale_link))
1820 g_free (name);
1821 continue;
1823 /* Need to grow the *list? */
1824 if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
1826 g_free (name);
1827 break;
1830 /* don't add files more than once to the panel */
1831 if (!content_is_empty && list->len != 0
1832 && strcmp (list->list[list->len - 1].fname, p) == 0)
1834 g_free (name);
1835 continue;
1838 list->list[list->len].fnamelen = strlen (p);
1839 list->list[list->len].fname = g_strndup (p, list->list[list->len].fnamelen);
1840 list->list[list->len].f.marked = 0;
1841 list->list[list->len].f.link_to_dir = link_to_dir ? 1 : 0;
1842 list->list[list->len].f.stale_link = stale_link ? 1 : 0;
1843 list->list[list->len].f.dir_size_computed = 0;
1844 list->list[list->len].st = st;
1845 list->list[list->len].sort_key = NULL;
1846 list->list[list->len].second_sort_key = NULL;
1847 list->len++;
1848 g_free (name);
1849 if ((list->len & 15) == 0)
1850 rotate_dash (TRUE);
1853 current_panel->is_panelized = TRUE;
1854 panelize_absolutize_if_needed (current_panel);
1855 panelize_save_panel (current_panel);
1858 kill_gui ();
1859 do_search (NULL); /* force do_search to release resources */
1860 MC_PTR_FREE (old_dir);
1861 rotate_dash (FALSE);
1863 return return_value;
1866 /* --------------------------------------------------------------------------------------------- */
1867 /*** public functions ****************************************************************************/
1868 /* --------------------------------------------------------------------------------------------- */
1870 void
1871 find_file (void)
1873 char *start_dir = NULL, *ignore_dirs = NULL;
1874 ssize_t start_dir_len;
1876 find_pattern = NULL;
1877 content_pattern = NULL;
1879 while (find_parameters (&start_dir, &start_dir_len,
1880 &ignore_dirs, &find_pattern, &content_pattern))
1882 char *filename = NULL, *dirname = NULL;
1883 int v = B_CANCEL;
1885 content_is_empty = content_pattern == NULL;
1887 if (find_pattern[0] != '\0')
1889 last_refresh.tv_sec = 0;
1890 last_refresh.tv_usec = 0;
1892 is_start = FALSE;
1894 if (!content_is_empty && !str_is_valid_string (content_pattern))
1895 MC_PTR_FREE (content_pattern);
1897 v = do_find (start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
1900 g_free (start_dir);
1901 g_free (ignore_dirs);
1902 MC_PTR_FREE (find_pattern);
1904 if (v == B_ENTER)
1906 if (dirname != NULL)
1908 vfs_path_t *dirname_vpath;
1910 dirname_vpath = vfs_path_from_str (dirname);
1911 do_cd (dirname_vpath, cd_exact);
1912 vfs_path_free (dirname_vpath);
1913 if (filename != NULL)
1914 try_to_select (current_panel,
1915 filename + (content_pattern != NULL
1916 ? strchr (filename + 4, ':') - filename + 1 : 4));
1918 else if (filename != NULL)
1920 vfs_path_t *filename_vpath;
1922 filename_vpath = vfs_path_from_str (filename);
1923 do_cd (filename_vpath, cd_exact);
1924 vfs_path_free (filename_vpath);
1928 MC_PTR_FREE (content_pattern);
1929 g_free (dirname);
1930 g_free (filename);
1932 if (v == B_ENTER || v == B_CANCEL)
1933 break;
1935 if (v == B_PANELIZE)
1937 panel_re_sort (current_panel);
1938 try_to_select (current_panel, NULL);
1939 break;
1944 /* --------------------------------------------------------------------------------------------- */