1 /* Find file command for the Midnight Commander
2 Copyright (C) The Free Software Foundation
3 Written 1995 by Miguel de Icaza
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
29 #include <malloc.h> /* For free() */
31 #include <sys/param.h>
42 extern int verbose
; /* Should be in a more sensible header file */
44 /* Dialog manager and widgets */
48 #include "dialog.h" /* For do_refresh() */
49 #define DIR_H_INCLUDE_HANDLE_DIRENT
51 #include "panel.h" /* current_panel */
52 #include "main.h" /* do_cd, try_to_select */
55 #include "cmd.h" /* view_file_at_line */
56 #include "../vfs/vfs.h"
62 #ifndef PORT_HAS_FLUSH_EVENTS
63 # define x_flush_events()
66 /* Size of the find parameters window */
68 static int FIND_X
= 50;
70 /* Size of the find window */
71 #define FIND2_Y LINES-4
72 static int FIND2_X
= 64;
75 # define FIND2_X_USE 35
77 # define FIND2_X_USE FIND2_X-20
80 /* A couple of extra messages we need */
89 /* A list of directories to be ignores, separated with ':' */
90 char *find_ignore_dirs
= 0;
92 static Dlg_head
*find_dlg
; /* The dialog */
93 static WInput
*in_start
; /* Start path */
94 static WInput
*in_name
; /* Pattern to search */
95 static WInput
*in_with
; /* text inside filename */
96 static WListbox
*find_list
; /* Listbox with the file list */
97 static int running
= 0; /* nice flag */
98 static WButton
*stop_button
; /* pointer to the stop button */
99 static WLabel
*status_label
; /* Finished, Searching etc. */
100 static char *find_pattern
; /* Pattern to search */
101 static char *content_pattern
; /* pattern to search inside files */
102 static int count
; /* Number of files displayed */
103 static int matches
; /* Number of matches */
104 static int is_start
; /* Status of the start/stop toggle button */
105 int max_loops_in_idle
= 10;
106 static char *old_dir
;
108 /* For nice updating */
109 static char *rotating_dash
= "|/-\\";
111 /* This keeps track of the directory stack */
112 typedef struct dir_stack
{
114 struct dir_stack
*prev
;
117 dir_stack
*dir_stack_base
= 0;
121 int len
; /* length including space and brackets */
124 { N_("&Suspend"), 11, 29 },
125 { N_("Con&tinue"), 12, 29 },
126 { N_("&Chdir"), 11, 3 },
127 { N_("&Again"), 9, 17 },
128 { N_("&Quit"), 8, 43 },
129 { N_("Pane&lize"), 12, 3 },
130 { N_("&View - F3"), 13, 20 },
131 { N_("&Edit - F4"), 13, 38 }
135 * find_parameters: gets information from the user
137 * If the return value is true, then the following holds:
139 * START_DIR and PATTERN are pointers to char * and upon return they
140 * contain the information provided by the user.
142 * CONTENT holds a strdup of the contents specified by the user if he
143 * asked for them or 0 if not (note, this is different from the
144 * behavior for the other two parameters.
149 find_parameters (char **start_dir
, char **pattern
, char **content
)
153 static char *in_contents
= NULL
;
154 static char *in_start_dir
= NULL
;
155 static char *in_start_name
= NULL
;
157 static char* labs
[] = {N_("Start at:"), N_("Filename:"), N_("Content: ")};
158 static char* buts
[] = {N_("&Ok"), N_("&Tree"), N_("&Cancel")};
159 static int ilen
= 30, istart
= 14;
160 static int b0
= 3, b1
= 16, b2
= 36;
163 static int i18n_flag
= 0;
167 register int i
= sizeof(labs
)/sizeof(labs
[0]);
172 l1
= strlen (labs
[i
] = _(labs
[i
]));
176 i
= maxlen
+ ilen
+ 7;
180 for (i
= sizeof(buts
)/sizeof(buts
[0]), l1
= 0; i
--; )
182 l1
+= strlen (buts
[i
] = _(buts
[i
]));
188 ilen
= FIND_X
- 7 - maxlen
; /* for the case of very long buttons :) */
189 istart
= FIND_X
- 3 - ilen
;
191 b1
= b0
+ strlen(buts
[0]) + 7;
192 b2
= FIND_X
- (strlen(buts
[2]) + 6);
197 #endif /* ENABLE_NLS */
201 in_start_dir
= strdup (".");
203 in_start_name
= strdup (easy_patterns
? "*" : ".");
205 in_contents
= strdup ("");
207 find_dlg
= create_dlg (0, 0, FIND_Y
, FIND_X
, dialog_colors
,
208 common_dialog_callback
, "[Find File]", "findfile",
209 DLG_CENTER
| DLG_GRID
);
210 x_set_dialog_title (find_dlg
, _("Find File"));
212 add_widgetl (find_dlg
, button_new (9, b2
, B_CANCEL
, NORMAL_BUTTON
,
213 buts
[2], 0 ,0, "cancel"), XV_WLAY_RIGHTOF
);
215 add_widgetl (find_dlg
, button_new (9, b1
, B_TREE
, NORMAL_BUTTON
,
216 buts
[1], 0, 0, "tree"), XV_WLAY_RIGHTOF
);
218 add_widgetl (find_dlg
, button_new (9, b0
, B_ENTER
, DEFPUSH_BUTTON
,
219 buts
[0], 0, 0, "ok"), XV_WLAY_CENTERROW
);
221 in_with
= input_new (7, istart
, INPUT_COLOR
, ilen
, in_contents
, "content");
222 add_widgetl (find_dlg
, in_with
, XV_WLAY_BELOWOF
);
224 in_name
= input_new (5, istart
, INPUT_COLOR
, ilen
, in_start_name
, "name");
225 add_widgetl (find_dlg
, in_name
, XV_WLAY_BELOWOF
);
227 in_start
= input_new (3, istart
, INPUT_COLOR
, ilen
, in_start_dir
, "start");
228 add_widgetl (find_dlg
, in_start
, XV_WLAY_NEXTCOLUMN
);
230 add_widgetl (find_dlg
, label_new (7, 3, labs
[2], "label-cont"), XV_WLAY_BELOWOF
);
231 add_widgetl (find_dlg
, label_new (5, 3, labs
[1], "label-file"), XV_WLAY_BELOWOF
);
232 add_widgetl (find_dlg
, label_new (3, 3, labs
[0], "label-start"), XV_WLAY_NEXTCOLUMN
);
235 if (find_dlg
->ret_value
== B_CANCEL
)
237 else if (find_dlg
->ret_value
== B_TREE
){
238 temp_dir
= strdup (in_start
->buffer
);
239 destroy_dlg (find_dlg
);
241 if (strcmp (temp_dir
, ".") == 0){
243 temp_dir
= strdup (cpanel
->cwd
);
245 in_start_dir
= tree (temp_dir
);
249 in_start_dir
= temp_dir
;
250 /* Warning: Dreadful goto */
254 *start_dir
= strdup (in_start
->buffer
);
255 *pattern
= strdup (in_name
->buffer
);
258 if (in_with
->buffer
[0]){
259 *content
= strdup (in_with
->buffer
);
260 in_contents
= strdup (*content
);
262 *content
= in_contents
= NULL
;
265 in_start_dir
= strdup (*start_dir
);
266 free (in_start_name
);
267 in_start_name
= strdup (*pattern
);
270 destroy_dlg (find_dlg
);
276 push_directory (char *dir
)
280 new = xmalloc (sizeof (dir_stack
), "find: push_directory");
281 new->name
= strdup (dir
);
282 new->prev
= dir_stack_base
;
283 dir_stack_base
= new;
293 name
= dir_stack_base
->name
;
294 next
= dir_stack_base
->prev
;
295 free (dir_stack_base
);
296 dir_stack_base
= next
;
303 insert_file (char *dir
, char *file
)
306 static char *dirname
;
309 if (dir
[0] == PATH_SEP
&& dir
[1] == PATH_SEP
)
313 if (dir
[i
- 1] != PATH_SEP
){
320 if (strcmp (old_dir
, dir
)){
322 old_dir
= strdup (dir
);
323 dirname
= listbox_add_item (find_list
, 0, 0, dir
, 0);
326 old_dir
= strdup (dir
);
327 dirname
= listbox_add_item (find_list
, 0, 0, dir
, 0);
330 tmp_name
= copy_strings (" ", file
, 0);
331 listbox_add_item (find_list
, 0, 0, tmp_name
, dirname
);
336 find_add_match (Dlg_head
*h
, char *dir
, char *file
)
338 int p
= ++matches
& 7;
340 insert_file (dir
, file
);
344 listbox_select_last (find_list
, 1);
346 listbox_select_last (find_list
, 0);
349 /* Updates the current listing */
350 send_message (h
, &find_list
->widget
, WIDGET_DRAW
, 0);
369 for (p
= &paths
[0]; *p
; p
++){
370 if (stat (*p
, &s
) == 0)
379 * Search with egrep the global (FIXME) content_pattern string in the
380 * DIRECTORY/FILE. It will add the found entries to the find listbox.
383 search_content (Dlg_head
*h
, char *directory
, char *filename
)
388 int file_fd
, pipe
, ignoring
;
392 static char *egrep_path
;
394 fname
= get_full_name (directory
, filename
);
396 if (mc_stat (fname
, &s
) != 0 && !S_ISREG (s
.st_mode
)){
400 if (!S_ISREG (s
.st_mode
)){
405 file_fd
= mc_open (fname
, O_RDONLY
);
412 egrep_path
= locate_egrep ();
415 pipe
= mc_doublepopen (file_fd
, -1, &pid
, egrep_path
, egrep_path
, "-n", content_pattern
, NULL
);
416 #else /* GREP_STDIN */
417 pipe
= mc_doublepopen (file_fd
, -1, &pid
, egrep_path
, egrep_path
, "-n", content_pattern
, "-", NULL
);
418 #endif /* GREP STDIN */
425 sprintf (buffer
, _("Grepping in %s"), name_trunc (filename
, FIND2_X_USE
));
427 label_set_text (status_label
, buffer
);
432 enable_interrupt_key ();
435 i
= read (pipe
, &c
, 1);
451 the_name
= copy_strings (buffer
, ":", filename
, NULL
);
452 find_add_match (h
, directory
, the_name
);
455 if (p
- buffer
< (sizeof (buffer
)-1) && ISASCII (c
) && isdigit (c
))
461 disable_interrupt_key ();
463 message (1, _(" Find/read "), _(" Problem reading from child "));
465 mc_doublepclose (pipe
, pid
);
470 do_search (struct Dlg_head
*h
)
472 static struct dirent
*dp
= 0;
473 static DIR *dirp
= 0;
474 static char directory
[MC_MAXPATHLEN
+2];
475 struct stat tmp_stat
;
477 static int subdirs_left
= 0;
478 char *tmp_name
; /* For bulding file names */
480 if (!h
) { /* someone forces me to close dirp */
501 attrset (REVERSE_COLOR
);
504 tmp
= pop_directory ();
507 label_set_text (status_label
, _("Finished"));
508 set_idle_proc (h
, 0);
511 if (find_ignore_dirs
){
512 char *temp_dir
= copy_strings (":", tmp
, ":", 0);
513 if (strstr (find_ignore_dirs
, temp_dir
))
521 strcpy (directory
, tmp
);
527 sprintf (buffer
, _("Searching %s"), name_trunc (directory
, FIND2_X_USE
));
528 label_set_text (status_label
, buffer
);
530 dirp
= mc_opendir (directory
);
531 mc_stat (directory
, &tmp_stat
);
532 subdirs_left
= tmp_stat
.st_nlink
- 2;
533 /* Commented out as unnecessary
534 if (subdirs_left < 0)
535 subdirs_left = MAXINT;
538 dp
= mc_readdir (dirp
);
541 if (strcmp (dp
->d_name
, ".") == 0 ||
542 strcmp (dp
->d_name
, "..") == 0){
543 dp
= mc_readdir (dirp
);
545 xv_post_proc (h
, (void (*)(void *))do_search
, (void *)h
);
550 tmp_name
= get_full_name (directory
, dp
->d_name
);
553 mc_lstat (tmp_name
, &tmp_stat
);
554 if (S_ISDIR (tmp_stat
.st_mode
)){
555 push_directory (tmp_name
);
560 if (regexp_match (find_pattern
, dp
->d_name
, match_file
)){
562 search_content (h
, directory
, dp
->d_name
);
564 find_add_match (h
, directory
, dp
->d_name
);
568 dp
= mc_readdir (dirp
);
570 /* Displays the nice dot */
577 dlg_move (h
, FIND2_Y
-6, FIND2_X
- 4);
578 addch (rotating_dash
[pos
]);
582 goto do_search_begin
;
587 xv_post_proc (h
, (void (*)(void *))do_search
, (void *)h
);
594 view_edit_currently_selected_file (int unparsed_view
, int edit
)
596 WLEntry
*entry
= find_list
->current
;
597 char *dir
, *fullname
, *filename
;
601 return MSG_NOT_HANDLED
;
605 if (!entry
->text
|| !dir
)
606 return MSG_NOT_HANDLED
;
608 if (content_pattern
){
609 filename
= strchr (entry
->text
+ 4, ':') + 1;
610 line
= atoi (entry
->text
+ 4);
612 filename
= entry
->text
+ 4;
615 if (dir
[0] == '.' && dir
[1] == 0)
616 fullname
= strdup (filename
);
617 else if (dir
[0] == '.' && dir
[1] == PATH_SEP
)
618 fullname
= get_full_name (dir
+2, filename
);
620 fullname
= get_full_name (dir
, filename
);
623 do_edit_at_line (fullname
, line
);
625 view_file_at_line (fullname
, unparsed_view
, use_internal_view
, line
);
631 find_callback (struct Dlg_head
*h
, int id
, int Msg
)
636 common_dialog_repaint (h
);
641 if (id
== KEY_F(3) || id
== KEY_F(13)){
642 int unparsed_view
= (id
== KEY_F(13));
643 return view_edit_currently_selected_file (unparsed_view
, 0);
646 return view_edit_currently_selected_file (0, 1);
648 return MSG_NOT_HANDLED
;
657 /* Handles the Stop/Start button in the find window */
659 start_stop (int button
, void *extra
)
662 set_idle_proc (find_dlg
, running
);
663 is_start
= !is_start
;
665 label_set_text (status_label
, is_start
? _("Stopped") : _("Searching"));
666 button_set_text (stop_button
, fbuts
[is_start
].text
);
671 /* Handle view command, when invoked as a button */
673 find_do_view_file (int button
, void *extra
)
675 view_edit_currently_selected_file (0, 0);
679 /* Handle edit command, when invoked as a button */
681 find_do_edit_file (int button
, void *extra
)
683 view_edit_currently_selected_file (0, 1);
688 init_find_vars (void)
699 /* Remove all the items in the stack */
700 while ((dir
= pop_directory ()) != NULL
)
705 find_file (char *start_dir
, char *pattern
, char *content
, char **dirname
, char **filename
)
707 int return_value
= 0;
709 char *dir_tmp
, *file_tmp
;
712 static int i18n_flag
= 0;
715 register int i
= sizeof (fbuts
) / sizeof (fbuts
[0]);
717 fbuts
[i
].len
= strlen (fbuts
[i
].text
= _(fbuts
[i
].text
)) + 3;
718 fbuts
[2].len
+= 2; /* DEFPUSH_BUTTON */
721 #endif /* ENABLE_NLS */
724 * Dynamically place buttons centered within current window size
727 int l0
= max (fbuts
[0].len
, fbuts
[1].len
);
728 int l1
= fbuts
[2].len
+ fbuts
[3].len
+ l0
+ fbuts
[4].len
;
729 int l2
= fbuts
[5].len
+ fbuts
[6].len
+ fbuts
[7].len
;
734 /* Check, if both button rows fit within FIND2_X */
735 if (l1
+ 9 > FIND2_X
) FIND2_X
= l1
+ 9;
736 if (l2
+ 8 > FIND2_X
) FIND2_X
= l2
+ 8;
738 /* compute amount of space between buttons for each row */
739 r1
= (FIND2_X
- 4 - l1
) % 5;
740 l1
= (FIND2_X
- 4 - l1
) / 5;
741 r2
= (FIND2_X
- 4 - l2
) % 4;
742 l2
= (FIND2_X
- 4 - l2
) / 4;
744 /* ...and finally, place buttons */
745 fbuts
[2].x
= 2 + r1
/2 + l1
;
746 fbuts
[3].x
= fbuts
[2].x
+ fbuts
[2].len
+ l1
;
747 fbuts
[0].x
= fbuts
[3].x
+ fbuts
[3].len
+ l1
;
748 fbuts
[4].x
= fbuts
[0].x
+ l0
+ l1
;
749 fbuts
[5].x
= 2 + r2
/2 + l2
;
750 fbuts
[6].x
= fbuts
[5].x
+ fbuts
[5].len
+ l2
;
751 fbuts
[7].x
= fbuts
[6].x
+ fbuts
[6].len
+ l2
;
754 find_dlg
= create_dlg (0, 0, FIND2_Y
, FIND2_X
, dialog_colors
,
755 find_callback
, "[Find File]", "mfind", DLG_CENTER
| DLG_GRID
);
757 x_set_dialog_title (find_dlg
, _("Find file"));
759 add_widgetl (find_dlg
,
760 button_new (FIND2_Y
-3, fbuts
[7].x
, B_VIEW
, NORMAL_BUTTON
,
761 fbuts
[7].text
, find_do_edit_file
, find_dlg
, "button-edit"), 0);
762 add_widgetl (find_dlg
,
763 button_new (FIND2_Y
-3, fbuts
[6].x
, B_VIEW
, NORMAL_BUTTON
,
764 fbuts
[6].text
, find_do_view_file
, find_dlg
, "button-view"), 0);
765 add_widgetl (find_dlg
,
766 button_new (FIND2_Y
-3, fbuts
[5].x
, B_PANELIZE
, NORMAL_BUTTON
,
767 fbuts
[5].text
, 0, 0, "button-panelize"), XV_WLAY_CENTERROW
);
769 add_widgetl (find_dlg
,
770 button_new (FIND2_Y
-4, fbuts
[4].x
, B_CANCEL
, NORMAL_BUTTON
,
771 fbuts
[4].text
, 0, 0, "button-quit"), XV_WLAY_RIGHTOF
);
772 stop_button
= button_new (FIND2_Y
-4, fbuts
[0].x
, B_STOP
, NORMAL_BUTTON
,
773 fbuts
[0].text
, start_stop
, find_dlg
, "start-stop");
774 add_widgetl (find_dlg
, stop_button
, XV_WLAY_RIGHTOF
);
775 add_widgetl (find_dlg
,
776 button_new (FIND2_Y
-4, fbuts
[3].x
, B_AGAIN
, NORMAL_BUTTON
,
777 fbuts
[3].text
, 0, 0, "button-again"), XV_WLAY_RIGHTOF
);
778 add_widgetl (find_dlg
,
779 button_new (FIND2_Y
-4, fbuts
[2].x
, B_ENTER
, DEFPUSH_BUTTON
,
780 fbuts
[2].text
, 0, 0, "button-chdir"), XV_WLAY_CENTERROW
);
782 status_label
= label_new (FIND2_Y
-6, 4, _("Searching"), "label-search");
783 add_widgetl (find_dlg
, status_label
, XV_WLAY_BELOWOF
);
785 find_list
= listbox_new (2, 2, FIND2_X
-4, FIND2_Y
-9, listbox_finish
, 0, "listbox");
786 add_widgetl (find_dlg
, find_list
, XV_WLAY_EXTENDWIDTH
);
788 /* FIXME: Need to cleanup this, this ought to be passed non-globaly */
789 find_pattern
= pattern
;
790 content_pattern
= content
;
792 set_idle_proc (find_dlg
, 1);
794 push_directory (start_dir
);
797 xv_post_proc (find_dlg
, (void (*)(void *))do_search
, (void *)find_dlg
);
801 return_value
= find_dlg
->ret_value
;
803 /* Remove all the items in the stack */
804 while ((dir
= pop_directory ()) != NULL
)
807 listbox_get_current (find_list
, &file_tmp
, &dir_tmp
);
810 *dirname
= strdup (dir_tmp
);
812 *filename
= strdup (file_tmp
);
813 if (return_value
== B_PANELIZE
&& *filename
){
814 int status
, link_to_dir
, stalled_link
;
818 WLEntry
*entry
= find_list
->list
;
819 dir_list
*list
= &cpanel
->dir
;
822 for (i
= 0; entry
&& i
< find_list
->count
; entry
= entry
->next
, i
++){
826 filename
= strchr (entry
->text
+4, ':')+1;
828 filename
= entry
->text
+4;
830 if (!entry
->text
|| !entry
->data
)
833 if (dir
[0] == '.' && dir
[1] == 0)
834 name
= strdup (filename
);
835 else if (dir
[0] == '.' && dir
[1] == PATH_SEP
)
836 name
= get_full_name (dir
+ 2, filename
);
838 name
= get_full_name (dir
, filename
);
839 status
= handle_path (list
, name
, &buf
, next_free
, &link_to_dir
,
850 /* don't add files more than once to the panel */
851 if (content_pattern
&& next_free
> 0){
852 if (strcmp (list
->list
[next_free
-1].fname
, name
) == 0) {
858 if (!next_free
) /* first turn i.e clean old list */
859 clean_dir (list
, cpanel
->count
);
860 list
->list
[next_free
].fnamelen
= strlen (name
);
861 list
->list
[next_free
].fname
= name
;
862 list
->list
[next_free
].cache
= NULL
;
863 file_mark (cpanel
, next_free
, 0);
864 list
->list
[next_free
].f
.link_to_dir
= link_to_dir
;
865 list
->list
[next_free
].f
.stalled_link
= stalled_link
;
866 list
->list
[next_free
].buf
= buf
;
868 if (!(next_free
& 15))
872 cpanel
->count
= next_free
;
873 cpanel
->is_panelized
= 1;
874 cpanel
->dirs_marked
= 0;
875 cpanel
->has_dir_sizes
= 0;
878 cpanel
->top_file
= 0;
879 cpanel
->selected
= 0;
881 if (start_dir
[0] == PATH_SEP
){
882 strcpy (cpanel
->cwd
, PATH_SEP_STR
);
883 chdir (PATH_SEP_STR
);
888 set_idle_proc (find_dlg
, 0);
889 destroy_dlg (find_dlg
);
890 do_search (0); /* force do_search to release resources */
901 char *start_dir
, *pattern
, *content
;
902 char *filename
, *dirname
;
903 int v
, dir_and_file_set
;
907 if (!find_parameters (&start_dir
, &pattern
, &content
))
910 dirname
= filename
= NULL
;
912 v
= find_file (start_dir
, pattern
, content
, &dirname
, &filename
);
917 if (dirname
|| filename
){
919 do_cd (dirname
, cd_exact
);
921 try_to_select (cpanel
, filename
+ (content
?
922 (strchr (filename
+ 4, ':') - filename
+ 1) : 4) );
924 do_cd (filename
, cd_exact
);
925 paint_panel (cpanel
);
926 select_item (cpanel
);
936 dir_and_file_set
= dirname
&& filename
;
937 if (dirname
) free (dirname
);
938 if (filename
) free (filename
);
942 if (v
== B_PANELIZE
){
943 if (dir_and_file_set
){
944 try_to_select (cpanel
, NULL
);
945 paint_panel (cpanel
);