Merge branch '1921_lslR_fix'
[midnight-commander.git] / src / find.c
blob9f8f898e87c1d70b20f563f75bbd749c6900af2f
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 = 16;
62 #else
63 static int FIND_Y = 15;
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 {
75 B_STOP = B_USER + 1,
76 B_AGAIN,
77 B_PANELIZE,
78 B_TREE,
79 B_VIEW
82 typedef enum {
83 FIND_CONT = 0,
84 FIND_SUSPEND,
85 FIND_ABORT
86 } FindProgressStatus;
88 /* List of directories to be ignored, separated by ':' */
89 char *find_ignore_dirs = NULL;
91 /* static variables to remember find parameters */
92 static WInput *in_start; /* Start path */
93 static WInput *in_name; /* Filename */
94 static WInput *in_with; /* Text inside filename */
95 static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */
96 static WCheck *file_pattern_cbox; /* File name is glob or regexp */
97 static WCheck *recursively_cbox;
98 static WCheck *skip_hidden_cbox;
99 static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */
100 static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */
101 static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */
102 static WCheck *content_whole_words_cbox; /* "whole words" checkbox */
103 #ifdef HAVE_CHARSET
104 static WCheck *file_all_charsets_cbox;
105 static WCheck *content_all_charsets_cbox;
106 #endif
108 static gboolean running = FALSE; /* nice flag */
109 static char *find_pattern = NULL; /* Pattern to search */
110 static char *content_pattern = NULL; /* pattern to search inside files; if
111 content_regexp_flag is true, it contains the
112 regex pattern, else the search string. */
113 static unsigned long matches; /* Number of matches */
114 static gboolean is_start = FALSE; /* Status of the start/stop toggle button */
115 static char *old_dir = NULL;
117 /* Where did we stop */
118 static int resuming;
119 static int last_line;
120 static int last_pos;
122 static Dlg_head *find_dlg; /* The dialog */
123 static WButton *stop_button; /* pointer to the stop button */
124 static WLabel *status_label; /* Finished, Searching etc. */
125 static WLabel *found_num_label; /* Number of found items */
126 static WListbox *find_list; /* Listbox with the file list */
128 /* This keeps track of the directory stack */
129 #if GLIB_CHECK_VERSION (2, 14, 0)
130 static GQueue dir_queue = G_QUEUE_INIT;
131 #else
132 typedef struct dir_stack {
133 char *name;
134 struct dir_stack *prev;
135 } dir_stack;
137 static dir_stack *dir_stack_base = 0;
138 #endif /* GLIB_CHECK_VERSION */
140 static struct {
141 const char* text;
142 int len; /* length including space and brackets */
143 int x;
144 } fbuts [] = {
145 { N_("&Suspend"), 11, 29 },
146 { N_("Con&tinue"), 12, 29 },
147 { N_("&Chdir"), 11, 3 },
148 { N_("&Again"), 9, 17 },
149 { N_("&Quit"), 8, 43 },
150 { N_("Pane&lize"), 12, 3 },
151 { N_("&View - F3"), 13, 20 },
152 { N_("&Edit - F4"), 13, 38 }
155 /* find file options */
156 typedef struct
158 /* file name options */
159 gboolean file_case_sens;
160 gboolean file_pattern;
161 gboolean find_recurs;
162 gboolean skip_hidden;
163 gboolean file_all_charsets;
165 /* file content options */
166 gboolean content_case_sens;
167 gboolean content_regexp;
168 gboolean content_first_hit;
169 gboolean content_whole_words;
170 gboolean content_all_charsets;
171 } find_file_options_t;
173 static find_file_options_t options =
175 TRUE, TRUE, TRUE, FALSE, FALSE,
176 TRUE, FALSE, FALSE, FALSE, FALSE
179 static char *in_start_dir = INPUT_LAST_TEXT;
181 static mc_search_t *search_file_handle = NULL;
182 static mc_search_t *search_content_handle = NULL;
184 static void
185 find_load_options (void)
187 static gboolean loaded = FALSE;
188 char *ignore_dirs;
190 if (loaded)
191 return;
193 loaded = TRUE;
195 /* Back compatibility: try load old parameter at first */
196 ignore_dirs = mc_config_get_string (mc_main_config, "Misc", "find_ignore_dirs", "");
197 if (ignore_dirs [0] != '\0') {
198 find_ignore_dirs = g_strconcat (":", ignore_dirs, ":", (char *) NULL);
199 mc_config_set_string (mc_main_config, "FindFile", "ignore_dirs", ignore_dirs);
201 g_free (ignore_dirs);
202 mc_config_del_param (mc_main_config, "Misc", "find_ignore_dirs");
204 /* Then load new parameters */
205 ignore_dirs = mc_config_get_string (mc_main_config, "FindFile", "ignore_dirs", "");
206 if (ignore_dirs [0] != '\0') {
207 g_free (find_ignore_dirs);
208 find_ignore_dirs = g_strconcat (":", ignore_dirs, ":", (char *) NULL);
210 g_free (ignore_dirs);
212 options.file_case_sens = mc_config_get_bool (mc_main_config, "FindFile", "file_case_sens", TRUE);
213 options.file_pattern = mc_config_get_bool (mc_main_config, "FindFile", "file_shell_pattern", TRUE);
214 options.find_recurs = mc_config_get_bool (mc_main_config, "FindFile", "file_find_recurs", TRUE);
215 options.skip_hidden = mc_config_get_bool (mc_main_config, "FindFile", "file_skip_hidden", FALSE);
216 options.file_all_charsets = mc_config_get_bool (mc_main_config, "FindFile", "file_all_charsets", FALSE);
217 options.content_case_sens = mc_config_get_bool (mc_main_config, "FindFile", "content_case_sens", TRUE);
218 options.content_regexp = mc_config_get_bool (mc_main_config, "FindFile", "content_regexp", FALSE);
219 options.content_first_hit = mc_config_get_bool (mc_main_config, "FindFile", "content_first_hit", FALSE);
220 options.content_whole_words = mc_config_get_bool (mc_main_config, "FindFile", "content_whole_words", FALSE);
221 options.content_all_charsets = mc_config_get_bool (mc_main_config, "FindFile", "content_all_charsets", FALSE);
224 static void
225 find_save_options (void)
227 mc_config_set_bool (mc_main_config, "FindFile", "file_case_sens", options.file_case_sens);
228 mc_config_set_bool (mc_main_config, "FindFile", "file_shell_pattern", options.file_pattern);
229 mc_config_set_bool (mc_main_config, "FindFile", "file_find_recurs", options.find_recurs);
230 mc_config_set_bool (mc_main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
231 mc_config_set_bool (mc_main_config, "FindFile", "file_all_charsets", options.file_all_charsets);
232 mc_config_set_bool (mc_main_config, "FindFile", "content_case_sens", options.content_case_sens);
233 mc_config_set_bool (mc_main_config, "FindFile", "content_regexp", options.content_regexp);
234 mc_config_set_bool (mc_main_config, "FindFile", "content_first_hit", options.content_first_hit);
235 mc_config_set_bool (mc_main_config, "FindFile", "content_whole_words", options.content_whole_words);
236 mc_config_set_bool (mc_main_config, "FindFile", "content_all_charsets", options.content_all_charsets);
239 static inline char *
240 add_to_list (const char *text, void *data)
242 return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data);
245 static inline void
246 stop_idle (void *data)
248 set_idle_proc (data, 0);
251 static inline void
252 status_update (const char *text)
254 label_set_text (status_label, text);
257 static void
258 found_num_update (void)
260 char buffer [BUF_TINY];
261 g_snprintf (buffer, sizeof (buffer), _("Found: %ld"), matches);
262 label_set_text (found_num_label, buffer);
265 static void
266 get_list_info (char **file, char **dir)
268 listbox_get_current (find_list, file, dir);
271 /* check regular expression */
272 static gboolean
273 find_check_regexp (const char *r)
275 mc_search_t *search;
276 gboolean regexp_ok = FALSE;
278 search = mc_search_new (r, -1);
280 if (search != NULL) {
281 search->search_type = MC_SEARCH_T_REGEX;
282 regexp_ok = mc_search_prepare (search);
283 mc_search_free (search);
286 return regexp_ok;
290 * Callback for the parameter dialog.
291 * Validate regex, prevent closing the dialog if it's invalid.
293 static cb_ret_t
294 find_parm_callback (Dlg_head *h, Widget *sender,
295 dlg_msg_t msg, int parm, void *data)
297 switch (msg) {
298 case DLG_VALIDATE:
299 if (h->ret_value != B_ENTER)
300 return MSG_HANDLED;
302 /* check filename regexp */
303 if (!(file_pattern_cbox->state & C_BOOL)
304 && (in_name->buffer[0] != '\0')
305 && !find_check_regexp (in_name->buffer)) {
306 message (D_ERROR, MSG_ERROR, _(" Malformed regular expression "));
307 dlg_select_widget (in_name);
308 h->running = 1; /* Don't stop the dialog */
309 return MSG_HANDLED;
312 /* check content regexp */
313 if ((content_regexp_cbox->state & C_BOOL)
314 && (in_with->buffer[0] != '\0')
315 && !find_check_regexp (in_with->buffer)) {
316 message (D_ERROR, MSG_ERROR, _(" Malformed regular expression "));
317 dlg_select_widget (in_with);
318 h->running = 1; /* Don't stop the dialog */
319 return MSG_HANDLED;
322 return MSG_HANDLED;
324 default:
325 return default_dlg_callback (h, sender, msg, parm, data);
330 * find_parameters: gets information from the user
332 * If the return value is TRUE, then the following holds:
334 * START_DIR and PATTERN are pointers to char * and upon return they
335 * contain the information provided by the user.
337 * CONTENT holds a strdup of the contents specified by the user if he
338 * asked for them or 0 if not (note, this is different from the
339 * behavior for the other two parameters.
342 static gboolean
343 find_parameters (char **start_dir, char **pattern, char **content)
345 gboolean return_value;
347 /* file name */
348 const char *file_case_label = N_("Cas&e sensitive");
349 const char *file_pattern_label = N_("&Using shell patterns");
350 const char *file_recurs_label = N_("&Find recursively");
351 const char *file_skip_hidden_label = N_("S&kip hidden");
352 #ifdef HAVE_CHARSET
353 const char *file_all_charsets_label = N_("&All charsets");
354 #endif
356 /* file content */
357 const char *content_case_label = N_("Case sens&itive");
358 const char *content_regexp_label = N_("Re&gular expression");
359 const char *content_first_hit_label = N_("Fir&st hit");
360 const char *content_whole_words_label = N_("&Whole words");
361 #ifdef HAVE_CHARSET
362 const char *content_all_charsets_label = N_("All cha&rsets");
363 #endif
365 const char *buts[] = { N_("&OK"), N_("&Cancel"), N_("&Tree") };
367 int b0, b1, b2;
369 #ifdef ENABLE_NLS
371 int i = sizeof (buts) / sizeof (buts[0]);
372 while (i-- != 0)
373 buts[i] = _(buts[i]);
375 file_case_label = _(file_case_label);
376 file_pattern_label = _(file_pattern_label);
377 file_recurs_label = _(file_recurs_label);
378 file_skip_hidden_label = _(file_skip_hidden_label);
379 #ifdef HAVE_CHARSET
380 file_all_charsets_label = _(file_all_charsets_label);
381 content_all_charsets_label = _(content_all_charsets_label);
382 #endif
383 content_case_label = _(content_case_label);
384 content_regexp_label = _(content_regexp_label);
385 content_first_hit_label = _(content_first_hit_label);
386 content_whole_words_label = _(content_whole_words_label);
388 #endif /* ENABLE_NLS */
390 b0 = str_term_width1 (buts[0]) + 6; /* default button */
391 b1 = str_term_width1 (buts[1]) + 4;
392 b2 = str_term_width1 (buts[2]) + 4;
394 find_load_options ();
396 if (in_start_dir == NULL)
397 in_start_dir = g_strdup (".");
399 find_par_start:
400 find_dlg =
401 create_dlg (0, 0, FIND_Y, FIND_X, dialog_colors,
402 find_parm_callback, "[Find File]", _("Find File"),
403 DLG_CENTER | DLG_REVERSE);
405 add_widget (find_dlg,
406 button_new (FIND_Y - 3, FIND_X * 3/4 - b1/2, B_CANCEL, NORMAL_BUTTON, buts[1], 0));
407 add_widget (find_dlg,
408 button_new (FIND_Y - 3, FIND_X/4 - b0/2, B_ENTER, DEFPUSH_BUTTON, buts[0], 0));
410 #ifdef HAVE_CHARSET
411 content_all_charsets_cbox = check_new (11, FIND_X / 2 + 1,
412 options.content_all_charsets, content_all_charsets_label);
413 add_widget (find_dlg, content_all_charsets_cbox);
414 #endif
416 content_whole_words_cbox = check_new (10, FIND_X / 2 + 1, options.content_whole_words, content_whole_words_label);
417 add_widget (find_dlg, content_whole_words_cbox);
419 content_first_hit_cbox = check_new (9, FIND_X / 2 + 1, options.content_first_hit, content_first_hit_label);
420 add_widget (find_dlg, content_first_hit_cbox);
422 content_regexp_cbox = check_new (8, FIND_X / 2 + 1, options.content_regexp, content_regexp_label);
423 add_widget (find_dlg, content_regexp_cbox);
425 content_case_sens_cbox = check_new (7, FIND_X / 2 + 1, options.content_case_sens, content_case_label);
426 add_widget (find_dlg, content_case_sens_cbox);
428 #ifdef HAVE_CHARSET
429 file_all_charsets_cbox = check_new (11, 3,
430 options.file_all_charsets, file_all_charsets_label);
431 add_widget (find_dlg, file_all_charsets_cbox);
432 #endif
434 skip_hidden_cbox = check_new (10, 3, options.skip_hidden, file_skip_hidden_label);
435 add_widget (find_dlg, skip_hidden_cbox);
437 recursively_cbox = check_new (9, 3, options.find_recurs, file_recurs_label);
438 add_widget (find_dlg, recursively_cbox);
440 file_pattern_cbox = check_new (8, 3, options.file_pattern, file_pattern_label);
441 add_widget (find_dlg, file_pattern_cbox);
443 file_case_sens_cbox = check_new (7, 3, options.file_case_sens, file_case_label);
444 add_widget (find_dlg, file_case_sens_cbox);
446 in_with = input_new (6, FIND_X / 2 + 1, INPUT_COLOR, FIND_X / 2 - 4, INPUT_LAST_TEXT,
447 MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_DEFAULT);
448 add_widget (find_dlg, in_with);
449 add_widget (find_dlg, label_new (5, FIND_X / 2 + 1, _("Content:")));
451 in_name = input_new (6, 3, INPUT_COLOR, FIND_X / 2 - 4, INPUT_LAST_TEXT, "name",
452 INPUT_COMPLETE_DEFAULT);
453 add_widget (find_dlg, in_name);
454 add_widget (find_dlg, label_new (5, 3, _("File name:")));
456 add_widget (find_dlg,
457 button_new (3, FIND_X - b2 - 2, B_TREE, NORMAL_BUTTON, buts[2], 0));
459 in_start = input_new (3, 3, INPUT_COLOR, FIND_X - b2 - 6, in_start_dir, "start",
460 INPUT_COMPLETE_DEFAULT);
461 add_widget (find_dlg, in_start);
462 add_widget (find_dlg, label_new (2, 3, _("Start at:")));
464 dlg_select_widget (in_name);
466 switch (run_dlg (find_dlg)) {
467 case B_CANCEL:
468 return_value = FALSE;
469 break;
471 case B_TREE:
473 char temp_dir[MC_MAXPATHLEN];
475 g_strlcpy (temp_dir, in_start->buffer, sizeof (temp_dir));
476 #ifdef HAVE_CHARSET
477 options.file_all_charsets = file_all_charsets_cbox->state & C_BOOL;
478 options.content_all_charsets = content_all_charsets_cbox->state & C_BOOL;
479 #endif
480 options.content_case_sens = content_case_sens_cbox->state & C_BOOL;
481 options.content_regexp = content_regexp_cbox->state & C_BOOL;
482 options.content_first_hit = content_first_hit_cbox->state & C_BOOL;
483 options.content_whole_words = content_whole_words_cbox->state & C_BOOL;
484 options.file_pattern = file_pattern_cbox->state & C_BOOL;
485 options.file_case_sens = file_case_sens_cbox->state & C_BOOL;
486 options.find_recurs = recursively_cbox->state & C_BOOL;
487 options.skip_hidden = skip_hidden_cbox->state & C_BOOL;
488 destroy_dlg (find_dlg);
490 if ((temp_dir[0] == '\0')
491 || ((temp_dir[0] == '.') && (temp_dir[1] == '\0')))
492 g_strlcpy (temp_dir, current_panel->cwd, sizeof (temp_dir));
494 if (in_start_dir != INPUT_LAST_TEXT)
495 g_free (in_start_dir);
496 in_start_dir = tree_box (temp_dir);
497 if (in_start_dir == NULL)
498 in_start_dir = g_strdup (temp_dir);
499 /* Warning: Dreadful goto */
500 goto find_par_start;
501 break;
504 default:
505 #ifdef HAVE_CHARSET
506 options.file_all_charsets = file_all_charsets_cbox->state & C_BOOL;
507 options.content_all_charsets = content_all_charsets_cbox->state & C_BOOL;
508 #endif
509 options.content_case_sens = content_case_sens_cbox->state & C_BOOL;
510 options.content_regexp = content_regexp_cbox->state & C_BOOL;
511 options.content_first_hit = content_first_hit_cbox->state & C_BOOL;
512 options.content_whole_words = content_whole_words_cbox->state & C_BOOL;
513 options.find_recurs = recursively_cbox->state & C_BOOL;
514 options.file_pattern = file_pattern_cbox->state & C_BOOL;
515 options.file_case_sens = file_case_sens_cbox->state & C_BOOL;
516 options.skip_hidden = skip_hidden_cbox->state & C_BOOL;
518 *content = (in_with->buffer[0] != '\0') ? g_strdup (in_with->buffer) : NULL;
519 *start_dir = g_strdup ((in_start->buffer[0] != '\0') ? in_start->buffer : ".");
520 *pattern = g_strdup (in_name->buffer);
521 if (in_start_dir != INPUT_LAST_TEXT)
522 g_free (in_start_dir);
523 in_start_dir = g_strdup (*start_dir);
525 find_save_options ();
527 return_value = TRUE;
530 destroy_dlg (find_dlg);
532 return return_value;
535 #if GLIB_CHECK_VERSION (2, 14, 0)
537 static inline void
538 push_directory (const char *dir)
540 g_queue_push_head (&dir_queue, (void *) dir);
543 static inline char *
544 pop_directory (void)
546 return (char *) g_queue_pop_tail (&dir_queue);
549 /* Remove all the items in the stack */
550 static void
551 clear_stack (void)
553 g_queue_foreach (&dir_queue, (GFunc) g_free, NULL);
554 g_queue_clear (&dir_queue);
557 #else /* GLIB_CHAECK_VERSION */
559 static void
560 push_directory (const char *dir)
562 dir_stack *new;
564 new = g_new (dir_stack, 1);
565 new->name = str_unconst (dir);
566 new->prev = dir_stack_base;
567 dir_stack_base = new;
570 static char *
571 pop_directory (void)
573 char *name = NULL;
575 if (dir_stack_base != NULL) {
576 dir_stack *next;
577 name = dir_stack_base->name;
578 next = dir_stack_base->prev;
579 g_free (dir_stack_base);
580 dir_stack_base = next;
583 return name;
586 /* Remove all the items in the stack */
587 static void
588 clear_stack (void)
590 char *dir = NULL;
591 while ((dir = pop_directory ()) != NULL)
592 g_free (dir);
595 #endif /* GLIB_CHAECK_VERSION */
597 static void
598 insert_file (const char *dir, const char *file)
600 char *tmp_name = NULL;
601 static char *dirname = NULL;
603 while (dir [0] == PATH_SEP && dir [1] == PATH_SEP)
604 dir++;
606 if (old_dir){
607 if (strcmp (old_dir, dir)){
608 g_free (old_dir);
609 old_dir = g_strdup (dir);
610 dirname = add_to_list (dir, NULL);
612 } else {
613 old_dir = g_strdup (dir);
614 dirname = add_to_list (dir, NULL);
617 tmp_name = g_strdup_printf (" %s", file);
618 add_to_list (tmp_name, dirname);
619 g_free (tmp_name);
622 static void
623 find_add_match (const char *dir, const char *file)
625 insert_file (dir, file);
627 /* Don't scroll */
628 if (matches == 0)
629 listbox_select_by_number (find_list, 0);
630 send_message (&find_list->widget, WIDGET_DRAW, 0);
632 matches++;
633 found_num_update ();
637 * get_line_at:
639 * Returns malloced null-terminated line from file file_fd.
640 * Input is buffered in buf_size long buffer.
641 * Current pos in buf is stored in pos.
642 * n_read - number of read chars.
643 * has_newline - is there newline ?
645 static char *
646 get_line_at (int file_fd, char *buf, int buf_size, int *pos, int *n_read,
647 gboolean *has_newline)
649 char *buffer = NULL;
650 int buffer_size = 0;
651 char ch = 0;
652 int i = 0;
654 for (;;) {
655 if (*pos >= *n_read) {
656 *pos = 0;
657 *n_read = mc_read (file_fd, buf, buf_size);
658 if (*n_read <= 0)
659 break;
662 ch = buf[(*pos)++];
663 if (ch == '\0') {
664 /* skip possible leading zero(s) */
665 if (i == 0)
666 continue;
667 else
668 break;
671 if (i >= buffer_size - 1) {
672 buffer = g_realloc (buffer, buffer_size += 80);
674 /* Strip newline */
675 if (ch == '\n')
676 break;
678 buffer[i++] = ch;
681 *has_newline = (ch != '\0');
683 if (buffer != NULL)
684 buffer[i] = '\0';
686 return buffer;
689 static FindProgressStatus
690 check_find_events(Dlg_head *h)
692 Gpm_Event event;
693 int c;
695 event.x = -1;
696 c = tty_get_event (&event, h->mouse_status == MOU_REPEAT, FALSE);
697 if (c != EV_NONE) {
698 dlg_process_event (h, c, &event);
699 if (h->ret_value == B_ENTER
700 || h->ret_value == B_CANCEL
701 || h->ret_value == B_AGAIN
702 || h->ret_value == B_PANELIZE) {
703 /* dialog terminated */
704 return FIND_ABORT;
706 if (!(h->flags & DLG_WANT_IDLE)) {
707 /* searching suspended */
708 return FIND_SUSPEND;
712 return FIND_CONT;
716 * search_content:
718 * Search the content_pattern string in the DIRECTORY/FILE.
719 * It will add the found entries to the find listbox.
721 * returns FALSE if do_search should look for another file
722 * TRUE if do_search should exit and proceed to the event handler
724 static gboolean
725 search_content (Dlg_head *h, const char *directory, const char *filename)
727 struct stat s;
728 char buffer [BUF_4K];
729 char *fname = NULL;
730 int file_fd;
731 gboolean ret_val = FALSE;
733 fname = concat_dir_and_file (directory, filename);
735 if (mc_stat (fname, &s) != 0 || !S_ISREG (s.st_mode)){
736 g_free (fname);
737 return 0;
740 file_fd = mc_open (fname, O_RDONLY);
741 g_free (fname);
743 if (file_fd == -1)
744 return 0;
746 g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), str_trunc (filename, FIND2_X_USE));
748 status_update (buffer);
749 mc_refresh ();
751 tty_enable_interrupt_key ();
752 tty_got_interrupt ();
755 int line = 1;
756 int pos = 0;
757 int n_read = 0;
758 gboolean has_newline;
759 char *p = NULL;
760 gboolean found = FALSE;
761 gsize found_len;
762 char result [BUF_MEDIUM];
764 if (resuming) {
765 /* We've been previously suspended, start from the previous position */
766 resuming = 0;
767 line = last_line;
768 pos = last_pos;
770 while (!ret_val
771 && (p = get_line_at (file_fd, buffer, sizeof (buffer),
772 &pos, &n_read, &has_newline)) != NULL) {
773 if (!found /* Search in binary line once */
774 && mc_search_run (search_content_handle,
775 (const void *) p, 0, strlen (p), &found_len)) {
776 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
777 find_add_match (directory, result);
778 found = TRUE;
780 g_free (p);
782 if (found && options.content_first_hit)
783 break;
785 if (has_newline) {
786 found = FALSE;
787 line++;
790 if ((line & 0xff) == 0) {
791 FindProgressStatus res;
792 res = check_find_events(h);
793 switch (res) {
794 case FIND_ABORT:
795 stop_idle (h);
796 ret_val = TRUE;
797 break;
798 case FIND_SUSPEND:
799 resuming = 1;
800 last_line = line;
801 last_pos = pos;
802 ret_val = TRUE;
803 break;
804 default:
805 break;
811 tty_disable_interrupt_key ();
812 mc_close (file_fd);
813 return ret_val;
816 static int
817 do_search (struct Dlg_head *h)
819 static struct dirent *dp = NULL;
820 static DIR *dirp = NULL;
821 static char *directory = NULL;
822 struct stat tmp_stat;
823 static int pos = 0;
824 static int subdirs_left = 0;
825 gsize bytes_found;
826 unsigned long count; /* Number of files displayed */
828 if (!h) { /* someone forces me to close dirp */
829 if (dirp) {
830 mc_closedir (dirp);
831 dirp = NULL;
833 g_free (directory);
834 directory = NULL;
835 dp = NULL;
836 return 1;
839 search_content_handle = mc_search_new(content_pattern, -1);
840 if (search_content_handle) {
841 search_content_handle->search_type = options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
842 search_content_handle->is_case_sentitive = options.content_case_sens;
843 search_content_handle->whole_words = options.content_whole_words;
844 search_content_handle->is_all_charsets = options.content_all_charsets;
846 search_file_handle = mc_search_new(find_pattern, -1);
847 search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
848 search_file_handle->is_case_sentitive = options.file_case_sens;
849 search_file_handle->is_all_charsets = options.file_all_charsets;
850 search_file_handle->is_entire_line = options.file_pattern;
852 count = 0;
854 do_search_begin:
855 while (!dp){
856 if (dirp){
857 mc_closedir (dirp);
858 dirp = 0;
861 while (!dirp){
862 char *tmp = NULL;
864 tty_setcolor (REVERSE_COLOR);
865 while (1) {
866 char *temp_dir = NULL;
867 gboolean found;
869 tmp = pop_directory ();
870 if (tmp == NULL) {
871 running = FALSE;
872 status_update (_("Finished"));
873 stop_idle (h);
874 mc_search_free (search_file_handle);
875 search_file_handle = NULL;
876 mc_search_free (search_content_handle);
877 search_content_handle = NULL;
878 return 0;
881 if ((find_ignore_dirs == NULL) || (find_ignore_dirs[0] == '\0'))
882 break;
884 temp_dir = g_strdup_printf (":%s:", tmp);
885 found = strstr (find_ignore_dirs, temp_dir) != 0;
886 g_free (temp_dir);
888 if (!found)
889 break;
891 g_free (tmp);
894 g_free (directory);
895 directory = tmp;
897 if (verbose){
898 char buffer [BUF_SMALL];
900 g_snprintf (buffer, sizeof (buffer), _("Searching %s"),
901 str_trunc (directory, FIND2_X_USE));
902 status_update (buffer);
904 /* mc_stat should not be called after mc_opendir
905 because vfs_s_opendir modifies the st_nlink
907 if (!mc_stat (directory, &tmp_stat))
908 subdirs_left = tmp_stat.st_nlink - 2;
909 else
910 subdirs_left = 0;
912 dirp = mc_opendir (directory);
913 } /* while (!dirp) */
915 /* skip invalid filenames */
916 while ((dp = mc_readdir (dirp)) != NULL
917 && !str_is_valid_string (dp->d_name))
919 } /* while (!dp) */
921 if (strcmp (dp->d_name, ".") == 0 ||
922 strcmp (dp->d_name, "..") == 0){
923 dp = mc_readdir (dirp);
924 /* skip invalid filenames */
925 while (dp != NULL && !str_is_valid_string (dp->d_name))
926 dp = mc_readdir (dirp);
928 mc_search_free(search_file_handle);
929 search_file_handle = NULL;
930 mc_search_free(search_content_handle);
931 search_content_handle = NULL;
932 return 1;
935 if (!(options.skip_hidden && (dp->d_name[0] == '.'))) {
936 gboolean search_ok;
938 if ((subdirs_left != 0) && options.find_recurs
939 && (directory != NULL)) { /* Can directory be NULL ? */
940 char *tmp_name = concat_dir_and_file (directory, dp->d_name);
941 if (!mc_lstat (tmp_name, &tmp_stat)
942 && S_ISDIR (tmp_stat.st_mode)) {
943 push_directory (tmp_name);
944 subdirs_left--;
945 } else
946 g_free (tmp_name);
949 search_ok = mc_search_run (search_file_handle, dp->d_name,
950 0, strlen (dp->d_name), &bytes_found);
952 if (search_ok) {
953 if (content_pattern == NULL)
954 find_add_match (directory, dp->d_name);
955 else if (search_content (h, directory, dp->d_name)) {
956 mc_search_free(search_file_handle);
957 search_file_handle = NULL;
958 mc_search_free(search_content_handle);
959 search_content_handle = NULL;
960 return 1;
965 /* skip invalid filenames */
966 while ((dp = mc_readdir (dirp)) != NULL
967 && !str_is_valid_string (dp->d_name))
970 /* Displays the nice dot */
971 count++;
972 if (!(count & 31)){
973 /* For nice updating */
974 const char rotating_dash[] = "|/-\\";
976 if (verbose){
977 pos = (pos + 1) % 4;
978 tty_setcolor (DLG_NORMALC (h));
979 dlg_move (h, FIND2_Y - 7, FIND2_X - 4);
980 tty_print_char (rotating_dash [pos]);
981 mc_refresh ();
983 } else
984 goto do_search_begin;
986 mc_search_free (search_file_handle);
987 search_file_handle = NULL;
988 mc_search_free (search_content_handle);
989 search_content_handle = NULL;
990 return 1;
993 static void
994 init_find_vars (void)
996 g_free (old_dir);
997 old_dir = NULL;
998 matches = 0;
1000 /* Remove all the items in the stack */
1001 clear_stack ();
1004 static char *
1005 make_fullname (const char *dirname, const char *filename)
1008 if (strcmp(dirname, ".") == 0 || strcmp(dirname, "."PATH_SEP_STR) == 0)
1009 return g_strdup (filename);
1010 if (strncmp(dirname, "."PATH_SEP_STR, 2) == 0)
1011 return concat_dir_and_file (dirname + 2, filename);
1012 return concat_dir_and_file (dirname, filename);
1015 static void
1016 find_do_view_edit (int unparsed_view, int edit, char *dir, char *file)
1018 char *fullname = NULL;
1019 const char *filename = NULL;
1020 int line;
1022 if (content_pattern != NULL) {
1023 filename = strchr (file + 4, ':') + 1;
1024 line = atoi (file + 4);
1025 } else {
1026 filename = file + 4;
1027 line = 0;
1030 fullname = make_fullname (dir, filename);
1031 if (edit)
1032 do_edit_at_line (fullname, line);
1033 else
1034 view_file_at_line (fullname, unparsed_view, use_internal_view, line);
1035 g_free (fullname);
1038 static cb_ret_t
1039 view_edit_currently_selected_file (int unparsed_view, int edit)
1041 WLEntry *entry = find_list->current;
1042 char *dir = NULL;
1044 if (!entry)
1045 return MSG_NOT_HANDLED;
1047 dir = entry->data;
1049 if (!entry->text || !dir)
1050 return MSG_NOT_HANDLED;
1052 find_do_view_edit (unparsed_view, edit, dir, entry->text);
1053 return MSG_HANDLED;
1056 static cb_ret_t
1057 find_callback (struct Dlg_head *h, Widget *sender,
1058 dlg_msg_t msg, int parm, void *data)
1060 switch (msg) {
1061 case DLG_KEY:
1062 if (parm == KEY_F (3) || parm == KEY_F (13)) {
1063 int unparsed_view = (parm == KEY_F (13));
1064 return view_edit_currently_selected_file (unparsed_view, 0);
1066 if (parm == KEY_F (4)) {
1067 return view_edit_currently_selected_file (0, 1);
1069 return MSG_NOT_HANDLED;
1071 case DLG_IDLE:
1072 do_search (h);
1073 return MSG_HANDLED;
1075 default:
1076 return default_dlg_callback (h, sender, msg, parm, data);
1080 /* Handles the Stop/Start button in the find window */
1081 static int
1082 start_stop (int button)
1084 (void) button;
1086 running = is_start;
1087 set_idle_proc (find_dlg, running);
1088 is_start = !is_start;
1090 status_update (is_start ? _("Stopped") : _("Searching"));
1091 button_set_text (stop_button, fbuts [is_start ? 1 : 0].text);
1093 return 0;
1096 /* Handle view command, when invoked as a button */
1097 static int
1098 find_do_view_file (int button)
1100 (void) button;
1102 view_edit_currently_selected_file (0, 0);
1103 return 0;
1106 /* Handle edit command, when invoked as a button */
1107 static int
1108 find_do_edit_file (int button)
1110 (void) button;
1112 view_edit_currently_selected_file (0, 1);
1113 return 0;
1116 static void
1117 setup_gui (void)
1119 #ifdef ENABLE_NLS
1120 static gboolean i18n_flag = FALSE;
1122 if (!i18n_flag) {
1123 int i = sizeof (fbuts) / sizeof (fbuts[0]);
1124 while (i-- != 0) {
1125 fbuts[i].text = _(fbuts[i].text);
1126 fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1129 fbuts[2].len += 2; /* DEFPUSH_BUTTON */
1130 i18n_flag = TRUE;
1132 #endif /* ENABLE_NLS */
1135 * Dynamically place buttons centered within current window size
1138 int l0 = max (fbuts[0].len, fbuts[1].len);
1139 int l1 = fbuts[2].len + fbuts[3].len + l0 + fbuts[4].len;
1140 int l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len;
1141 int r1, r2;
1143 /* Check, if both button rows fit within FIND2_X */
1144 FIND2_X = max (l1 + 9, COLS - 16);
1145 FIND2_X = max (l2 + 8, FIND2_X);
1147 /* compute amount of space between buttons for each row */
1148 r1 = (FIND2_X - 4 - l1) % 5;
1149 l1 = (FIND2_X - 4 - l1) / 5;
1150 r2 = (FIND2_X - 4 - l2) % 4;
1151 l2 = (FIND2_X - 4 - l2) / 4;
1153 /* ...and finally, place buttons */
1154 fbuts[2].x = 2 + r1 / 2 + l1;
1155 fbuts[3].x = fbuts[2].x + fbuts[2].len + l1;
1156 fbuts[0].x = fbuts[3].x + fbuts[3].len + l1;
1157 fbuts[4].x = fbuts[0].x + l0 + l1;
1158 fbuts[5].x = 2 + r2 / 2 + l2;
1159 fbuts[6].x = fbuts[5].x + fbuts[5].len + l2;
1160 fbuts[7].x = fbuts[6].x + fbuts[6].len + l2;
1163 find_dlg =
1164 create_dlg (0, 0, FIND2_Y, FIND2_X, dialog_colors, find_callback,
1165 "[Find File]", _("Find File"), DLG_CENTER | DLG_REVERSE);
1167 add_widget (find_dlg,
1168 button_new (FIND2_Y - 3, fbuts[7].x, B_VIEW, NORMAL_BUTTON,
1169 fbuts[7].text, find_do_edit_file));
1170 add_widget (find_dlg,
1171 button_new (FIND2_Y - 3, fbuts[6].x, B_VIEW, NORMAL_BUTTON,
1172 fbuts[6].text, find_do_view_file));
1173 add_widget (find_dlg,
1174 button_new (FIND2_Y - 3, fbuts[5].x, B_PANELIZE,
1175 NORMAL_BUTTON, fbuts[5].text, 0));
1177 add_widget (find_dlg,
1178 button_new (FIND2_Y - 4, fbuts[4].x, B_CANCEL,
1179 NORMAL_BUTTON, fbuts[4].text, 0));
1180 stop_button =
1181 button_new (FIND2_Y - 4, fbuts[0].x, B_STOP, NORMAL_BUTTON,
1182 fbuts[0].text, start_stop);
1183 add_widget (find_dlg, stop_button);
1184 add_widget (find_dlg,
1185 button_new (FIND2_Y - 4, fbuts[3].x, B_AGAIN,
1186 NORMAL_BUTTON, fbuts[3].text, 0));
1187 add_widget (find_dlg,
1188 button_new (FIND2_Y - 4, fbuts[2].x, B_ENTER,
1189 DEFPUSH_BUTTON, fbuts[2].text, 0));
1191 status_label = label_new (FIND2_Y - 7, 4, _("Searching"));
1192 add_widget (find_dlg, status_label);
1194 found_num_label = label_new (FIND2_Y - 6, 4, "");
1195 add_widget (find_dlg, found_num_label);
1197 find_list =
1198 listbox_new (2, 2, FIND2_Y - 10, FIND2_X - 4, NULL);
1199 add_widget (find_dlg, find_list);
1202 static int
1203 run_process (void)
1205 resuming = 0;
1206 set_idle_proc (find_dlg, 1);
1207 return run_dlg (find_dlg);
1210 static void
1211 kill_gui (void)
1213 set_idle_proc (find_dlg, 0);
1214 destroy_dlg (find_dlg);
1217 static int
1218 find_file (const char *start_dir, const char *pattern, const char *content,
1219 char **dirname, char **filename)
1221 int return_value = 0;
1222 char *dir_tmp = NULL, *file_tmp = NULL;
1224 setup_gui ();
1226 /* FIXME: Need to cleanup this, this ought to be passed non-globaly */
1227 find_pattern = str_unconst (pattern);
1228 content_pattern = (content != NULL && str_is_valid_string (content))
1229 ? g_strdup(content)
1230 : NULL;
1232 init_find_vars ();
1233 push_directory (start_dir);
1235 return_value = run_process ();
1237 /* Remove all the items in the stack */
1238 clear_stack ();
1240 get_list_info (&file_tmp, &dir_tmp);
1242 if (dir_tmp)
1243 *dirname = g_strdup (dir_tmp);
1244 if (file_tmp)
1245 *filename = g_strdup (file_tmp);
1247 if (return_value == B_PANELIZE && *filename) {
1248 int status, link_to_dir, stale_link;
1249 int next_free = 0;
1250 int i;
1251 struct stat st;
1252 WLEntry *entry = find_list->list;
1253 dir_list *list = &current_panel->dir;
1254 char *name = NULL;
1256 for (i = 0; entry != NULL && i < find_list->count;
1257 entry = entry->next, i++) {
1258 const char *lc_filename = NULL;
1260 if (!entry->text || !entry->data)
1261 continue;
1263 if (content_pattern != NULL)
1264 lc_filename = strchr (entry->text + 4, ':') + 1;
1265 else
1266 lc_filename = entry->text + 4;
1268 name = make_fullname (entry->data, lc_filename);
1269 status =
1270 handle_path (list, name, &st, next_free, &link_to_dir,
1271 &stale_link);
1272 if (status == 0) {
1273 g_free (name);
1274 continue;
1276 if (status == -1) {
1277 g_free (name);
1278 break;
1281 /* don't add files more than once to the panel */
1282 if (content_pattern != NULL && next_free > 0
1283 && strcmp (list->list[next_free - 1].fname, name) == 0) {
1284 g_free (name);
1285 continue;
1288 if (!next_free) /* first turn i.e clean old list */
1289 panel_clean_dir (current_panel);
1290 list->list[next_free].fnamelen = strlen (name);
1291 list->list[next_free].fname = name;
1292 list->list[next_free].f.marked = 0;
1293 list->list[next_free].f.link_to_dir = link_to_dir;
1294 list->list[next_free].f.stale_link = stale_link;
1295 list->list[next_free].f.dir_size_computed = 0;
1296 list->list[next_free].st = st;
1297 list->list[next_free].sort_key = NULL;
1298 list->list[next_free].second_sort_key = NULL;
1299 next_free++;
1300 if (!(next_free & 15))
1301 rotate_dash ();
1304 if (next_free) {
1305 current_panel->count = next_free;
1306 current_panel->is_panelized = 1;
1308 if (start_dir[0] == PATH_SEP) {
1309 strcpy (current_panel->cwd, PATH_SEP_STR);
1310 chdir (PATH_SEP_STR);
1315 g_free (content_pattern);
1316 kill_gui ();
1317 do_search (NULL); /* force do_search to release resources */
1318 g_free (old_dir);
1319 old_dir = NULL;
1321 return return_value;
1324 void
1325 do_find (void)
1327 char *start_dir = NULL, *pattern = NULL, *content = NULL;
1328 char *filename = NULL, *dirname = NULL;
1329 int v;
1330 gboolean dir_and_file_set;
1332 while (find_parameters (&start_dir, &pattern, &content)){
1333 if (pattern [0] == '\0')
1334 break; /* nothing search*/
1336 dirname = filename = NULL;
1337 is_start = FALSE;
1338 v = find_file (start_dir, pattern, content, &dirname, &filename);
1339 g_free (pattern);
1341 if (v == B_ENTER){
1342 if (dirname || filename){
1343 if (dirname){
1344 do_cd (dirname, cd_exact);
1345 if (filename)
1346 try_to_select (current_panel, filename + (content ?
1347 (strchr (filename + 4, ':') - filename + 1) : 4) );
1348 } else if (filename)
1349 do_cd (filename, cd_exact);
1350 select_item (current_panel);
1352 g_free (dirname);
1353 g_free (filename);
1354 break;
1357 g_free (content);
1358 dir_and_file_set = dirname && filename;
1359 g_free (dirname);
1360 g_free (filename);
1362 if (v == B_CANCEL)
1363 break;
1365 if (v == B_PANELIZE){
1366 if (dir_and_file_set){
1367 try_to_select (current_panel, NULL);
1368 panel_re_sort (current_panel);
1369 try_to_select (current_panel, NULL);
1371 break;