Optimization: don't destroy the "Find file" dialog before call "Tree" one
[midnight-commander.git] / src / find.c
blob34fed7953451ec620f71b9670373ed632f435956
1 /* Find file command for the Midnight Commander
2 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007 Free Software Foundation, Inc.
4 Written 1995 by Miguel de Icaza
6 Complete rewrote.
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
22 /** \file find.c
23 * \brief Source: Find file command
26 #include <config.h>
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <sys/stat.h>
35 #include "lib/global.h"
37 #include "lib/tty/tty.h"
38 #include "lib/tty/key.h"
39 #include "lib/skin.h"
40 #include "lib/search.h"
41 #include "lib/mcconfig.h"
42 #include "lib/vfs/mc-vfs/vfs.h"
43 #include "lib/strutil.h"
45 #include "setup.h" /* verbose */
46 #include "dialog.h"
47 #include "widget.h"
48 #include "dir.h"
49 #include "panel.h" /* current_panel */
50 #include "main.h" /* do_cd, try_to_select */
51 #include "wtools.h"
52 #include "cmd.h" /* view_file_at_line */
53 #include "boxes.h"
54 #include "history.h" /* MC_HISTORY_SHARED_SEARCH */
55 #include "layout.h" /* mc_refresh() */
57 #include "find.h"
59 /* Size of the find parameters window */
60 #if HAVE_CHARSET
61 static int FIND_Y = 17;
62 #else
63 static int FIND_Y = 16;
64 #endif
65 static int FIND_X = 68;
67 /* Size of the find window */
68 #define FIND2_Y (LINES - 4)
70 static int FIND2_X = 64;
71 #define FIND2_X_USE (FIND2_X - 20)
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 /* List of directories to be ignored, separated by ':' */
91 char *find_ignore_dirs = NULL;
93 /* static variables to remember find parameters */
94 static WInput *in_start; /* Start path */
95 static WInput *in_name; /* Filename */
96 static WInput *in_with; /* Text */
97 static WLabel *content_label; /* 'Content:' label */
98 static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */
99 static WCheck *file_pattern_cbox; /* File name is glob or regexp */
100 static WCheck *recursively_cbox;
101 static WCheck *skip_hidden_cbox;
102 static WCheck *content_use_cbox; /* Take into account the Content field */
103 static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */
104 static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */
105 static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */
106 static WCheck *content_whole_words_cbox; /* "whole words" checkbox */
107 #ifdef HAVE_CHARSET
108 static WCheck *file_all_charsets_cbox;
109 static WCheck *content_all_charsets_cbox;
110 #endif
112 static gboolean running = FALSE; /* nice flag */
113 static char *find_pattern = NULL; /* Pattern to search */
114 static char *content_pattern = NULL; /* pattern to search inside files; if
115 content_regexp_flag is true, it contains the
116 regex pattern, else the search string. */
117 static unsigned long matches; /* Number of matches */
118 static gboolean is_start = FALSE; /* Status of the start/stop toggle button */
119 static char *old_dir = NULL;
121 /* Where did we stop */
122 static int resuming;
123 static int last_line;
124 static int last_pos;
126 static Dlg_head *find_dlg; /* The dialog */
127 static WButton *stop_button; /* pointer to the stop button */
128 static WLabel *status_label; /* Finished, Searching etc. */
129 static WLabel *found_num_label; /* Number of found items */
130 static WListbox *find_list; /* Listbox with the file list */
132 /* This keeps track of the directory stack */
133 #if GLIB_CHECK_VERSION (2, 14, 0)
134 static GQueue dir_queue = G_QUEUE_INIT;
135 #else
136 typedef struct dir_stack
138 char *name;
139 struct dir_stack *prev;
140 } dir_stack;
142 static dir_stack *dir_stack_base = 0;
143 #endif /* GLIB_CHECK_VERSION */
145 /* *INDENT-OFF* */
146 static struct
148 const char *text;
149 int len; /* length including space and brackets */
150 int x;
151 } fbuts[] =
153 {N_("&Suspend"), 11, 29},
154 {N_("Con&tinue"), 12, 29},
155 {N_("&Chdir"), 11, 3},
156 {N_("&Again"), 9, 17},
157 {N_("&Quit"), 8, 43},
158 {N_("Pane&lize"), 12, 3},
159 {N_("&View - F3"), 13, 20},
160 {N_("&Edit - F4"), 13, 38}
162 /* *INDENT-ON* */
164 /* find file options */
165 typedef struct
167 /* file name options */
168 gboolean file_case_sens;
169 gboolean file_pattern;
170 gboolean find_recurs;
171 gboolean skip_hidden;
172 gboolean file_all_charsets;
174 /* file content options */
175 gboolean content_use;
176 gboolean content_case_sens;
177 gboolean content_regexp;
178 gboolean content_first_hit;
179 gboolean content_whole_words;
180 gboolean content_all_charsets;
181 } find_file_options_t;
183 static find_file_options_t options = {
184 TRUE, TRUE, TRUE, FALSE, FALSE,
185 FALSE, TRUE, FALSE, FALSE, FALSE, FALSE
188 static char *in_start_dir = INPUT_LAST_TEXT;
190 static mc_search_t *search_file_handle = NULL;
191 static mc_search_t *search_content_handle = NULL;
193 static void
194 find_load_options (void)
196 static gboolean loaded = FALSE;
197 char *ignore_dirs;
199 if (loaded)
200 return;
202 loaded = TRUE;
204 /* Back compatibility: try load old parameter at first */
205 ignore_dirs = mc_config_get_string (mc_main_config, "Misc", "find_ignore_dirs", "");
206 if (ignore_dirs[0] != '\0')
208 find_ignore_dirs = g_strconcat (":", ignore_dirs, ":", (char *) NULL);
209 mc_config_set_string (mc_main_config, "FindFile", "ignore_dirs", ignore_dirs);
211 g_free (ignore_dirs);
212 mc_config_del_param (mc_main_config, "Misc", "find_ignore_dirs");
214 /* Then load new parameters */
215 ignore_dirs = mc_config_get_string (mc_main_config, "FindFile", "ignore_dirs", "");
216 if (ignore_dirs[0] != '\0')
218 g_free (find_ignore_dirs);
219 find_ignore_dirs = g_strconcat (":", ignore_dirs, ":", (char *) NULL);
221 g_free (ignore_dirs);
223 options.file_case_sens =
224 mc_config_get_bool (mc_main_config, "FindFile", "file_case_sens", TRUE);
225 options.file_pattern =
226 mc_config_get_bool (mc_main_config, "FindFile", "file_shell_pattern", TRUE);
227 options.find_recurs = mc_config_get_bool (mc_main_config, "FindFile", "file_find_recurs", TRUE);
228 options.skip_hidden =
229 mc_config_get_bool (mc_main_config, "FindFile", "file_skip_hidden", FALSE);
230 options.file_all_charsets =
231 mc_config_get_bool (mc_main_config, "FindFile", "file_all_charsets", FALSE);
232 options.content_use =
233 mc_config_get_bool (mc_main_config, "FindFile", "content_use", FALSE);
234 options.content_case_sens =
235 mc_config_get_bool (mc_main_config, "FindFile", "content_case_sens", TRUE);
236 options.content_regexp =
237 mc_config_get_bool (mc_main_config, "FindFile", "content_regexp", FALSE);
238 options.content_first_hit =
239 mc_config_get_bool (mc_main_config, "FindFile", "content_first_hit", FALSE);
240 options.content_whole_words =
241 mc_config_get_bool (mc_main_config, "FindFile", "content_whole_words", FALSE);
242 options.content_all_charsets =
243 mc_config_get_bool (mc_main_config, "FindFile", "content_all_charsets", FALSE);
246 static void
247 find_save_options (void)
249 mc_config_set_bool (mc_main_config, "FindFile", "file_case_sens", options.file_case_sens);
250 mc_config_set_bool (mc_main_config, "FindFile", "file_shell_pattern", options.file_pattern);
251 mc_config_set_bool (mc_main_config, "FindFile", "file_find_recurs", options.find_recurs);
252 mc_config_set_bool (mc_main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
253 mc_config_set_bool (mc_main_config, "FindFile", "file_all_charsets", options.file_all_charsets);
254 mc_config_set_bool (mc_main_config, "FindFile", "content_use", options.content_use);
255 mc_config_set_bool (mc_main_config, "FindFile", "content_case_sens", options.content_case_sens);
256 mc_config_set_bool (mc_main_config, "FindFile", "content_regexp", options.content_regexp);
257 mc_config_set_bool (mc_main_config, "FindFile", "content_first_hit", options.content_first_hit);
258 mc_config_set_bool (mc_main_config, "FindFile", "content_whole_words",
259 options.content_whole_words);
260 mc_config_set_bool (mc_main_config, "FindFile", "content_all_charsets",
261 options.content_all_charsets);
264 static inline char *
265 add_to_list (const char *text, void *data)
267 return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data);
270 static inline void
271 stop_idle (void *data)
273 set_idle_proc (data, 0);
276 static inline void
277 status_update (const char *text)
279 label_set_text (status_label, text);
282 static void
283 found_num_update (void)
285 char buffer[BUF_TINY];
286 g_snprintf (buffer, sizeof (buffer), _("Found: %ld"), matches);
287 label_set_text (found_num_label, buffer);
290 static void
291 get_list_info (char **file, char **dir)
293 listbox_get_current (find_list, file, (void **) dir);
296 /* check regular expression */
297 static gboolean
298 find_check_regexp (const char *r)
300 mc_search_t *search;
301 gboolean regexp_ok = FALSE;
303 search = mc_search_new (r, -1);
305 if (search != NULL)
307 search->search_type = MC_SEARCH_T_REGEX;
308 regexp_ok = mc_search_prepare (search);
309 mc_search_free (search);
312 return regexp_ok;
316 * Callback for the parameter dialog.
317 * Validate regex, prevent closing the dialog if it's invalid.
319 static cb_ret_t
320 find_parm_callback (Dlg_head * h, Widget * sender, dlg_msg_t msg, int parm, void *data)
322 switch (msg)
324 case DLG_ACTION:
325 if (sender == (Widget *) content_use_cbox)
327 gboolean disable = !(content_use_cbox->state & C_BOOL);
329 widget_disable (content_label->widget, disable);
330 send_message ((Widget *) content_label, WIDGET_DRAW, 0);
331 widget_disable (in_with->widget, disable);
332 send_message ((Widget *) in_with, WIDGET_DRAW, 0);
333 widget_disable (content_first_hit_cbox->widget, disable);
334 send_message ((Widget *) content_first_hit_cbox, WIDGET_DRAW, 0);
335 widget_disable (content_regexp_cbox->widget, disable);
336 send_message ((Widget *) content_regexp_cbox, WIDGET_DRAW, 0);
337 widget_disable (content_case_sens_cbox->widget, disable);
338 send_message ((Widget *) content_case_sens_cbox, WIDGET_DRAW, 0);
339 #ifdef HAVE_CHARSET
340 widget_disable (content_all_charsets_cbox->widget, disable);
341 send_message ((Widget *) content_all_charsets_cbox, WIDGET_DRAW, 0);
342 #endif
343 widget_disable (content_whole_words_cbox->widget, disable);
344 send_message ((Widget *) content_whole_words_cbox, WIDGET_DRAW, 0);
346 return MSG_HANDLED;
349 return MSG_NOT_HANDLED;
352 case DLG_VALIDATE:
353 if (h->ret_value != B_ENTER)
354 return MSG_HANDLED;
356 /* check filename regexp */
357 if (!(file_pattern_cbox->state & C_BOOL)
358 && (in_name->buffer[0] != '\0') && !find_check_regexp (in_name->buffer))
360 message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
361 dlg_select_widget (in_name);
362 h->state = DLG_ACTIVE; /* Don't stop the dialog */
363 return MSG_HANDLED;
366 /* check content regexp */
367 if ((content_regexp_cbox->state & C_BOOL)
368 && (in_with->buffer[0] != '\0') && !find_check_regexp (in_with->buffer))
370 message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
371 dlg_select_widget (in_with);
372 h->state = DLG_ACTIVE; /* Don't stop the dialog */
373 return MSG_HANDLED;
376 return MSG_HANDLED;
378 default:
379 return default_dlg_callback (h, sender, msg, parm, data);
384 * find_parameters: gets information from the user
386 * If the return value is TRUE, then the following holds:
388 * START_DIR and PATTERN are pointers to char * and upon return they
389 * contain the information provided by the user.
391 * CONTENT holds a strdup of the contents specified by the user if he
392 * asked for them or 0 if not (note, this is different from the
393 * behavior for the other two parameters.
396 static gboolean
397 find_parameters (char **start_dir, char **pattern, char **content)
399 gboolean return_value;
401 /* file name */
402 const char *file_case_label = N_("Cas&e sensitive");
403 const char *file_pattern_label = N_("&Using shell patterns");
404 const char *file_recurs_label = N_("&Find recursively");
405 const char *file_skip_hidden_label = N_("S&kip hidden");
406 #ifdef HAVE_CHARSET
407 const char *file_all_charsets_label = N_("&All charsets");
408 #endif
410 /* file content */
411 const char *content_use_label = N_("Searc&h for content");
412 const char *content_case_label = N_("Case sens&itive");
413 const char *content_regexp_label = N_("Re&gular expression");
414 const char *content_first_hit_label = N_("Fir&st hit");
415 const char *content_whole_words_label = N_("&Whole words");
416 #ifdef HAVE_CHARSET
417 const char *content_all_charsets_label = N_("All cha&rsets");
418 #endif
420 const char *buts[] = { N_("&OK"), N_("&Cancel"), N_("&Tree") };
422 int b0, b1, b2;
424 int cbox_position;
425 gboolean disable;
427 #ifdef ENABLE_NLS
429 int i = sizeof (buts) / sizeof (buts[0]);
430 while (i-- != 0)
431 buts[i] = _(buts[i]);
433 file_case_label = _(file_case_label);
434 file_pattern_label = _(file_pattern_label);
435 file_recurs_label = _(file_recurs_label);
436 file_skip_hidden_label = _(file_skip_hidden_label);
437 #ifdef HAVE_CHARSET
438 file_all_charsets_label = _(file_all_charsets_label);
439 content_all_charsets_label = _(content_all_charsets_label);
440 #endif
441 content_use_label = _(content_use_label);
442 content_case_label = _(content_case_label);
443 content_regexp_label = _(content_regexp_label);
444 content_first_hit_label = _(content_first_hit_label);
445 content_whole_words_label = _(content_whole_words_label);
447 #endif /* ENABLE_NLS */
449 b0 = str_term_width1 (buts[0]) + 6; /* default button */
450 b1 = str_term_width1 (buts[1]) + 4;
451 b2 = str_term_width1 (buts[2]) + 4;
453 find_load_options ();
455 if (in_start_dir == NULL)
456 in_start_dir = g_strdup (".");
458 disable = !options.content_use;
460 find_dlg =
461 create_dlg (TRUE, 0, 0, FIND_Y, FIND_X, dialog_colors,
462 find_parm_callback, "[Find File]", _("Find File"), DLG_CENTER | DLG_REVERSE);
464 add_widget (find_dlg,
465 button_new (FIND_Y - 3, FIND_X * 3 / 4 - b1 / 2, B_CANCEL, NORMAL_BUTTON, buts[1],
466 0));
467 add_widget (find_dlg,
468 button_new (FIND_Y - 3, FIND_X / 4 - b0 / 2, B_ENTER, DEFPUSH_BUTTON, buts[0], 0));
470 cbox_position = FIND_Y - 5;
472 content_first_hit_cbox =
473 check_new (cbox_position--, FIND_X / 2 + 1, options.content_first_hit, content_first_hit_label);
474 widget_disable (content_first_hit_cbox->widget, disable);
475 add_widget (find_dlg, content_first_hit_cbox);
477 content_whole_words_cbox =
478 check_new (cbox_position--, FIND_X / 2 + 1, options.content_whole_words, content_whole_words_label);
479 widget_disable (content_whole_words_cbox->widget, disable);
480 add_widget (find_dlg, content_whole_words_cbox);
482 #ifdef HAVE_CHARSET
483 content_all_charsets_cbox = check_new (cbox_position--, FIND_X / 2 + 1,
484 options.content_all_charsets,
485 content_all_charsets_label);
486 widget_disable (content_all_charsets_cbox->widget, disable);
487 add_widget (find_dlg, content_all_charsets_cbox);
488 #endif
490 content_case_sens_cbox =
491 check_new (cbox_position--, FIND_X / 2 + 1, options.content_case_sens, content_case_label);
492 widget_disable (content_case_sens_cbox->widget, disable);
493 add_widget (find_dlg, content_case_sens_cbox);
495 content_regexp_cbox =
496 check_new (cbox_position--, FIND_X / 2 + 1, options.content_regexp, content_regexp_label);
497 widget_disable (content_regexp_cbox->widget, disable);
498 add_widget (find_dlg, content_regexp_cbox);
500 cbox_position = FIND_Y - 6;
502 skip_hidden_cbox = check_new (cbox_position--, 3, options.skip_hidden, file_skip_hidden_label);
503 add_widget (find_dlg, skip_hidden_cbox);
505 #ifdef HAVE_CHARSET
506 file_all_charsets_cbox = check_new (cbox_position--, 3, options.file_all_charsets, file_all_charsets_label);
507 add_widget (find_dlg, file_all_charsets_cbox);
508 #endif
510 file_case_sens_cbox = check_new (cbox_position--, 3, options.file_case_sens, file_case_label);
511 add_widget (find_dlg, file_case_sens_cbox);
513 file_pattern_cbox = check_new (cbox_position--, 3, options.file_pattern, file_pattern_label);
514 add_widget (find_dlg, file_pattern_cbox);
516 recursively_cbox = check_new (cbox_position, 3, options.find_recurs, file_recurs_label);
517 add_widget (find_dlg, recursively_cbox);
519 /* This checkbox is located in the second column */
520 content_use_cbox = check_new (cbox_position, FIND_X / 2 + 1, options.content_use, content_use_label);
521 add_widget (find_dlg, content_use_cbox);
523 in_with = input_new (6, FIND_X / 2 + 1, input_get_default_colors(), FIND_X / 2 - 4, INPUT_LAST_TEXT,
524 MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_DEFAULT);
525 widget_disable (in_with->widget, disable);
526 add_widget (find_dlg, in_with);
528 content_label = label_new (5, FIND_X / 2 + 1, _("Content:"));
529 widget_disable (content_label->widget, disable);
530 add_widget (find_dlg, content_label);
532 in_name = input_new (6, 3, input_get_default_colors(),
533 FIND_X / 2 - 4, INPUT_LAST_TEXT, "name", INPUT_COMPLETE_DEFAULT);
534 add_widget (find_dlg, in_name);
535 add_widget (find_dlg, label_new (5, 3, _("File name:")));
537 add_widget (find_dlg, button_new (3, FIND_X - b2 - 2, B_TREE, NORMAL_BUTTON, buts[2], 0));
539 in_start = input_new (3, 3, input_get_default_colors(),
540 FIND_X - b2 - 6, in_start_dir, "start", INPUT_COMPLETE_DEFAULT);
541 add_widget (find_dlg, in_start);
542 add_widget (find_dlg, label_new (2, 3, _("Start at:")));
544 find_par_start:
545 dlg_select_widget (in_name);
547 switch (run_dlg (find_dlg))
549 case B_CANCEL:
550 return_value = FALSE;
551 break;
553 case B_TREE:
555 const char *temp_dir = in_start->buffer;
557 if ((temp_dir[0] == '\0') || ((temp_dir[0] == '.') && (temp_dir[1] == '\0')))
558 temp_dir = current_panel->cwd;
560 if (in_start_dir != INPUT_LAST_TEXT)
561 g_free (in_start_dir);
562 in_start_dir = tree_box (temp_dir);
563 if (in_start_dir == NULL)
564 in_start_dir = g_strdup (temp_dir);
566 assign_text (in_start, in_start_dir);
568 /* Warning: Dreadful goto */
569 goto find_par_start;
572 default:
573 #ifdef HAVE_CHARSET
574 options.file_all_charsets = file_all_charsets_cbox->state & C_BOOL;
575 options.content_all_charsets = content_all_charsets_cbox->state & C_BOOL;
576 #endif
577 options.content_use = content_use_cbox->state & C_BOOL;
578 options.content_case_sens = content_case_sens_cbox->state & C_BOOL;
579 options.content_regexp = content_regexp_cbox->state & C_BOOL;
580 options.content_first_hit = content_first_hit_cbox->state & C_BOOL;
581 options.content_whole_words = content_whole_words_cbox->state & C_BOOL;
582 options.find_recurs = recursively_cbox->state & C_BOOL;
583 options.file_pattern = file_pattern_cbox->state & C_BOOL;
584 options.file_case_sens = file_case_sens_cbox->state & C_BOOL;
585 options.skip_hidden = skip_hidden_cbox->state & C_BOOL;
587 *content = (in_with->buffer[0] != '\0') ? g_strdup (in_with->buffer) : NULL;
588 *start_dir = g_strdup ((in_start->buffer[0] != '\0') ? in_start->buffer : ".");
589 *pattern = g_strdup (in_name->buffer);
590 if (in_start_dir != INPUT_LAST_TEXT)
591 g_free (in_start_dir);
592 in_start_dir = g_strdup (*start_dir);
594 find_save_options ();
596 return_value = TRUE;
599 destroy_dlg (find_dlg);
601 return return_value;
604 #if GLIB_CHECK_VERSION (2, 14, 0)
606 static inline void
607 push_directory (const char *dir)
609 g_queue_push_head (&dir_queue, (void *) dir);
612 static inline char *
613 pop_directory (void)
615 return (char *) g_queue_pop_tail (&dir_queue);
618 /* Remove all the items in the stack */
619 static void
620 clear_stack (void)
622 g_queue_foreach (&dir_queue, (GFunc) g_free, NULL);
623 g_queue_clear (&dir_queue);
626 #else /* GLIB_CHAECK_VERSION */
628 static void
629 push_directory (const char *dir)
631 dir_stack *new;
633 new = g_new (dir_stack, 1);
634 new->name = str_unconst (dir);
635 new->prev = dir_stack_base;
636 dir_stack_base = new;
639 static char *
640 pop_directory (void)
642 char *name = NULL;
644 if (dir_stack_base != NULL)
646 dir_stack *next;
647 name = dir_stack_base->name;
648 next = dir_stack_base->prev;
649 g_free (dir_stack_base);
650 dir_stack_base = next;
653 return name;
656 /* Remove all the items in the stack */
657 static void
658 clear_stack (void)
660 char *dir = NULL;
661 while ((dir = pop_directory ()) != NULL)
662 g_free (dir);
665 #endif /* GLIB_CHAECK_VERSION */
667 static void
668 insert_file (const char *dir, const char *file)
670 char *tmp_name = NULL;
671 static char *dirname = NULL;
673 while (dir[0] == PATH_SEP && dir[1] == PATH_SEP)
674 dir++;
676 if (old_dir)
678 if (strcmp (old_dir, dir))
680 g_free (old_dir);
681 old_dir = g_strdup (dir);
682 dirname = add_to_list (dir, NULL);
685 else
687 old_dir = g_strdup (dir);
688 dirname = add_to_list (dir, NULL);
691 tmp_name = g_strdup_printf (" %s", file);
692 add_to_list (tmp_name, dirname);
693 g_free (tmp_name);
696 static void
697 find_add_match (const char *dir, const char *file)
699 insert_file (dir, file);
701 /* Don't scroll */
702 if (matches == 0)
703 listbox_select_first (find_list);
704 send_message (&find_list->widget, WIDGET_DRAW, 0);
706 matches++;
707 found_num_update ();
711 * get_line_at:
713 * Returns malloced null-terminated line from file file_fd.
714 * Input is buffered in buf_size long buffer.
715 * Current pos in buf is stored in pos.
716 * n_read - number of read chars.
717 * has_newline - is there newline ?
719 static char *
720 get_line_at (int file_fd, char *buf, int buf_size, int *pos, int *n_read, gboolean * has_newline)
722 char *buffer = NULL;
723 int buffer_size = 0;
724 char ch = 0;
725 int i = 0;
727 for (;;)
729 if (*pos >= *n_read)
731 *pos = 0;
732 *n_read = mc_read (file_fd, buf, buf_size);
733 if (*n_read <= 0)
734 break;
737 ch = buf[(*pos)++];
738 if (ch == '\0')
740 /* skip possible leading zero(s) */
741 if (i == 0)
742 continue;
743 else
744 break;
747 if (i >= buffer_size - 1)
749 buffer = g_realloc (buffer, buffer_size += 80);
751 /* Strip newline */
752 if (ch == '\n')
753 break;
755 buffer[i++] = ch;
758 *has_newline = (ch != '\0');
760 if (buffer != NULL)
761 buffer[i] = '\0';
763 return buffer;
766 static FindProgressStatus
767 check_find_events (Dlg_head * h)
769 Gpm_Event event;
770 int c;
772 event.x = -1;
773 c = tty_get_event (&event, h->mouse_status == MOU_REPEAT, FALSE);
774 if (c != EV_NONE)
776 dlg_process_event (h, c, &event);
777 if (h->ret_value == B_ENTER
778 || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
780 /* dialog terminated */
781 return FIND_ABORT;
783 if (!(h->flags & DLG_WANT_IDLE))
785 /* searching suspended */
786 return FIND_SUSPEND;
790 return FIND_CONT;
794 * search_content:
796 * Search the content_pattern string in the DIRECTORY/FILE.
797 * It will add the found entries to the find listbox.
799 * returns FALSE if do_search should look for another file
800 * TRUE if do_search should exit and proceed to the event handler
802 static gboolean
803 search_content (Dlg_head * h, const char *directory, const char *filename)
805 struct stat s;
806 char buffer[BUF_4K];
807 char *fname = NULL;
808 int file_fd;
809 gboolean ret_val = FALSE;
811 fname = concat_dir_and_file (directory, filename);
813 if (mc_stat (fname, &s) != 0 || !S_ISREG (s.st_mode))
815 g_free (fname);
816 return 0;
819 file_fd = mc_open (fname, O_RDONLY);
820 g_free (fname);
822 if (file_fd == -1)
823 return 0;
825 g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), str_trunc (filename, FIND2_X_USE));
827 status_update (buffer);
828 mc_refresh ();
830 tty_enable_interrupt_key ();
831 tty_got_interrupt ();
834 int line = 1;
835 int pos = 0;
836 int n_read = 0;
837 gboolean has_newline;
838 char *p = NULL;
839 gboolean found = FALSE;
840 gsize found_len;
841 char result[BUF_MEDIUM];
843 if (resuming)
845 /* We've been previously suspended, start from the previous position */
846 resuming = 0;
847 line = last_line;
848 pos = last_pos;
850 while (!ret_val
851 && (p = get_line_at (file_fd, buffer, sizeof (buffer),
852 &pos, &n_read, &has_newline)) != NULL)
854 if (!found /* Search in binary line once */
855 && mc_search_run (search_content_handle,
856 (const void *) p, 0, strlen (p), &found_len))
858 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
859 find_add_match (directory, result);
860 found = TRUE;
862 g_free (p);
864 if (found && options.content_first_hit)
865 break;
867 if (has_newline)
869 found = FALSE;
870 line++;
873 if ((line & 0xff) == 0)
875 FindProgressStatus res;
876 res = check_find_events (h);
877 switch (res)
879 case FIND_ABORT:
880 stop_idle (h);
881 ret_val = TRUE;
882 break;
883 case FIND_SUSPEND:
884 resuming = 1;
885 last_line = line;
886 last_pos = pos;
887 ret_val = TRUE;
888 break;
889 default:
890 break;
896 tty_disable_interrupt_key ();
897 mc_close (file_fd);
898 return ret_val;
901 static int
902 do_search (struct Dlg_head *h)
904 static struct dirent *dp = NULL;
905 static DIR *dirp = NULL;
906 static char *directory = NULL;
907 struct stat tmp_stat;
908 static int pos = 0;
909 static int subdirs_left = 0;
910 gsize bytes_found;
911 unsigned long count; /* Number of files displayed */
913 if (!h)
914 { /* someone forces me to close dirp */
915 if (dirp)
917 mc_closedir (dirp);
918 dirp = NULL;
920 g_free (directory);
921 directory = NULL;
922 dp = NULL;
923 return 1;
926 search_content_handle = mc_search_new (content_pattern, -1);
927 if (search_content_handle)
929 search_content_handle->search_type =
930 options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
931 search_content_handle->is_case_sensitive = options.content_case_sens;
932 search_content_handle->whole_words = options.content_whole_words;
933 search_content_handle->is_all_charsets = options.content_all_charsets;
935 search_file_handle = mc_search_new (find_pattern, -1);
936 search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
937 search_file_handle->is_case_sensitive = options.file_case_sens;
938 search_file_handle->is_all_charsets = options.file_all_charsets;
939 search_file_handle->is_entire_line = options.file_pattern;
941 count = 0;
943 do_search_begin:
944 while (!dp)
946 if (dirp)
948 mc_closedir (dirp);
949 dirp = 0;
952 while (!dirp)
954 char *tmp = NULL;
956 tty_setcolor (REVERSE_COLOR);
957 while (1)
959 char *temp_dir = NULL;
960 gboolean found;
962 tmp = pop_directory ();
963 if (tmp == NULL)
965 running = FALSE;
966 status_update (_("Finished"));
967 stop_idle (h);
968 mc_search_free (search_file_handle);
969 search_file_handle = NULL;
970 mc_search_free (search_content_handle);
971 search_content_handle = NULL;
972 return 0;
975 if ((find_ignore_dirs == NULL) || (find_ignore_dirs[0] == '\0'))
976 break;
978 temp_dir = g_strdup_printf (":%s:", tmp);
979 found = strstr (find_ignore_dirs, temp_dir) != 0;
980 g_free (temp_dir);
982 if (!found)
983 break;
985 g_free (tmp);
988 g_free (directory);
989 directory = tmp;
991 if (verbose)
993 char buffer[BUF_SMALL];
995 g_snprintf (buffer, sizeof (buffer), _("Searching %s"),
996 str_trunc (directory, FIND2_X_USE));
997 status_update (buffer);
999 /* mc_stat should not be called after mc_opendir
1000 because vfs_s_opendir modifies the st_nlink
1002 if (!mc_stat (directory, &tmp_stat))
1003 subdirs_left = tmp_stat.st_nlink - 2;
1004 else
1005 subdirs_left = 0;
1007 dirp = mc_opendir (directory);
1008 } /* while (!dirp) */
1010 /* skip invalid filenames */
1011 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1013 } /* while (!dp) */
1015 if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
1017 dp = mc_readdir (dirp);
1018 /* skip invalid filenames */
1019 while (dp != NULL && !str_is_valid_string (dp->d_name))
1020 dp = mc_readdir (dirp);
1022 mc_search_free (search_file_handle);
1023 search_file_handle = NULL;
1024 mc_search_free (search_content_handle);
1025 search_content_handle = NULL;
1026 return 1;
1029 if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1031 gboolean search_ok;
1033 if ((subdirs_left != 0) && options.find_recurs && (directory != NULL))
1034 { /* Can directory be NULL ? */
1035 char *tmp_name = concat_dir_and_file (directory, dp->d_name);
1036 if (!mc_lstat (tmp_name, &tmp_stat) && S_ISDIR (tmp_stat.st_mode))
1038 push_directory (tmp_name);
1039 subdirs_left--;
1041 else
1042 g_free (tmp_name);
1045 search_ok = mc_search_run (search_file_handle, dp->d_name,
1046 0, strlen (dp->d_name), &bytes_found);
1048 if (search_ok)
1050 if (content_pattern == NULL)
1051 find_add_match (directory, dp->d_name);
1052 else if (search_content (h, directory, dp->d_name))
1054 mc_search_free (search_file_handle);
1055 search_file_handle = NULL;
1056 mc_search_free (search_content_handle);
1057 search_content_handle = NULL;
1058 return 1;
1063 /* skip invalid filenames */
1064 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1067 /* Displays the nice dot */
1068 count++;
1069 if (!(count & 31))
1071 /* For nice updating */
1072 const char rotating_dash[] = "|/-\\";
1074 if (verbose)
1076 pos = (pos + 1) % 4;
1077 tty_setcolor (h->color[DLG_COLOR_NORMAL]);
1078 dlg_move (h, FIND2_Y - 7, FIND2_X - 4);
1079 tty_print_char (rotating_dash[pos]);
1080 mc_refresh ();
1083 else
1084 goto do_search_begin;
1086 mc_search_free (search_file_handle);
1087 search_file_handle = NULL;
1088 mc_search_free (search_content_handle);
1089 search_content_handle = NULL;
1090 return 1;
1093 static void
1094 init_find_vars (void)
1096 g_free (old_dir);
1097 old_dir = NULL;
1098 matches = 0;
1100 /* Remove all the items in the stack */
1101 clear_stack ();
1104 static char *
1105 make_fullname (const char *dirname, const char *filename)
1108 if (strcmp (dirname, ".") == 0 || strcmp (dirname, "." PATH_SEP_STR) == 0)
1109 return g_strdup (filename);
1110 if (strncmp (dirname, "." PATH_SEP_STR, 2) == 0)
1111 return concat_dir_and_file (dirname + 2, filename);
1112 return concat_dir_and_file (dirname, filename);
1115 static void
1116 find_do_view_edit (int unparsed_view, int edit, char *dir, char *file)
1118 char *fullname = NULL;
1119 const char *filename = NULL;
1120 int line;
1122 if (content_pattern != NULL)
1124 filename = strchr (file + 4, ':') + 1;
1125 line = atoi (file + 4);
1127 else
1129 filename = file + 4;
1130 line = 0;
1133 fullname = make_fullname (dir, filename);
1134 if (edit)
1135 do_edit_at_line (fullname, use_internal_edit, line);
1136 else
1137 view_file_at_line (fullname, unparsed_view, use_internal_view, line);
1138 g_free (fullname);
1141 static cb_ret_t
1142 view_edit_currently_selected_file (int unparsed_view, int edit)
1144 char *dir = NULL;
1145 char *text = NULL;
1147 listbox_get_current (find_list, &text, (void **) &dir);
1149 if ((text == NULL) || (dir == NULL))
1150 return MSG_NOT_HANDLED;
1152 find_do_view_edit (unparsed_view, edit, dir, text);
1153 return MSG_HANDLED;
1156 static cb_ret_t
1157 find_callback (struct Dlg_head *h, Widget * sender, dlg_msg_t msg, int parm, void *data)
1159 switch (msg)
1161 case DLG_KEY:
1162 if (parm == KEY_F (3) || parm == KEY_F (13))
1164 int unparsed_view = (parm == KEY_F (13));
1165 return view_edit_currently_selected_file (unparsed_view, 0);
1167 if (parm == KEY_F (4))
1169 return view_edit_currently_selected_file (0, 1);
1171 return MSG_NOT_HANDLED;
1173 case DLG_IDLE:
1174 do_search (h);
1175 return MSG_HANDLED;
1177 default:
1178 return default_dlg_callback (h, sender, msg, parm, data);
1182 /* Handles the Stop/Start button in the find window */
1183 static int
1184 start_stop (WButton *button, int action)
1186 (void) button;
1187 (void) action;
1189 running = is_start;
1190 set_idle_proc (find_dlg, running);
1191 is_start = !is_start;
1193 status_update (is_start ? _("Stopped") : _("Searching"));
1194 button_set_text (stop_button, fbuts[is_start ? 1 : 0].text);
1196 return 0;
1199 /* Handle view command, when invoked as a button */
1200 static int
1201 find_do_view_file (WButton *button, int action)
1203 (void) button;
1204 (void) action;
1206 view_edit_currently_selected_file (0, 0);
1207 return 0;
1210 /* Handle edit command, when invoked as a button */
1211 static int
1212 find_do_edit_file (WButton *button, int action)
1214 (void) button;
1215 (void) action;
1217 view_edit_currently_selected_file (0, 1);
1218 return 0;
1221 static void
1222 setup_gui (void)
1224 #ifdef ENABLE_NLS
1225 static gboolean i18n_flag = FALSE;
1227 if (!i18n_flag)
1229 int i = sizeof (fbuts) / sizeof (fbuts[0]);
1230 while (i-- != 0)
1232 fbuts[i].text = _(fbuts[i].text);
1233 fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1236 fbuts[2].len += 2; /* DEFPUSH_BUTTON */
1237 i18n_flag = TRUE;
1239 #endif /* ENABLE_NLS */
1242 * Dynamically place buttons centered within current window size
1245 int l0 = max (fbuts[0].len, fbuts[1].len);
1246 int l1 = fbuts[2].len + fbuts[3].len + l0 + fbuts[4].len;
1247 int l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len;
1248 int r1, r2;
1250 /* Check, if both button rows fit within FIND2_X */
1251 FIND2_X = max (l1 + 9, COLS - 16);
1252 FIND2_X = max (l2 + 8, FIND2_X);
1254 /* compute amount of space between buttons for each row */
1255 r1 = (FIND2_X - 4 - l1) % 5;
1256 l1 = (FIND2_X - 4 - l1) / 5;
1257 r2 = (FIND2_X - 4 - l2) % 4;
1258 l2 = (FIND2_X - 4 - l2) / 4;
1260 /* ...and finally, place buttons */
1261 fbuts[2].x = 2 + r1 / 2 + l1;
1262 fbuts[3].x = fbuts[2].x + fbuts[2].len + l1;
1263 fbuts[0].x = fbuts[3].x + fbuts[3].len + l1;
1264 fbuts[4].x = fbuts[0].x + l0 + l1;
1265 fbuts[5].x = 2 + r2 / 2 + l2;
1266 fbuts[6].x = fbuts[5].x + fbuts[5].len + l2;
1267 fbuts[7].x = fbuts[6].x + fbuts[6].len + l2;
1270 find_dlg =
1271 create_dlg (TRUE, 0, 0, FIND2_Y, FIND2_X, dialog_colors, find_callback,
1272 "[Find File]", _("Find File"), DLG_CENTER | DLG_REVERSE);
1274 add_widget (find_dlg,
1275 button_new (FIND2_Y - 3, fbuts[7].x, B_VIEW, NORMAL_BUTTON,
1276 fbuts[7].text, find_do_edit_file));
1277 add_widget (find_dlg,
1278 button_new (FIND2_Y - 3, fbuts[6].x, B_VIEW, NORMAL_BUTTON,
1279 fbuts[6].text, find_do_view_file));
1280 add_widget (find_dlg,
1281 button_new (FIND2_Y - 3, fbuts[5].x, B_PANELIZE, NORMAL_BUTTON, fbuts[5].text, NULL));
1283 add_widget (find_dlg,
1284 button_new (FIND2_Y - 4, fbuts[4].x, B_CANCEL, NORMAL_BUTTON, fbuts[4].text, NULL));
1285 stop_button =
1286 button_new (FIND2_Y - 4, fbuts[0].x, B_STOP, NORMAL_BUTTON, fbuts[0].text, start_stop);
1287 add_widget (find_dlg, stop_button);
1288 add_widget (find_dlg,
1289 button_new (FIND2_Y - 4, fbuts[3].x, B_AGAIN, NORMAL_BUTTON, fbuts[3].text, NULL));
1290 add_widget (find_dlg,
1291 button_new (FIND2_Y - 4, fbuts[2].x, B_ENTER, DEFPUSH_BUTTON, fbuts[2].text, NULL));
1293 status_label = label_new (FIND2_Y - 7, 4, _("Searching"));
1294 add_widget (find_dlg, status_label);
1296 found_num_label = label_new (FIND2_Y - 6, 4, "");
1297 add_widget (find_dlg, found_num_label);
1299 find_list = listbox_new (2, 2, FIND2_Y - 10, FIND2_X - 4, FALSE, NULL);
1300 add_widget (find_dlg, find_list);
1303 static int
1304 run_process (void)
1306 resuming = 0;
1307 set_idle_proc (find_dlg, 1);
1308 return run_dlg (find_dlg);
1311 static void
1312 kill_gui (void)
1314 set_idle_proc (find_dlg, 0);
1315 destroy_dlg (find_dlg);
1318 static int
1319 find_file (const char *start_dir, const char *pattern, const char *content,
1320 char **dirname, char **filename)
1322 int return_value = 0;
1323 char *dir_tmp = NULL, *file_tmp = NULL;
1325 setup_gui ();
1327 /* FIXME: Need to cleanup this, this ought to be passed non-globaly */
1328 find_pattern = str_unconst (pattern);
1330 content_pattern = NULL;
1331 if (options.content_use && content != NULL && str_is_valid_string (content))
1332 content_pattern = g_strdup (content);
1334 init_find_vars ();
1335 push_directory (start_dir);
1337 return_value = run_process ();
1339 /* Remove all the items in the stack */
1340 clear_stack ();
1342 get_list_info (&file_tmp, &dir_tmp);
1344 if (dir_tmp)
1345 *dirname = g_strdup (dir_tmp);
1346 if (file_tmp)
1347 *filename = g_strdup (file_tmp);
1349 if (return_value == B_PANELIZE && *filename)
1351 int status, link_to_dir, stale_link;
1352 int next_free = 0;
1353 int i;
1354 struct stat st;
1355 GList *entry;
1356 dir_list *list = &current_panel->dir;
1357 char *name = NULL;
1359 for (i = 0, entry = find_list->list; entry != NULL; i++, entry = g_list_next (entry))
1361 const char *lc_filename = NULL;
1362 WLEntry *le = (WLEntry *) entry->data;
1364 if ((le->text == NULL) || (le->data == NULL))
1365 continue;
1367 if (content_pattern != NULL)
1368 lc_filename = strchr (le->text + 4, ':') + 1;
1369 else
1370 lc_filename = le->text + 4;
1372 name = make_fullname (le->data, lc_filename);
1373 status = handle_path (list, name, &st, next_free, &link_to_dir, &stale_link);
1374 if (status == 0)
1376 g_free (name);
1377 continue;
1379 if (status == -1)
1381 g_free (name);
1382 break;
1385 /* don't add files more than once to the panel */
1386 if (content_pattern != NULL && next_free > 0
1387 && strcmp (list->list[next_free - 1].fname, name) == 0)
1389 g_free (name);
1390 continue;
1393 if (!next_free) /* first turn i.e clean old list */
1394 panel_clean_dir (current_panel);
1395 list->list[next_free].fnamelen = strlen (name);
1396 list->list[next_free].fname = name;
1397 list->list[next_free].f.marked = 0;
1398 list->list[next_free].f.link_to_dir = link_to_dir;
1399 list->list[next_free].f.stale_link = stale_link;
1400 list->list[next_free].f.dir_size_computed = 0;
1401 list->list[next_free].st = st;
1402 list->list[next_free].sort_key = NULL;
1403 list->list[next_free].second_sort_key = NULL;
1404 next_free++;
1405 if (!(next_free & 15))
1406 rotate_dash ();
1409 if (next_free)
1411 current_panel->count = next_free;
1412 current_panel->is_panelized = 1;
1414 if (start_dir[0] == PATH_SEP)
1416 int ret;
1417 strcpy (current_panel->cwd, PATH_SEP_STR);
1418 ret = chdir (PATH_SEP_STR);
1423 g_free (content_pattern);
1424 kill_gui ();
1425 do_search (NULL); /* force do_search to release resources */
1426 g_free (old_dir);
1427 old_dir = NULL;
1429 return return_value;
1432 void
1433 do_find (void)
1435 char *start_dir = NULL, *pattern = NULL, *content = NULL;
1436 char *filename = NULL, *dirname = NULL;
1437 int v;
1438 gboolean dir_and_file_set;
1440 while (find_parameters (&start_dir, &pattern, &content))
1442 if (pattern[0] == '\0')
1443 break; /* nothing search */
1445 dirname = filename = NULL;
1446 is_start = FALSE;
1447 v = find_file (start_dir, pattern, content, &dirname, &filename);
1448 g_free (pattern);
1450 if (v == B_ENTER)
1452 if (dirname || filename)
1454 if (dirname)
1456 do_cd (dirname, cd_exact);
1457 if (filename)
1458 try_to_select (current_panel, filename + (content ?
1459 (strchr (filename + 4, ':') -
1460 filename + 1) : 4));
1462 else if (filename)
1463 do_cd (filename, cd_exact);
1464 select_item (current_panel);
1466 g_free (dirname);
1467 g_free (filename);
1468 break;
1471 g_free (content);
1472 dir_and_file_set = dirname && filename;
1473 g_free (dirname);
1474 g_free (filename);
1476 if (v == B_CANCEL)
1477 break;
1479 if (v == B_PANELIZE)
1481 if (dir_and_file_set)
1483 try_to_select (current_panel, NULL);
1484 panel_re_sort (current_panel);
1485 try_to_select (current_panel, NULL);
1487 break;