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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
34 /* Dialog manager and widgets */
38 #include "dialog.h" /* For do_refresh() */
39 #define DIR_H_INCLUDE_HANDLE_DIRENT
41 #include "panel.h" /* current_panel */
42 #include "main.h" /* do_cd, try_to_select */
44 #include "cmd.h" /* view_file_at_line */
46 #include "../vfs/vfs.h"
48 /* Size of the find parameters window */
50 static int FIND_X
= 50;
52 /* Size of the find window */
53 #define FIND2_Y LINES-4
55 static int FIND2_X
= 64;
56 #define FIND2_X_USE FIND2_X-20
58 /* A couple of extra messages we need */
67 /* List of directories to be ignored, separated by ':' */
68 char *find_ignore_dirs
= 0;
70 static WInput
*in_start
; /* Start path */
71 static WInput
*in_name
; /* Pattern to search */
72 static WInput
*in_with
; /* Text inside filename */
73 static WCheck
*case_sense
; /* "case sensitive" checkbox */
75 static int running
= 0; /* nice flag */
76 static char *find_pattern
; /* Pattern to search */
77 static char *content_pattern
; /* pattern to search inside files */
78 static int count
; /* Number of files displayed */
79 static int matches
; /* Number of matches */
80 static int is_start
; /* Status of the start/stop toggle button */
83 static Dlg_head
*find_dlg
; /* The dialog */
85 static WButton
*stop_button
; /* pointer to the stop button */
86 static WLabel
*status_label
; /* Finished, Searching etc. */
87 static WListbox
*find_list
; /* Listbox with the file list */
89 /* This keeps track of the directory stack */
90 typedef struct dir_stack
{
92 struct dir_stack
*prev
;
95 static dir_stack
*dir_stack_base
= 0;
99 int len
; /* length including space and brackets */
102 { N_("&Suspend"), 11, 29 },
103 { N_("Con&tinue"), 12, 29 },
104 { N_("&Chdir"), 11, 3 },
105 { N_("&Again"), 9, 17 },
106 { N_("&Quit"), 8, 43 },
107 { N_("Pane&lize"), 12, 3 },
108 { N_("&View - F3"), 13, 20 },
109 { N_("&Edit - F4"), 13, 38 }
112 static char *add_to_list (char *text
, void *closure
);
113 static void stop_idle (void *data
);
114 static void status_update (char *text
);
115 static void get_list_info (char **file
, char **dir
);
117 /* FIXME: r should be local variables */
118 static regex_t
*r
; /* Pointer to compiled content_pattern */
120 static int case_sensitive
= 1;
123 * Callback for the parameter dialog.
124 * Validate regex, prevent closing the dialog if it's invalid.
127 find_parm_callback (struct Dlg_head
*h
, int id
, int Msg
)
133 if ((h
->ret_value
!= B_ENTER
) || !in_with
->buffer
[0])
136 flags
= REG_EXTENDED
| REG_NOSUB
;
138 if (!(case_sense
->state
& C_BOOL
))
141 if (regcomp (r
, in_with
->buffer
, flags
)) {
142 message (1, MSG_ERROR
, _(" Malformed regular expression "));
143 dlg_select_widget (h
, in_with
);
144 h
->running
= 1; /* Don't stop the dialog */
148 return default_dlg_callback (h
, id
, Msg
);
152 * find_parameters: gets information from the user
154 * If the return value is true, then the following holds:
156 * START_DIR and PATTERN are pointers to char * and upon return they
157 * contain the information provided by the user.
159 * CONTENT holds a strdup of the contents specified by the user if he
160 * asked for them or 0 if not (note, this is different from the
161 * behavior for the other two parameters.
165 find_parameters (char **start_dir
, char **pattern
, char **content
)
169 static char *case_label
= N_("case &Sensitive");
171 static char *in_contents
= NULL
;
172 static char *in_start_dir
= NULL
;
173 static char *in_start_name
= NULL
;
175 static char *labs
[] =
176 { N_("Start at:"), N_("Filename:"), N_("Content: ") };
177 static char *buts
[] = { N_("&OK"), N_("&Tree"), N_("&Cancel") };
178 static int ilen
= 30, istart
= 14;
179 static int b0
= 3, b1
= 16, b2
= 36;
182 static int i18n_flag
= 0;
185 register int i
= sizeof (labs
) / sizeof (labs
[0]);
189 l1
= strlen (labs
[i
] = _(labs
[i
]));
193 i
= maxlen
+ ilen
+ 7;
197 for (i
= sizeof (buts
) / sizeof (buts
[0]), l1
= 0; i
--;) {
198 l1
+= strlen (buts
[i
] = _(buts
[i
]));
204 ilen
= FIND_X
- 7 - maxlen
; /* for the case of very long buttons :) */
205 istart
= FIND_X
- 3 - ilen
;
207 b1
= b0
+ strlen (buts
[0]) + 7;
208 b2
= FIND_X
- (strlen (buts
[2]) + 6);
211 case_label
= _(case_label
);
213 #endif /* ENABLE_NLS */
217 in_start_dir
= g_strdup (".");
219 in_start_name
= g_strdup (easy_patterns
? "*" : ".");
221 in_contents
= g_strdup ("");
224 create_dlg (0, 0, FIND_Y
, FIND_X
, dialog_colors
,
225 find_parm_callback
, "[Find File]", _("Find File"),
228 add_widget (find_dlg
,
229 button_new (11, b2
, B_CANCEL
, NORMAL_BUTTON
, buts
[2], 0, 0,
231 add_widget (find_dlg
,
232 button_new (11, b1
, B_TREE
, NORMAL_BUTTON
, buts
[1], 0, 0,
234 add_widget (find_dlg
,
235 button_new (11, b0
, B_ENTER
, DEFPUSH_BUTTON
, buts
[0], 0, 0,
239 check_new (9, 3, case_sensitive
, case_label
, "find-case-check");
240 add_widget (find_dlg
, case_sense
);
243 input_new (7, istart
, INPUT_COLOR
, ilen
, in_contents
, "content");
244 add_widget (find_dlg
, in_with
);
247 input_new (5, istart
, INPUT_COLOR
, ilen
, in_start_name
, "name");
248 add_widget (find_dlg
, in_name
);
251 input_new (3, istart
, INPUT_COLOR
, ilen
, in_start_dir
, "start");
252 add_widget (find_dlg
, in_start
);
254 add_widget (find_dlg
, label_new (7, 3, labs
[2], "label-cont"));
255 add_widget (find_dlg
, label_new (5, 3, labs
[1], "label-file"));
256 add_widget (find_dlg
, label_new (3, 3, labs
[0], "label-start"));
260 switch (find_dlg
->ret_value
) {
266 temp_dir
= g_strdup (in_start
->buffer
);
267 case_sensitive
= case_sense
->state
& C_BOOL
;
268 destroy_dlg (find_dlg
);
269 g_free (in_start_dir
);
270 if (strcmp (temp_dir
, ".") == 0) {
272 temp_dir
= g_strdup (cpanel
->cwd
);
274 in_start_dir
= tree_box (temp_dir
);
278 in_start_dir
= temp_dir
;
279 /* Warning: Dreadful goto */
284 g_free (in_contents
);
285 if (in_with
->buffer
[0]) {
286 *content
= g_strdup (in_with
->buffer
);
287 in_contents
= g_strdup (*content
);
289 *content
= in_contents
= NULL
;
293 case_sensitive
= case_sense
->state
& C_BOOL
;
295 *start_dir
= g_strdup (in_start
->buffer
);
296 *pattern
= g_strdup (in_name
->buffer
);
298 g_free (in_start_dir
);
299 in_start_dir
= g_strdup (*start_dir
);
300 g_free (in_start_name
);
301 in_start_name
= g_strdup (*pattern
);
304 destroy_dlg (find_dlg
);
310 push_directory (char *dir
)
314 new = g_new (dir_stack
, 1);
315 new->name
= g_strdup (dir
);
316 new->prev
= dir_stack_base
;
317 dir_stack_base
= new;
327 name
= dir_stack_base
->name
;
328 next
= dir_stack_base
->prev
;
329 g_free (dir_stack_base
);
330 dir_stack_base
= next
;
337 insert_file (char *dir
, char *file
)
340 static char *dirname
;
343 if (dir
[0] == PATH_SEP
&& dir
[1] == PATH_SEP
)
347 if (dir
[i
- 1] != PATH_SEP
){
354 if (strcmp (old_dir
, dir
)){
356 old_dir
= g_strdup (dir
);
357 dirname
= add_to_list (dir
, NULL
);
360 old_dir
= g_strdup (dir
);
361 dirname
= add_to_list (dir
, NULL
);
364 tmp_name
= g_strconcat (" ", file
, NULL
);
365 add_to_list (tmp_name
, dirname
);
370 find_add_match (Dlg_head
*h
, char *dir
, char *file
)
372 int p
= ++matches
& 7;
374 insert_file (dir
, file
);
378 listbox_select_last (find_list
, 1);
380 listbox_select_last (find_list
, 0);
381 /* Updates the current listing */
382 send_message (&find_list
->widget
, WIDGET_DRAW
, 0);
390 * Returns malloced null-terminated line from file file_fd.
391 * Input is buffered in buf_size long buffer.
392 * Current pos in buf is stored in pos.
393 * n_read - number of read chars.
394 * has_newline - is there newline ?
397 get_line_at (int file_fd
, char *buf
, int *pos
, int *n_read
, int buf_size
, int *has_newline
)
405 if (*pos
>= *n_read
){
407 if ((*n_read
= mc_read (file_fd
, buf
, buf_size
)) <= 0)
413 /* skip possible leading zero(s) */
420 if (i
>= buffer_size
- 1){
421 buffer
= g_realloc (buffer
, buffer_size
+= 80);
426 } while (ch
!= '\n');
428 *has_newline
= ch
? 1 : 0;
440 * Search the global (FIXME) regexp compiled content_pattern string in the
441 * DIRECTORY/FILE. It will add the found entries to the find listbox.
444 search_content (Dlg_head
*h
, char *directory
, char *filename
)
447 char buffer
[BUF_SMALL
];
451 fname
= concat_dir_and_file (directory
, filename
);
453 if (mc_stat (fname
, &s
) != 0 || !S_ISREG (s
.st_mode
)){
458 file_fd
= mc_open (fname
, O_RDONLY
);
464 g_snprintf (buffer
, sizeof (buffer
), _("Grepping in %s"), name_trunc (filename
, FIND2_X_USE
));
466 status_update (buffer
);
469 enable_interrupt_key ();
480 while ((p
= get_line_at (file_fd
, buffer
, &pos
, &n_read
, sizeof (buffer
), &has_newline
))){
481 if (found
== 0){ /* Search in binary line once */
482 if (regexec (r
, p
, 1, 0, 0) == 0){
484 p
= g_strdup_printf ("%d:%s", line
, filename
);
485 find_add_match (h
, directory
, p
);
496 disable_interrupt_key ();
501 do_search (struct Dlg_head
*h
)
503 static struct dirent
*dp
= 0;
504 static DIR *dirp
= 0;
505 static char directory
[MC_MAXPATHLEN
+2];
506 struct stat tmp_stat
;
508 static int subdirs_left
= 0;
509 char *tmp_name
; /* For building file names */
511 if (!h
) { /* someone forces me to close dirp */
530 attrset (REVERSE_COLOR
);
532 tmp
= pop_directory ();
535 status_update (_("Finished"));
539 if (find_ignore_dirs
){
541 char *temp_dir
= g_strconcat (":", tmp
, ":", NULL
);
543 found
= strstr (find_ignore_dirs
, temp_dir
) != 0;
553 strcpy (directory
, tmp
);
557 char buffer
[BUF_SMALL
];
559 g_snprintf (buffer
, sizeof (buffer
), _("Searching %s"), name_trunc (directory
, FIND2_X_USE
));
560 status_update (buffer
);
562 /* mc_stat should not be called after mc_opendir
563 because vfs_s_opendir modifies the st_nlink
565 mc_stat (directory
, &tmp_stat
);
566 subdirs_left
= tmp_stat
.st_nlink
- 2;
567 /* Commented out as unnecessary
568 if (subdirs_left < 0)
569 subdirs_left = MAXINT;
571 dirp
= mc_opendir (directory
);
573 dp
= mc_readdir (dirp
);
576 if (strcmp (dp
->d_name
, ".") == 0 ||
577 strcmp (dp
->d_name
, "..") == 0){
578 dp
= mc_readdir (dirp
);
582 tmp_name
= concat_dir_and_file (directory
, dp
->d_name
);
585 mc_lstat (tmp_name
, &tmp_stat
);
586 if (S_ISDIR (tmp_stat
.st_mode
)){
587 push_directory (tmp_name
);
592 if (regexp_match (find_pattern
, dp
->d_name
, match_file
)){
594 search_content (h
, directory
, dp
->d_name
);
596 find_add_match (h
, directory
, dp
->d_name
);
600 dp
= mc_readdir (dirp
);
602 /* Displays the nice dot */
605 /* For nice updating */
606 char *rotating_dash
= "|/-\\";
611 dlg_move (h
, FIND2_Y
-6, FIND2_X
- 4);
612 addch (rotating_dash
[pos
]);
616 goto do_search_begin
;
621 init_find_vars (void)
632 /* Remove all the items in the stack */
633 while ((dir
= pop_directory ()) != NULL
)
638 find_do_view_edit (int unparsed_view
, int edit
, char *dir
, char *file
)
640 char *fullname
, *filename
;
643 if (content_pattern
){
644 filename
= strchr (file
+ 4, ':') + 1;
645 line
= atoi (file
+ 4);
650 if (dir
[0] == '.' && dir
[1] == 0)
651 fullname
= g_strdup (filename
);
652 else if (dir
[0] == '.' && dir
[1] == PATH_SEP
)
653 fullname
= concat_dir_and_file (dir
+2, filename
);
655 fullname
= concat_dir_and_file (dir
, filename
);
658 do_edit_at_line (fullname
, line
);
660 view_file_at_line (fullname
, unparsed_view
, use_internal_view
, line
);
665 get_list_info (char **file
, char **dir
)
667 listbox_get_current (find_list
, file
, dir
);
671 add_to_list (char *text
, void *data
)
673 return listbox_add_item (find_list
, 0, 0, text
, data
);
677 stop_idle (void *data
)
679 set_idle_proc (data
, 0);
683 view_edit_currently_selected_file (int unparsed_view
, int edit
)
685 WLEntry
*entry
= find_list
->current
;
689 return MSG_NOT_HANDLED
;
693 if (!entry
->text
|| !dir
)
694 return MSG_NOT_HANDLED
;
696 find_do_view_edit (unparsed_view
, edit
, dir
, entry
->text
);
701 find_callback (struct Dlg_head
*h
, int id
, int Msg
)
705 common_dialog_repaint (h
);
709 if (id
== KEY_F(3) || id
== KEY_F(13)){
710 int unparsed_view
= (id
== KEY_F(13));
711 return view_edit_currently_selected_file (unparsed_view
, 0);
714 return view_edit_currently_selected_file (0, 1);
716 return MSG_NOT_HANDLED
;
725 /* Handles the Stop/Start button in the find window */
727 start_stop (int button
, void *extra
)
730 set_idle_proc (find_dlg
, running
);
731 is_start
= !is_start
;
733 status_update (is_start
? _("Stopped") : _("Searching"));
734 button_set_text (stop_button
, fbuts
[is_start
].text
);
739 /* Handle view command, when invoked as a button */
741 find_do_view_file (int button
, void *extra
)
743 view_edit_currently_selected_file (0, 0);
747 /* Handle edit command, when invoked as a button */
749 find_do_edit_file (int button
, void *extra
)
751 view_edit_currently_selected_file (0, 1);
759 static int i18n_flag
= 0;
761 register int i
= sizeof (fbuts
) / sizeof (fbuts
[0]);
763 fbuts
[i
].len
= strlen (fbuts
[i
].text
= _(fbuts
[i
].text
)) + 3;
764 fbuts
[2].len
+= 2; /* DEFPUSH_BUTTON */
767 #endif /* ENABLE_NLS */
770 * Dynamically place buttons centered within current window size
773 int l0
= max (fbuts
[0].len
, fbuts
[1].len
);
774 int l1
= fbuts
[2].len
+ fbuts
[3].len
+ l0
+ fbuts
[4].len
;
775 int l2
= fbuts
[5].len
+ fbuts
[6].len
+ fbuts
[7].len
;
780 /* Check, if both button rows fit within FIND2_X */
781 if (l1
+ 9 > FIND2_X
)
783 if (l2
+ 8 > FIND2_X
)
786 /* compute amount of space between buttons for each row */
787 r1
= (FIND2_X
- 4 - l1
) % 5;
788 l1
= (FIND2_X
- 4 - l1
) / 5;
789 r2
= (FIND2_X
- 4 - l2
) % 4;
790 l2
= (FIND2_X
- 4 - l2
) / 4;
792 /* ...and finally, place buttons */
793 fbuts
[2].x
= 2 + r1
/ 2 + l1
;
794 fbuts
[3].x
= fbuts
[2].x
+ fbuts
[2].len
+ l1
;
795 fbuts
[0].x
= fbuts
[3].x
+ fbuts
[3].len
+ l1
;
796 fbuts
[4].x
= fbuts
[0].x
+ l0
+ l1
;
797 fbuts
[5].x
= 2 + r2
/ 2 + l2
;
798 fbuts
[6].x
= fbuts
[5].x
+ fbuts
[5].len
+ l2
;
799 fbuts
[7].x
= fbuts
[6].x
+ fbuts
[6].len
+ l2
;
802 find_dlg
= create_dlg (0, 0, FIND2_Y
, FIND2_X
, dialog_colors
,
803 find_callback
, "[Find File]", _("Find File"),
806 add_widget (find_dlg
,
807 button_new (FIND2_Y
- 3, fbuts
[7].x
, B_VIEW
, NORMAL_BUTTON
,
808 fbuts
[7].text
, find_do_edit_file
, find_dlg
,
810 add_widget (find_dlg
,
811 button_new (FIND2_Y
- 3, fbuts
[6].x
, B_VIEW
, NORMAL_BUTTON
,
812 fbuts
[6].text
, find_do_view_file
, find_dlg
,
814 add_widget (find_dlg
,
815 button_new (FIND2_Y
- 3, fbuts
[5].x
, B_PANELIZE
,
816 NORMAL_BUTTON
, fbuts
[5].text
, 0, 0,
819 add_widget (find_dlg
,
820 button_new (FIND2_Y
- 4, fbuts
[4].x
, B_CANCEL
,
821 NORMAL_BUTTON
, fbuts
[4].text
, 0, 0,
824 button_new (FIND2_Y
- 4, fbuts
[0].x
, B_STOP
, NORMAL_BUTTON
,
825 fbuts
[0].text
, start_stop
, find_dlg
, "start-stop");
826 add_widget (find_dlg
, stop_button
);
827 add_widget (find_dlg
,
828 button_new (FIND2_Y
- 4, fbuts
[3].x
, B_AGAIN
,
829 NORMAL_BUTTON
, fbuts
[3].text
, 0, 0,
831 add_widget (find_dlg
,
832 button_new (FIND2_Y
- 4, fbuts
[2].x
, B_ENTER
,
833 DEFPUSH_BUTTON
, fbuts
[2].text
, 0, 0,
837 label_new (FIND2_Y
- 6, 4, _("Searching"), "label-search");
838 add_widget (find_dlg
, status_label
);
841 listbox_new (2, 2, FIND2_X
- 4, FIND2_Y
- 9, listbox_finish
, 0,
843 add_widget (find_dlg
, find_list
);
849 set_idle_proc (find_dlg
, 1);
851 return find_dlg
->ret_value
;
855 status_update (char *text
)
857 label_set_text (status_label
, text
);
863 set_idle_proc (find_dlg
, 0);
864 destroy_dlg (find_dlg
);
868 find_file (char *start_dir
, char *pattern
, char *content
, char **dirname
, char **filename
)
870 int return_value
= 0;
872 char *dir_tmp
, *file_tmp
;
876 /* FIXME: Need to cleanup this, this ought to be passed non-globaly */
877 find_pattern
= pattern
;
878 content_pattern
= content
;
881 push_directory (start_dir
);
883 return_value
= run_process ();
885 /* Remove all the items in the stack */
886 while ((dir
= pop_directory ()) != NULL
)
889 get_list_info (&file_tmp
, &dir_tmp
);
892 *dirname
= g_strdup (dir_tmp
);
894 *filename
= g_strdup (file_tmp
);
896 if (return_value
== B_PANELIZE
&& *filename
){
897 int status
, link_to_dir
, stale_link
;
901 WLEntry
*entry
= find_list
->list
;
902 dir_list
*list
= &cpanel
->dir
;
905 for (i
= 0; entry
&& i
< find_list
->count
; entry
= entry
->next
, i
++){
908 if (!entry
->text
|| !entry
->data
)
912 filename
= strchr (entry
->text
+4, ':')+1;
914 filename
= entry
->text
+4;
917 if (dir
[0] == '.' && dir
[1] == 0)
918 name
= g_strdup (filename
);
919 else if (dir
[0] == '.' && dir
[1] == PATH_SEP
)
920 name
= concat_dir_and_file (dir
+ 2, filename
);
922 name
= concat_dir_and_file (dir
, filename
);
923 status
= handle_path (list
, name
, &buf
, next_free
, &link_to_dir
,
934 /* don't add files more than once to the panel */
935 if (content_pattern
&& next_free
> 0){
936 if (strcmp (list
->list
[next_free
-1].fname
, name
) == 0) {
942 if (!next_free
) /* first turn i.e clean old list */
943 panel_clean_dir (cpanel
);
944 list
->list
[next_free
].fnamelen
= strlen (name
);
945 list
->list
[next_free
].fname
= name
;
946 file_mark (cpanel
, next_free
, 0);
947 list
->list
[next_free
].f
.link_to_dir
= link_to_dir
;
948 list
->list
[next_free
].f
.stale_link
= stale_link
;
949 list
->list
[next_free
].f
.dir_size_computed
= 0;
950 list
->list
[next_free
].buf
= buf
;
952 if (!(next_free
& 15))
956 cpanel
->count
= next_free
;
957 cpanel
->is_panelized
= 1;
958 /* Done by panel_clean_dir a few lines above
959 cpanel->dirs_marked = 0;
962 cpanel->top_file = 0;
963 cpanel->selected = 0;*/
965 if (start_dir
[0] == PATH_SEP
){
966 strcpy (cpanel
->cwd
, PATH_SEP_STR
);
967 chdir (PATH_SEP_STR
);
973 do_search (0); /* force do_search to release resources */
984 char *start_dir
, *pattern
, *content
;
985 char *filename
, *dirname
;
986 int v
, dir_and_file_set
;
987 regex_t rx
; /* Compiled content_pattern to search inside files */
989 for (r
= &rx
; find_parameters (&start_dir
, &pattern
, &content
); r
= &rx
){
991 dirname
= filename
= NULL
;
993 v
= find_file (start_dir
, pattern
, content
, &dirname
, &filename
);
1000 if (dirname
|| filename
){
1002 do_cd (dirname
, cd_exact
);
1004 try_to_select (cpanel
, filename
+ (content
?
1005 (strchr (filename
+ 4, ':') - filename
+ 1) : 4) );
1006 } else if (filename
)
1007 do_cd (filename
, cd_exact
);
1008 paint_panel (cpanel
);
1009 select_item (cpanel
);
1019 dir_and_file_set
= dirname
&& filename
;
1020 if (dirname
) g_free (dirname
);
1021 if (filename
) g_free (filename
);
1025 if (v
== B_PANELIZE
){
1026 if (dir_and_file_set
){
1027 try_to_select (cpanel
, NULL
);
1028 panel_re_sort (cpanel
);
1029 paint_panel (cpanel
);