Ticket #2229: speed up of up/down moving in viewer in wrapped mode.
[midnight-commander.git] / src / find.c
blob04d82b4aa56dc5dca362e8b1d01c5e94bc54b86c
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 rewrite.
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/util.h" /* canonicalize_pathname() */
38 #include "lib/tty/tty.h"
39 #include "lib/tty/key.h"
40 #include "lib/skin.h"
41 #include "lib/search.h"
42 #include "lib/mcconfig.h"
43 #include "lib/vfs/mc-vfs/vfs.h"
44 #include "lib/strutil.h"
46 #include "setup.h" /* verbose */
47 #include "dialog.h"
48 #include "widget.h"
49 #include "dir.h"
50 #include "panel.h" /* current_panel */
51 #include "main.h" /* do_cd, try_to_select */
52 #include "wtools.h"
53 #include "cmd.h" /* view_file_at_line */
54 #include "boxes.h"
55 #include "history.h" /* MC_HISTORY_SHARED_SEARCH */
56 #include "layout.h" /* mc_refresh() */
58 #include "find.h"
60 /* Size of the find parameters window */
61 #if HAVE_CHARSET
62 static int FIND_Y = 17;
63 #else
64 static int FIND_Y = 16;
65 #endif
66 static int FIND_X = 68;
68 /* Size of the find window */
69 #define FIND2_Y (LINES - 4)
71 static int FIND2_X = 64;
72 #define FIND2_X_USE (FIND2_X - 20)
74 /* A couple of extra messages we need */
75 enum
77 B_STOP = B_USER + 1,
78 B_AGAIN,
79 B_PANELIZE,
80 B_TREE,
81 B_VIEW
84 typedef enum
86 FIND_CONT = 0,
87 FIND_SUSPEND,
88 FIND_ABORT
89 } FindProgressStatus;
91 /* List of directories to be ignored, separated by ':' */
92 char **find_ignore_dirs = NULL;
94 /* static variables to remember find parameters */
95 static WInput *in_start; /* Start path */
96 static WInput *in_name; /* Filename */
97 static WInput *in_with; /* Text */
98 static WLabel *content_label; /* 'Content:' label */
99 static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */
100 static WCheck *file_pattern_cbox; /* File name is glob or regexp */
101 static WCheck *recursively_cbox;
102 static WCheck *skip_hidden_cbox;
103 static WCheck *content_use_cbox; /* Take into account the Content field */
104 static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */
105 static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */
106 static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */
107 static WCheck *content_whole_words_cbox; /* "whole words" checkbox */
108 #ifdef HAVE_CHARSET
109 static WCheck *file_all_charsets_cbox;
110 static WCheck *content_all_charsets_cbox;
111 #endif
113 static gboolean running = FALSE; /* nice flag */
114 static char *find_pattern = NULL; /* Pattern to search */
115 static char *content_pattern = NULL; /* pattern to search inside files; if
116 content_regexp_flag is true, it contains the
117 regex pattern, else the search string. */
118 static unsigned long matches; /* Number of matches */
119 static gboolean is_start = FALSE; /* Status of the start/stop toggle button */
120 static char *old_dir = NULL;
122 /* Where did we stop */
123 static int resuming;
124 static int last_line;
125 static int last_pos;
127 static Dlg_head *find_dlg; /* The dialog */
128 static WButton *stop_button; /* pointer to the stop button */
129 static WLabel *status_label; /* Finished, Searching etc. */
130 static WLabel *found_num_label; /* Number of found items */
131 static WListbox *find_list; /* Listbox with the file list */
133 /* This keeps track of the directory stack */
134 #if GLIB_CHECK_VERSION (2, 14, 0)
135 static GQueue dir_queue = G_QUEUE_INIT;
136 #else
137 typedef struct dir_stack
139 char *name;
140 struct dir_stack *prev;
141 } dir_stack;
143 static dir_stack *dir_stack_base = 0;
144 #endif /* GLIB_CHECK_VERSION */
146 /* *INDENT-OFF* */
147 static struct
149 const char *text;
150 int len; /* length including space and brackets */
151 int x;
152 } fbuts[] =
154 {N_("&Suspend"), 11, 29},
155 {N_("Con&tinue"), 12, 29},
156 {N_("&Chdir"), 11, 3},
157 {N_("&Again"), 9, 17},
158 {N_("&Quit"), 8, 43},
159 {N_("Pane&lize"), 12, 3},
160 {N_("&View - F3"), 13, 20},
161 {N_("&Edit - F4"), 13, 38}
163 /* *INDENT-ON* */
165 /* find file options */
166 typedef struct
168 /* file name options */
169 gboolean file_case_sens;
170 gboolean file_pattern;
171 gboolean find_recurs;
172 gboolean skip_hidden;
173 gboolean file_all_charsets;
175 /* file content options */
176 gboolean content_use;
177 gboolean content_case_sens;
178 gboolean content_regexp;
179 gboolean content_first_hit;
180 gboolean content_whole_words;
181 gboolean content_all_charsets;
182 } find_file_options_t;
184 static find_file_options_t options = {
185 TRUE, TRUE, TRUE, FALSE, FALSE,
186 FALSE, TRUE, FALSE, FALSE, FALSE, FALSE
189 static char *in_start_dir = INPUT_LAST_TEXT;
191 static mc_search_t *search_file_handle = NULL;
192 static mc_search_t *search_content_handle = NULL;
194 static int
195 find_ignore_dirs_cmp (const void *d1, const void *d2)
197 return strcmp (*(const char **) d1, *(const char **) d2);
200 static void
201 find_load_options (void)
203 static gboolean loaded = FALSE;
204 char *ignore_dirs;
206 if (loaded)
207 return;
209 loaded = TRUE;
211 /* Back compatibility: try load old parameter at first */
212 ignore_dirs = mc_config_get_string (mc_main_config, "Misc", "find_ignore_dirs", "");
213 if (ignore_dirs[0] != '\0')
215 find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
216 mc_config_set_string (mc_main_config, "FindFile", "ignore_dirs", ignore_dirs);
218 g_free (ignore_dirs);
219 mc_config_del_param (mc_main_config, "Misc", "find_ignore_dirs");
221 /* Then load new parameters */
222 ignore_dirs = mc_config_get_string (mc_main_config, "FindFile", "ignore_dirs", "");
223 if (ignore_dirs[0] != '\0')
225 g_strfreev (find_ignore_dirs);
226 find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
228 g_free (ignore_dirs);
230 if (find_ignore_dirs != NULL)
232 /* Values like '/foo::/bar: produce holes in list.
233 Find and remove them */
234 size_t r = 0, w = 0; /* read and write iterators */
236 for (; find_ignore_dirs[r] != NULL; r++)
238 if (find_ignore_dirs [r][0] == '\0')
240 /* empty entry -- skip it */
241 g_free (find_ignore_dirs [r]);
242 find_ignore_dirs [r] = NULL;
243 continue;
246 if (r != w)
248 /* copy entry to the previous free array cell */
249 find_ignore_dirs [w] = find_ignore_dirs [r];
250 find_ignore_dirs [r] = NULL;
253 canonicalize_pathname (find_ignore_dirs [w]);
254 w++;
257 /* sort array */
258 if (find_ignore_dirs[0] != NULL)
259 qsort (find_ignore_dirs, g_strv_length (find_ignore_dirs),
260 sizeof (find_ignore_dirs[0]), &find_ignore_dirs_cmp);
261 else
263 g_strfreev (find_ignore_dirs);
264 find_ignore_dirs = NULL;
268 options.file_case_sens =
269 mc_config_get_bool (mc_main_config, "FindFile", "file_case_sens", TRUE);
270 options.file_pattern =
271 mc_config_get_bool (mc_main_config, "FindFile", "file_shell_pattern", TRUE);
272 options.find_recurs = mc_config_get_bool (mc_main_config, "FindFile", "file_find_recurs", TRUE);
273 options.skip_hidden =
274 mc_config_get_bool (mc_main_config, "FindFile", "file_skip_hidden", FALSE);
275 options.file_all_charsets =
276 mc_config_get_bool (mc_main_config, "FindFile", "file_all_charsets", FALSE);
277 options.content_use = mc_config_get_bool (mc_main_config, "FindFile", "content_use", FALSE);
278 options.content_case_sens =
279 mc_config_get_bool (mc_main_config, "FindFile", "content_case_sens", TRUE);
280 options.content_regexp =
281 mc_config_get_bool (mc_main_config, "FindFile", "content_regexp", FALSE);
282 options.content_first_hit =
283 mc_config_get_bool (mc_main_config, "FindFile", "content_first_hit", FALSE);
284 options.content_whole_words =
285 mc_config_get_bool (mc_main_config, "FindFile", "content_whole_words", FALSE);
286 options.content_all_charsets =
287 mc_config_get_bool (mc_main_config, "FindFile", "content_all_charsets", FALSE);
290 static void
291 find_save_options (void)
293 mc_config_set_bool (mc_main_config, "FindFile", "file_case_sens", options.file_case_sens);
294 mc_config_set_bool (mc_main_config, "FindFile", "file_shell_pattern", options.file_pattern);
295 mc_config_set_bool (mc_main_config, "FindFile", "file_find_recurs", options.find_recurs);
296 mc_config_set_bool (mc_main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
297 mc_config_set_bool (mc_main_config, "FindFile", "file_all_charsets", options.file_all_charsets);
298 mc_config_set_bool (mc_main_config, "FindFile", "content_use", options.content_use);
299 mc_config_set_bool (mc_main_config, "FindFile", "content_case_sens", options.content_case_sens);
300 mc_config_set_bool (mc_main_config, "FindFile", "content_regexp", options.content_regexp);
301 mc_config_set_bool (mc_main_config, "FindFile", "content_first_hit", options.content_first_hit);
302 mc_config_set_bool (mc_main_config, "FindFile", "content_whole_words",
303 options.content_whole_words);
304 mc_config_set_bool (mc_main_config, "FindFile", "content_all_charsets",
305 options.content_all_charsets);
308 static inline char *
309 add_to_list (const char *text, void *data)
311 return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data);
314 static inline void
315 stop_idle (void *data)
317 set_idle_proc (data, 0);
320 static inline void
321 status_update (const char *text)
323 label_set_text (status_label, text);
326 static void
327 found_num_update (void)
329 char buffer[BUF_TINY];
330 g_snprintf (buffer, sizeof (buffer), _("Found: %ld"), matches);
331 label_set_text (found_num_label, buffer);
334 static void
335 get_list_info (char **file, char **dir)
337 listbox_get_current (find_list, file, (void **) dir);
340 /* check regular expression */
341 static gboolean
342 find_check_regexp (const char *r)
344 mc_search_t *search;
345 gboolean regexp_ok = FALSE;
347 search = mc_search_new (r, -1);
349 if (search != NULL)
351 search->search_type = MC_SEARCH_T_REGEX;
352 regexp_ok = mc_search_prepare (search);
353 mc_search_free (search);
356 return regexp_ok;
360 * Callback for the parameter dialog.
361 * Validate regex, prevent closing the dialog if it's invalid.
363 static cb_ret_t
364 find_parm_callback (Dlg_head * h, Widget * sender, dlg_msg_t msg, int parm, void *data)
366 switch (msg)
368 case DLG_ACTION:
369 if (sender == (Widget *) content_use_cbox)
371 gboolean disable = !(content_use_cbox->state & C_BOOL);
373 widget_disable (content_label->widget, disable);
374 send_message ((Widget *) content_label, WIDGET_DRAW, 0);
375 widget_disable (in_with->widget, disable);
376 send_message ((Widget *) in_with, WIDGET_DRAW, 0);
377 widget_disable (content_first_hit_cbox->widget, disable);
378 send_message ((Widget *) content_first_hit_cbox, WIDGET_DRAW, 0);
379 widget_disable (content_regexp_cbox->widget, disable);
380 send_message ((Widget *) content_regexp_cbox, WIDGET_DRAW, 0);
381 widget_disable (content_case_sens_cbox->widget, disable);
382 send_message ((Widget *) content_case_sens_cbox, WIDGET_DRAW, 0);
383 #ifdef HAVE_CHARSET
384 widget_disable (content_all_charsets_cbox->widget, disable);
385 send_message ((Widget *) content_all_charsets_cbox, WIDGET_DRAW, 0);
386 #endif
387 widget_disable (content_whole_words_cbox->widget, disable);
388 send_message ((Widget *) content_whole_words_cbox, WIDGET_DRAW, 0);
390 return MSG_HANDLED;
393 return MSG_NOT_HANDLED;
396 case DLG_VALIDATE:
397 if (h->ret_value != B_ENTER)
398 return MSG_HANDLED;
400 /* check filename regexp */
401 if (!(file_pattern_cbox->state & C_BOOL)
402 && (in_name->buffer[0] != '\0') && !find_check_regexp (in_name->buffer))
404 h->state = DLG_ACTIVE; /* Don't stop the dialog */
405 message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
406 dlg_select_widget (in_name);
407 return MSG_HANDLED;
410 /* check content regexp */
411 if ((content_regexp_cbox->state & C_BOOL)
412 && (in_with->buffer[0] != '\0') && !find_check_regexp (in_with->buffer))
414 h->state = DLG_ACTIVE; /* Don't stop the dialog */
415 message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
416 dlg_select_widget (in_with);
417 return MSG_HANDLED;
420 return MSG_HANDLED;
422 default:
423 return default_dlg_callback (h, sender, msg, parm, data);
428 * find_parameters: gets information from the user
430 * If the return value is TRUE, then the following holds:
432 * START_DIR and PATTERN are pointers to char * and upon return they
433 * contain the information provided by the user.
435 * CONTENT holds a strdup of the contents specified by the user if he
436 * asked for them or 0 if not (note, this is different from the
437 * behavior for the other two parameters.
440 static gboolean
441 find_parameters (char **start_dir, char **pattern, char **content)
443 gboolean return_value;
445 /* file name */
446 const char *file_case_label = N_("Cas&e sensitive");
447 const char *file_pattern_label = N_("&Using shell patterns");
448 const char *file_recurs_label = N_("&Find recursively");
449 const char *file_skip_hidden_label = N_("S&kip hidden");
450 #ifdef HAVE_CHARSET
451 const char *file_all_charsets_label = N_("&All charsets");
452 #endif
454 /* file content */
455 const char *content_use_label = N_("Sea&rch for content");
456 const char *content_case_label = N_("Case sens&itive");
457 const char *content_regexp_label = N_("Re&gular expression");
458 const char *content_first_hit_label = N_("Fir&st hit");
459 const char *content_whole_words_label = N_("&Whole words");
460 #ifdef HAVE_CHARSET
461 const char *content_all_charsets_label = N_("A&ll charsets");
462 #endif
464 const char *buts[] = { N_("&OK"), N_("&Cancel"), N_("&Tree") };
466 int b0, b1, b2;
468 int cbox_position;
469 gboolean disable;
471 #ifdef ENABLE_NLS
473 int i = sizeof (buts) / sizeof (buts[0]);
474 while (i-- != 0)
475 buts[i] = _(buts[i]);
477 file_case_label = _(file_case_label);
478 file_pattern_label = _(file_pattern_label);
479 file_recurs_label = _(file_recurs_label);
480 file_skip_hidden_label = _(file_skip_hidden_label);
481 #ifdef HAVE_CHARSET
482 file_all_charsets_label = _(file_all_charsets_label);
483 content_all_charsets_label = _(content_all_charsets_label);
484 #endif
485 content_use_label = _(content_use_label);
486 content_case_label = _(content_case_label);
487 content_regexp_label = _(content_regexp_label);
488 content_first_hit_label = _(content_first_hit_label);
489 content_whole_words_label = _(content_whole_words_label);
491 #endif /* ENABLE_NLS */
493 b0 = str_term_width1 (buts[0]) + 6; /* default button */
494 b1 = str_term_width1 (buts[1]) + 4;
495 b2 = str_term_width1 (buts[2]) + 4;
497 find_load_options ();
499 if (in_start_dir == NULL)
500 in_start_dir = g_strdup (".");
502 disable = !options.content_use;
504 find_dlg =
505 create_dlg (TRUE, 0, 0, FIND_Y, FIND_X, dialog_colors,
506 find_parm_callback, "[Find File]", _("Find File"), DLG_CENTER | DLG_REVERSE);
508 add_widget (find_dlg,
509 button_new (FIND_Y - 3, FIND_X * 3 / 4 - b1 / 2, B_CANCEL, NORMAL_BUTTON, buts[1],
510 0));
511 add_widget (find_dlg,
512 button_new (FIND_Y - 3, FIND_X / 4 - b0 / 2, B_ENTER, DEFPUSH_BUTTON, buts[0], 0));
514 cbox_position = FIND_Y - 5;
516 content_first_hit_cbox =
517 check_new (cbox_position--, FIND_X / 2 + 1, options.content_first_hit,
518 content_first_hit_label);
519 widget_disable (content_first_hit_cbox->widget, disable);
520 add_widget (find_dlg, content_first_hit_cbox);
522 content_whole_words_cbox =
523 check_new (cbox_position--, FIND_X / 2 + 1, options.content_whole_words,
524 content_whole_words_label);
525 widget_disable (content_whole_words_cbox->widget, disable);
526 add_widget (find_dlg, content_whole_words_cbox);
528 #ifdef HAVE_CHARSET
529 content_all_charsets_cbox = check_new (cbox_position--, FIND_X / 2 + 1,
530 options.content_all_charsets,
531 content_all_charsets_label);
532 widget_disable (content_all_charsets_cbox->widget, disable);
533 add_widget (find_dlg, content_all_charsets_cbox);
534 #endif
536 content_case_sens_cbox =
537 check_new (cbox_position--, FIND_X / 2 + 1, options.content_case_sens, content_case_label);
538 widget_disable (content_case_sens_cbox->widget, disable);
539 add_widget (find_dlg, content_case_sens_cbox);
541 content_regexp_cbox =
542 check_new (cbox_position--, FIND_X / 2 + 1, options.content_regexp, content_regexp_label);
543 widget_disable (content_regexp_cbox->widget, disable);
544 add_widget (find_dlg, content_regexp_cbox);
546 cbox_position = FIND_Y - 6;
548 skip_hidden_cbox = check_new (cbox_position--, 3, options.skip_hidden, file_skip_hidden_label);
549 add_widget (find_dlg, skip_hidden_cbox);
551 #ifdef HAVE_CHARSET
552 file_all_charsets_cbox =
553 check_new (cbox_position--, 3, options.file_all_charsets, file_all_charsets_label);
554 add_widget (find_dlg, file_all_charsets_cbox);
555 #endif
557 file_case_sens_cbox = check_new (cbox_position--, 3, options.file_case_sens, file_case_label);
558 add_widget (find_dlg, file_case_sens_cbox);
560 file_pattern_cbox = check_new (cbox_position--, 3, options.file_pattern, file_pattern_label);
561 add_widget (find_dlg, file_pattern_cbox);
563 recursively_cbox = check_new (cbox_position, 3, options.find_recurs, file_recurs_label);
564 add_widget (find_dlg, recursively_cbox);
566 /* This checkbox is located in the second column */
567 content_use_cbox =
568 check_new (cbox_position, FIND_X / 2 + 1, options.content_use, content_use_label);
569 add_widget (find_dlg, content_use_cbox);
571 in_with =
572 input_new (6, FIND_X / 2 + 1, input_get_default_colors (), FIND_X / 2 - 4, INPUT_LAST_TEXT,
573 MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_DEFAULT);
574 widget_disable (in_with->widget, disable);
575 add_widget (find_dlg, in_with);
577 content_label = label_new (5, FIND_X / 2 + 1, _("Content:"));
578 widget_disable (content_label->widget, disable);
579 add_widget (find_dlg, content_label);
581 in_name = input_new (6, 3, input_get_default_colors (),
582 FIND_X / 2 - 4, INPUT_LAST_TEXT, "name", INPUT_COMPLETE_DEFAULT);
583 add_widget (find_dlg, in_name);
584 add_widget (find_dlg, label_new (5, 3, _("File name:")));
586 add_widget (find_dlg, button_new (3, FIND_X - b2 - 2, B_TREE, NORMAL_BUTTON, buts[2], 0));
588 in_start = input_new (3, 3, input_get_default_colors (),
589 FIND_X - b2 - 6, in_start_dir, "start", INPUT_COMPLETE_DEFAULT);
590 add_widget (find_dlg, in_start);
591 add_widget (find_dlg, label_new (2, 3, _("Start at:")));
593 find_par_start:
594 dlg_select_widget (in_name);
596 switch (run_dlg (find_dlg))
598 case B_CANCEL:
599 return_value = FALSE;
600 break;
602 case B_TREE:
604 const char *temp_dir = in_start->buffer;
606 if ((temp_dir[0] == '\0') || ((temp_dir[0] == '.') && (temp_dir[1] == '\0')))
607 temp_dir = current_panel->cwd;
609 if (in_start_dir != INPUT_LAST_TEXT)
610 g_free (in_start_dir);
611 in_start_dir = tree_box (temp_dir);
612 if (in_start_dir == NULL)
613 in_start_dir = g_strdup (temp_dir);
615 assign_text (in_start, in_start_dir);
617 /* Warning: Dreadful goto */
618 goto find_par_start;
621 default:
622 #ifdef HAVE_CHARSET
623 options.file_all_charsets = file_all_charsets_cbox->state & C_BOOL;
624 options.content_all_charsets = content_all_charsets_cbox->state & C_BOOL;
625 #endif
626 options.content_use = content_use_cbox->state & C_BOOL;
627 options.content_case_sens = content_case_sens_cbox->state & C_BOOL;
628 options.content_regexp = content_regexp_cbox->state & C_BOOL;
629 options.content_first_hit = content_first_hit_cbox->state & C_BOOL;
630 options.content_whole_words = content_whole_words_cbox->state & C_BOOL;
631 options.find_recurs = recursively_cbox->state & C_BOOL;
632 options.file_pattern = file_pattern_cbox->state & C_BOOL;
633 options.file_case_sens = file_case_sens_cbox->state & C_BOOL;
634 options.skip_hidden = skip_hidden_cbox->state & C_BOOL;
636 *content = (options.content_use && in_with->buffer[0] != '\0')
637 ? g_strdup (in_with->buffer) : NULL;
638 *start_dir = in_start->buffer[0] != '\0' ? in_start->buffer : ".";
639 *pattern = g_strdup (in_name->buffer);
640 if (in_start_dir != INPUT_LAST_TEXT)
641 g_free (in_start_dir);
642 in_start_dir = g_strdup (*start_dir);
643 if ((*start_dir)[0] == '.' && (*start_dir)[1] == '\0')
644 *start_dir = g_strdup (current_panel->cwd);
645 else if (g_path_is_absolute (*start_dir))
646 *start_dir = g_strdup (*start_dir);
647 else
648 *start_dir = g_build_filename (current_panel->cwd, *start_dir, (char *) NULL);
650 canonicalize_pathname (*start_dir);
652 find_save_options ();
654 return_value = TRUE;
657 destroy_dlg (find_dlg);
659 return return_value;
662 #if GLIB_CHECK_VERSION (2, 14, 0)
664 static inline void
665 push_directory (const char *dir)
667 g_queue_push_head (&dir_queue, (void *) dir);
670 static inline char *
671 pop_directory (void)
673 return (char *) g_queue_pop_tail (&dir_queue);
676 /* Remove all the items from the stack */
677 static void
678 clear_stack (void)
680 g_queue_foreach (&dir_queue, (GFunc) g_free, NULL);
681 g_queue_clear (&dir_queue);
684 #else /* GLIB_CHECK_VERSION */
686 static void
687 push_directory (const char *dir)
689 dir_stack *new;
691 new = g_new (dir_stack, 1);
692 new->name = str_unconst (dir);
693 new->prev = dir_stack_base;
694 dir_stack_base = new;
697 static char *
698 pop_directory (void)
700 char *name = NULL;
702 if (dir_stack_base != NULL)
704 dir_stack *next;
705 name = dir_stack_base->name;
706 next = dir_stack_base->prev;
707 g_free (dir_stack_base);
708 dir_stack_base = next;
711 return name;
714 /* Remove all the items from the stack */
715 static void
716 clear_stack (void)
718 char *dir = NULL;
719 while ((dir = pop_directory ()) != NULL)
720 g_free (dir);
723 #endif /* GLIB_CHECK_VERSION */
725 static void
726 insert_file (const char *dir, const char *file)
728 char *tmp_name = NULL;
729 static char *dirname = NULL;
731 while (dir[0] == PATH_SEP && dir[1] == PATH_SEP)
732 dir++;
734 if (old_dir)
736 if (strcmp (old_dir, dir))
738 g_free (old_dir);
739 old_dir = g_strdup (dir);
740 dirname = add_to_list (dir, NULL);
743 else
745 old_dir = g_strdup (dir);
746 dirname = add_to_list (dir, NULL);
749 tmp_name = g_strdup_printf (" %s", file);
750 add_to_list (tmp_name, dirname);
751 g_free (tmp_name);
754 static void
755 find_add_match (const char *dir, const char *file)
757 insert_file (dir, file);
759 /* Don't scroll */
760 if (matches == 0)
761 listbox_select_first (find_list);
762 send_message (&find_list->widget, WIDGET_DRAW, 0);
764 matches++;
765 found_num_update ();
769 * get_line_at:
771 * Returns malloced null-terminated line from file file_fd.
772 * Input is buffered in buf_size long buffer.
773 * Current pos in buf is stored in pos.
774 * n_read - number of read chars.
775 * has_newline - is there newline ?
777 static char *
778 get_line_at (int file_fd, char *buf, int buf_size, int *pos, int *n_read, gboolean * has_newline)
780 char *buffer = NULL;
781 int buffer_size = 0;
782 char ch = 0;
783 int i = 0;
785 for (;;)
787 if (*pos >= *n_read)
789 *pos = 0;
790 *n_read = mc_read (file_fd, buf, buf_size);
791 if (*n_read <= 0)
792 break;
795 ch = buf[(*pos)++];
796 if (ch == '\0')
798 /* skip possible leading zero(s) */
799 if (i == 0)
800 continue;
801 break;
804 if (i >= buffer_size - 1)
805 buffer = g_realloc (buffer, buffer_size += 80);
807 /* Strip newline */
808 if (ch == '\n')
809 break;
811 buffer[i++] = ch;
814 *has_newline = (ch != '\0');
816 if (buffer != NULL)
817 buffer[i] = '\0';
819 return buffer;
822 static FindProgressStatus
823 check_find_events (Dlg_head * h)
825 Gpm_Event event;
826 int c;
828 event.x = -1;
829 c = tty_get_event (&event, h->mouse_status == MOU_REPEAT, FALSE);
830 if (c != EV_NONE)
832 dlg_process_event (h, c, &event);
833 if (h->ret_value == B_ENTER
834 || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
836 /* dialog terminated */
837 return FIND_ABORT;
839 if (!(h->flags & DLG_WANT_IDLE))
841 /* searching suspended */
842 return FIND_SUSPEND;
846 return FIND_CONT;
850 * search_content:
852 * Search the content_pattern string in the DIRECTORY/FILE.
853 * It will add the found entries to the find listbox.
855 * returns FALSE if do_search should look for another file
856 * TRUE if do_search should exit and proceed to the event handler
858 static gboolean
859 search_content (Dlg_head * h, const char *directory, const char *filename)
861 struct stat s;
862 char buffer[BUF_4K];
863 char *fname = NULL;
864 int file_fd;
865 gboolean ret_val = FALSE;
867 fname = concat_dir_and_file (directory, filename);
869 if (mc_stat (fname, &s) != 0 || !S_ISREG (s.st_mode))
871 g_free (fname);
872 return 0;
875 file_fd = mc_open (fname, O_RDONLY);
876 g_free (fname);
878 if (file_fd == -1)
879 return 0;
881 g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), str_trunc (filename, FIND2_X_USE));
883 status_update (buffer);
884 mc_refresh ();
886 tty_enable_interrupt_key ();
887 tty_got_interrupt ();
890 int line = 1;
891 int pos = 0;
892 int n_read = 0;
893 gboolean has_newline;
894 char *p = NULL;
895 gboolean found = FALSE;
896 gsize found_len;
897 char result[BUF_MEDIUM];
899 if (resuming)
901 /* We've been previously suspended, start from the previous position */
902 resuming = 0;
903 line = last_line;
904 pos = last_pos;
906 while (!ret_val
907 && (p = get_line_at (file_fd, buffer, sizeof (buffer),
908 &pos, &n_read, &has_newline)) != NULL)
910 if (!found /* Search in binary line once */
911 && mc_search_run (search_content_handle,
912 (const void *) p, 0, strlen (p), &found_len))
914 g_snprintf (result, sizeof (result), "%d:%s", line, filename);
915 find_add_match (directory, result);
916 found = TRUE;
918 g_free (p);
920 if (found && options.content_first_hit)
921 break;
923 if (has_newline)
925 found = FALSE;
926 line++;
929 if ((line & 0xff) == 0)
931 FindProgressStatus res;
932 res = check_find_events (h);
933 switch (res)
935 case FIND_ABORT:
936 stop_idle (h);
937 ret_val = TRUE;
938 break;
939 case FIND_SUSPEND:
940 resuming = 1;
941 last_line = line;
942 last_pos = pos;
943 ret_val = TRUE;
944 break;
945 default:
946 break;
951 tty_disable_interrupt_key ();
952 mc_close (file_fd);
953 return ret_val;
956 static inline gboolean
957 find_ignore_dir_search (const char *dir)
959 if (find_ignore_dirs != NULL)
961 const size_t dlen = strlen (dir);
962 char **ignore_dir;
964 for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
966 const size_t ilen = strlen (*ignore_dir);
968 if (dlen < ilen)
969 continue; /* ignore dir is too long -- skip it */
971 if (strncmp (dir, *ignore_dir, ilen) != 0)
972 continue; /* strings are different -- skip ignore_dir */
974 /* be sure than ignore_dir is not a part of dir like:
975 ignore_dir is "/h", dir is "/home" */
976 if (dir[ilen] == PATH_SEP || dir[ilen] == '\0')
977 return TRUE;
981 return FALSE;
984 static void
985 find_rotate_dash (const Dlg_head *h, gboolean finish)
987 static const char rotating_dash[] = "|/-\\";
988 static unsigned int pos = 0;
990 if (verbose)
992 pos = (pos + 1) % 4;
993 tty_setcolor (h->color[DLG_COLOR_NORMAL]);
994 dlg_move (h, FIND2_Y - 7, FIND2_X - 4);
995 tty_print_char (finish ? ' ' : rotating_dash[pos]);
996 mc_refresh ();
1000 static int
1001 do_search (Dlg_head *h)
1003 static struct dirent *dp = NULL;
1004 static DIR *dirp = NULL;
1005 static char *directory = NULL;
1006 struct stat tmp_stat;
1007 static int subdirs_left = 0;
1008 gsize bytes_found;
1009 unsigned short count;
1011 if (h == NULL)
1012 { /* someone forces me to close dirp */
1013 if (dirp != NULL)
1015 mc_closedir (dirp);
1016 dirp = NULL;
1018 g_free (directory);
1019 directory = NULL;
1020 dp = NULL;
1021 return 1;
1024 for (count = 0; count < 32; count++)
1026 while (dp == NULL)
1028 if (dirp != NULL)
1030 mc_closedir (dirp);
1031 dirp = NULL;
1034 while (dirp == NULL)
1036 char *tmp = NULL;
1038 tty_setcolor (REVERSE_COLOR);
1040 while (TRUE)
1042 tmp = pop_directory ();
1043 if (tmp == NULL)
1045 running = FALSE;
1046 status_update (_("Finished"));
1047 find_rotate_dash (h, TRUE);
1048 stop_idle (h);
1049 return 0;
1052 if (!find_ignore_dir_search (tmp))
1053 break;
1055 g_free (tmp);
1058 g_free (directory);
1059 directory = tmp;
1061 if (verbose)
1063 char buffer[BUF_SMALL];
1065 g_snprintf (buffer, sizeof (buffer), _("Searching %s"),
1066 str_trunc (directory, FIND2_X_USE));
1067 status_update (buffer);
1069 /* mc_stat should not be called after mc_opendir
1070 because vfs_s_opendir modifies the st_nlink
1072 if (mc_stat (directory, &tmp_stat) == 0)
1073 subdirs_left = tmp_stat.st_nlink - 2;
1074 else
1075 subdirs_left = 0;
1077 dirp = mc_opendir (directory);
1078 } /* while (!dirp) */
1080 /* skip invalid filenames */
1081 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1083 } /* while (!dp) */
1085 if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
1087 /* skip invalid filenames */
1088 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1091 return 1;
1094 if (!(options.skip_hidden && (dp->d_name[0] == '.')))
1096 gboolean search_ok;
1098 if ((subdirs_left != 0) && options.find_recurs && (directory != NULL))
1099 { /* Can directory be NULL ? */
1100 char *tmp_name = concat_dir_and_file (directory, dp->d_name);
1101 if (mc_lstat (tmp_name, &tmp_stat) == 0 && S_ISDIR (tmp_stat.st_mode))
1103 push_directory (tmp_name);
1104 subdirs_left--;
1106 else
1107 g_free (tmp_name);
1110 search_ok = mc_search_run (search_file_handle, dp->d_name,
1111 0, strlen (dp->d_name), &bytes_found);
1113 if (search_ok)
1115 if (content_pattern == NULL)
1116 find_add_match (directory, dp->d_name);
1117 else if (search_content (h, directory, dp->d_name))
1118 return 1;
1122 /* skip invalid filenames */
1123 while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
1125 } /* for */
1127 find_rotate_dash (h, FALSE);
1129 return 1;
1132 static void
1133 init_find_vars (void)
1135 g_free (old_dir);
1136 old_dir = NULL;
1137 matches = 0;
1139 /* Remove all the items from the stack */
1140 clear_stack ();
1143 static char *
1144 make_fullname (const char *dirname, const char *filename)
1147 if (strcmp (dirname, ".") == 0 || strcmp (dirname, "." PATH_SEP_STR) == 0)
1148 return g_strdup (filename);
1149 if (strncmp (dirname, "." PATH_SEP_STR, 2) == 0)
1150 return concat_dir_and_file (dirname + 2, filename);
1151 return concat_dir_and_file (dirname, filename);
1154 static void
1155 find_do_view_edit (int unparsed_view, int edit, char *dir, char *file)
1157 char *fullname = NULL;
1158 const char *filename = NULL;
1159 int line;
1161 if (content_pattern != NULL)
1163 filename = strchr (file + 4, ':') + 1;
1164 line = atoi (file + 4);
1166 else
1168 filename = file + 4;
1169 line = 0;
1172 fullname = make_fullname (dir, filename);
1173 if (edit)
1174 do_edit_at_line (fullname, use_internal_edit, line);
1175 else
1176 view_file_at_line (fullname, unparsed_view, use_internal_view, line);
1177 g_free (fullname);
1180 static cb_ret_t
1181 view_edit_currently_selected_file (int unparsed_view, int edit)
1183 char *dir = NULL;
1184 char *text = NULL;
1186 listbox_get_current (find_list, &text, (void **) &dir);
1188 if ((text == NULL) || (dir == NULL))
1189 return MSG_NOT_HANDLED;
1191 find_do_view_edit (unparsed_view, edit, dir, text);
1192 return MSG_HANDLED;
1195 static cb_ret_t
1196 find_callback (Dlg_head *h, Widget * sender, dlg_msg_t msg, int parm, void *data)
1198 switch (msg)
1200 case DLG_KEY:
1201 if (parm == KEY_F (3) || parm == KEY_F (13))
1203 int unparsed_view = (parm == KEY_F (13));
1204 return view_edit_currently_selected_file (unparsed_view, 0);
1206 if (parm == KEY_F (4))
1208 return view_edit_currently_selected_file (0, 1);
1210 return MSG_NOT_HANDLED;
1212 case DLG_IDLE:
1213 do_search (h);
1214 return MSG_HANDLED;
1216 default:
1217 return default_dlg_callback (h, sender, msg, parm, data);
1221 /* Handles the Stop/Start button in the find window */
1222 static int
1223 start_stop (WButton * button, int action)
1225 (void) button;
1226 (void) action;
1228 running = is_start;
1229 set_idle_proc (find_dlg, running);
1230 is_start = !is_start;
1232 status_update (is_start ? _("Stopped") : _("Searching"));
1233 button_set_text (stop_button, fbuts[is_start ? 1 : 0].text);
1235 return 0;
1238 /* Handle view command, when invoked as a button */
1239 static int
1240 find_do_view_file (WButton * button, int action)
1242 (void) button;
1243 (void) action;
1245 view_edit_currently_selected_file (0, 0);
1246 return 0;
1249 /* Handle edit command, when invoked as a button */
1250 static int
1251 find_do_edit_file (WButton * button, int action)
1253 (void) button;
1254 (void) action;
1256 view_edit_currently_selected_file (0, 1);
1257 return 0;
1260 static void
1261 setup_gui (void)
1263 #ifdef ENABLE_NLS
1264 static gboolean i18n_flag = FALSE;
1266 if (!i18n_flag)
1268 int i = sizeof (fbuts) / sizeof (fbuts[0]);
1269 while (i-- != 0)
1271 fbuts[i].text = _(fbuts[i].text);
1272 fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
1275 fbuts[2].len += 2; /* DEFPUSH_BUTTON */
1276 i18n_flag = TRUE;
1278 #endif /* ENABLE_NLS */
1281 * Dynamically place buttons centered within current window size
1284 int l0 = max (fbuts[0].len, fbuts[1].len);
1285 int l1 = fbuts[2].len + fbuts[3].len + l0 + fbuts[4].len;
1286 int l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len;
1287 int r1, r2;
1289 /* Check, if both button rows fit within FIND2_X */
1290 FIND2_X = max (l1 + 9, COLS - 16);
1291 FIND2_X = max (l2 + 8, FIND2_X);
1293 /* compute amount of space between buttons for each row */
1294 r1 = (FIND2_X - 4 - l1) % 5;
1295 l1 = (FIND2_X - 4 - l1) / 5;
1296 r2 = (FIND2_X - 4 - l2) % 4;
1297 l2 = (FIND2_X - 4 - l2) / 4;
1299 /* ...and finally, place buttons */
1300 fbuts[2].x = 2 + r1 / 2 + l1;
1301 fbuts[3].x = fbuts[2].x + fbuts[2].len + l1;
1302 fbuts[0].x = fbuts[3].x + fbuts[3].len + l1;
1303 fbuts[4].x = fbuts[0].x + l0 + l1;
1304 fbuts[5].x = 2 + r2 / 2 + l2;
1305 fbuts[6].x = fbuts[5].x + fbuts[5].len + l2;
1306 fbuts[7].x = fbuts[6].x + fbuts[6].len + l2;
1309 find_dlg =
1310 create_dlg (TRUE, 0, 0, FIND2_Y, FIND2_X, dialog_colors, find_callback,
1311 "[Find File]", _("Find File"), DLG_CENTER | DLG_REVERSE);
1313 add_widget (find_dlg,
1314 button_new (FIND2_Y - 3, fbuts[7].x, B_VIEW, NORMAL_BUTTON,
1315 fbuts[7].text, find_do_edit_file));
1316 add_widget (find_dlg,
1317 button_new (FIND2_Y - 3, fbuts[6].x, B_VIEW, NORMAL_BUTTON,
1318 fbuts[6].text, find_do_view_file));
1319 add_widget (find_dlg,
1320 button_new (FIND2_Y - 3, fbuts[5].x, B_PANELIZE, NORMAL_BUTTON, fbuts[5].text,
1321 NULL));
1323 add_widget (find_dlg,
1324 button_new (FIND2_Y - 4, fbuts[4].x, B_CANCEL, NORMAL_BUTTON, fbuts[4].text, NULL));
1325 stop_button =
1326 button_new (FIND2_Y - 4, fbuts[0].x, B_STOP, NORMAL_BUTTON, fbuts[0].text, start_stop);
1327 add_widget (find_dlg, stop_button);
1328 add_widget (find_dlg,
1329 button_new (FIND2_Y - 4, fbuts[3].x, B_AGAIN, NORMAL_BUTTON, fbuts[3].text, NULL));
1330 add_widget (find_dlg,
1331 button_new (FIND2_Y - 4, fbuts[2].x, B_ENTER, DEFPUSH_BUTTON, fbuts[2].text, NULL));
1333 status_label = label_new (FIND2_Y - 7, 4, _("Searching"));
1334 add_widget (find_dlg, status_label);
1336 found_num_label = label_new (FIND2_Y - 6, 4, "");
1337 add_widget (find_dlg, found_num_label);
1339 find_list = listbox_new (2, 2, FIND2_Y - 10, FIND2_X - 4, FALSE, NULL);
1340 add_widget (find_dlg, find_list);
1343 static int
1344 run_process (void)
1346 int ret;
1348 search_content_handle = mc_search_new (content_pattern, -1);
1349 if (search_content_handle)
1351 search_content_handle->search_type =
1352 options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
1353 search_content_handle->is_case_sensitive = options.content_case_sens;
1354 search_content_handle->whole_words = options.content_whole_words;
1355 search_content_handle->is_all_charsets = options.content_all_charsets;
1357 search_file_handle = mc_search_new (find_pattern, -1);
1358 search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
1359 search_file_handle->is_case_sensitive = options.file_case_sens;
1360 search_file_handle->is_all_charsets = options.file_all_charsets;
1361 search_file_handle->is_entire_line = options.file_pattern;
1363 resuming = 0;
1364 set_idle_proc (find_dlg, 1);
1365 ret = run_dlg (find_dlg);
1367 mc_search_free (search_file_handle);
1368 search_file_handle = NULL;
1369 mc_search_free (search_content_handle);
1370 search_content_handle = NULL;
1372 return ret;
1375 static void
1376 kill_gui (void)
1378 set_idle_proc (find_dlg, 0);
1379 destroy_dlg (find_dlg);
1382 static int
1383 find_file (const char *start_dir, const char *pattern, const char *content,
1384 char **dirname, char **filename)
1386 int return_value = 0;
1387 char *dir_tmp = NULL, *file_tmp = NULL;
1389 setup_gui ();
1391 /* FIXME: Need to cleanup this, this ought to be passed non-globaly */
1392 find_pattern = str_unconst (pattern);
1394 content_pattern = NULL;
1395 if (options.content_use && content != NULL && str_is_valid_string (content))
1396 content_pattern = g_strdup (content);
1398 init_find_vars ();
1399 push_directory (start_dir);
1401 return_value = run_process ();
1403 /* Remove all the items from the stack */
1404 clear_stack ();
1406 get_list_info (&file_tmp, &dir_tmp);
1408 if (dir_tmp)
1409 *dirname = g_strdup (dir_tmp);
1410 if (file_tmp)
1411 *filename = g_strdup (file_tmp);
1413 if (return_value == B_PANELIZE && *filename)
1415 int status, link_to_dir, stale_link;
1416 int next_free = 0;
1417 int i;
1418 struct stat st;
1419 GList *entry;
1420 dir_list *list = &current_panel->dir;
1421 char *name = NULL;
1423 for (i = 0, entry = find_list->list; entry != NULL; i++, entry = g_list_next (entry))
1425 const char *lc_filename = NULL;
1426 WLEntry *le = (WLEntry *) entry->data;
1428 if ((le->text == NULL) || (le->data == NULL))
1429 continue;
1431 if (content_pattern != NULL)
1432 lc_filename = strchr (le->text + 4, ':') + 1;
1433 else
1434 lc_filename = le->text + 4;
1436 name = make_fullname (le->data, lc_filename);
1437 status = handle_path (list, name, &st, next_free, &link_to_dir, &stale_link);
1438 if (status == 0)
1440 g_free (name);
1441 continue;
1443 if (status == -1)
1445 g_free (name);
1446 break;
1449 /* don't add files more than once to the panel */
1450 if (content_pattern != NULL && next_free > 0
1451 && strcmp (list->list[next_free - 1].fname, name) == 0)
1453 g_free (name);
1454 continue;
1457 if (!next_free) /* first turn i.e clean old list */
1458 panel_clean_dir (current_panel);
1459 list->list[next_free].fnamelen = strlen (name);
1460 list->list[next_free].fname = name;
1461 list->list[next_free].f.marked = 0;
1462 list->list[next_free].f.link_to_dir = link_to_dir;
1463 list->list[next_free].f.stale_link = stale_link;
1464 list->list[next_free].f.dir_size_computed = 0;
1465 list->list[next_free].st = st;
1466 list->list[next_free].sort_key = NULL;
1467 list->list[next_free].second_sort_key = NULL;
1468 next_free++;
1469 if (!(next_free & 15))
1470 rotate_dash ();
1473 if (next_free)
1475 current_panel->count = next_free;
1476 current_panel->is_panelized = 1;
1478 if (start_dir[0] == PATH_SEP)
1480 int ret;
1481 strcpy (current_panel->cwd, PATH_SEP_STR);
1482 ret = chdir (PATH_SEP_STR);
1487 g_free (content_pattern);
1488 kill_gui ();
1489 do_search (NULL); /* force do_search to release resources */
1490 g_free (old_dir);
1491 old_dir = NULL;
1493 return return_value;
1496 void
1497 do_find (void)
1499 char *start_dir = NULL, *pattern = NULL, *content = NULL;
1500 char *filename = NULL, *dirname = NULL;
1501 int v;
1502 gboolean dir_and_file_set;
1504 while (find_parameters (&start_dir, &pattern, &content))
1506 if (pattern[0] == '\0')
1507 break; /* nothing search */
1509 dirname = filename = NULL;
1510 is_start = FALSE;
1511 v = find_file (start_dir, pattern, content, &dirname, &filename);
1512 g_free (pattern);
1514 if (v == B_ENTER)
1516 if (dirname != NULL)
1518 do_cd (dirname, cd_exact);
1519 if (filename != NULL)
1520 try_to_select (current_panel,
1521 filename + (content != NULL
1522 ? strchr (filename + 4, ':') - filename + 1 : 4));
1524 else if (filename != NULL)
1525 do_cd (filename, cd_exact);
1527 g_free (dirname);
1528 g_free (filename);
1529 break;
1532 g_free (content);
1533 dir_and_file_set = (dirname != NULL) && (filename != NULL);
1534 g_free (dirname);
1535 g_free (filename);
1537 if (v == B_CANCEL)
1538 break;
1540 if (v == B_PANELIZE)
1542 if (dir_and_file_set)
1544 try_to_select (current_panel, NULL);
1545 panel_re_sort (current_panel);
1546 try_to_select (current_panel, NULL);
1548 break;