diff options
Diffstat (limited to '')
-rw-r--r-- | src/filemanager/find.c | 1968 |
1 files changed, 1968 insertions, 0 deletions
diff --git a/src/filemanager/find.c b/src/filemanager/find.c new file mode 100644 index 0000000..c0d2cf9 --- /dev/null +++ b/src/filemanager/find.c @@ -0,0 +1,1968 @@ +/* + Find file command for the Midnight Commander + + Copyright (C) 1995-2023 + Free Software Foundation, Inc. + + Written by: + Miguel de Icaza, 1995 + Slava Zanko <slavazanko@gmail.com>, 2013 + Andrew Borodin <aborodin@vmail.ru>, 2013-2022 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file find.c + * \brief Source: Find file command + */ + +#include <config.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "lib/global.h" + +#include "lib/tty/tty.h" +#include "lib/tty/key.h" +#include "lib/skin.h" +#include "lib/search.h" +#include "lib/mcconfig.h" +#include "lib/vfs/vfs.h" +#include "lib/strutil.h" +#include "lib/widget.h" +#include "lib/util.h" /* canonicalize_pathname() */ + +#include "src/setup.h" /* verbose */ +#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */ + +#include "dir.h" +#include "cmd.h" /* find_cmd(), view_file_at_line() */ +#include "boxes.h" +#include "panelize.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */ +#define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */ + +/*** file scope type declarations ****************************************************************/ + +/* A couple of extra messages we need */ +enum +{ + B_STOP = B_USER + 1, + B_AGAIN, + B_PANELIZE, + B_TREE, + B_VIEW +}; + +typedef enum +{ + FIND_CONT = 0, + FIND_SUSPEND, + FIND_ABORT +} FindProgressStatus; + +/* find file options */ +typedef struct +{ + /* file name options */ + gboolean file_case_sens; + gboolean file_pattern; + gboolean find_recurs; + gboolean follow_symlinks; + gboolean skip_hidden; + gboolean file_all_charsets; + + /* file content options */ + gboolean content_case_sens; + gboolean content_regexp; + gboolean content_first_hit; + gboolean content_whole_words; + gboolean content_all_charsets; + + /* whether use ignore dirs or not */ + gboolean ignore_dirs_enable; + /* list of directories to be ignored, separated by ':' */ + char *ignore_dirs; +} find_file_options_t; + +typedef struct +{ + char *dir; + gsize start; + gsize end; +} find_match_location_t; + +/*** forward declarations (file scope functions) *************************************************/ + +/* button callbacks */ +static int start_stop (WButton * button, int action); +static int find_do_view_file (WButton * button, int action); +static int find_do_edit_file (WButton * button, int action); + +/*** file scope variables ************************************************************************/ + +/* Parsed ignore dirs */ +static char **find_ignore_dirs = NULL; + +/* static variables to remember find parameters */ +static WInput *in_start; /* Start path */ +static WInput *in_name; /* Filename */ +static WInput *in_with; /* Text */ +static WInput *in_ignore; +static WLabel *content_label; /* 'Content:' label */ +static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */ +static WCheck *file_pattern_cbox; /* File name is glob or regexp */ +static WCheck *recursively_cbox; +static WCheck *follow_sym_cbox; +static WCheck *skip_hidden_cbox; +static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */ +static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */ +static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */ +static WCheck *content_whole_words_cbox; /* "whole words" checkbox */ +#ifdef HAVE_CHARSET +static WCheck *file_all_charsets_cbox; +static WCheck *content_all_charsets_cbox; +#endif +static WCheck *ignore_dirs_cbox; + +static gboolean running = FALSE; /* nice flag */ +static char *find_pattern = NULL; /* Pattern to search */ +static char *content_pattern = NULL; /* pattern to search inside files; if + content_regexp_flag is true, it contains the + regex pattern, else the search string. */ +static gboolean content_is_empty = TRUE; /* remember content field state; initially is empty */ +static unsigned long matches; /* Number of matches */ +static gboolean is_start = FALSE; /* Status of the start/stop toggle button */ +static char *old_dir = NULL; + +static gint64 last_refresh; + +/* Where did we stop */ +static gboolean resuming; +static int last_line; +static int last_pos; +static off_t last_off; +static int last_i; + +static size_t ignore_count = 0; + +static WDialog *find_dlg; /* The dialog */ +static WLabel *status_label; /* Finished, Searching etc. */ +static WLabel *found_num_label; /* Number of found items */ + +/* This keeps track of the directory stack */ +static GQueue dir_queue = G_QUEUE_INIT; + +/* *INDENT-OFF* */ +static struct +{ + int ret_cmd; + button_flags_t flags; + const char *text; + int len; /* length including space and brackets */ + int x; + Widget *button; + bcback_fn callback; +} fbuts[] = +{ + { B_ENTER, DEFPUSH_BUTTON, N_("&Chdir"), 0, 0, NULL, NULL }, + { B_AGAIN, NORMAL_BUTTON, N_("&Again"), 0, 0, NULL, NULL }, + { B_STOP, NORMAL_BUTTON, N_("S&uspend"), 0, 0, NULL, start_stop }, + { B_STOP, NORMAL_BUTTON, N_("Con&tinue"), 0, 0, NULL, NULL }, + { B_CANCEL, NORMAL_BUTTON, N_("&Quit"), 0, 0, NULL, NULL }, + + { B_PANELIZE, NORMAL_BUTTON, N_("Pane&lize"), 0, 0, NULL, NULL }, + { B_VIEW, NORMAL_BUTTON, N_("&View - F3"), 0, 0, NULL, find_do_view_file }, + { B_VIEW, NORMAL_BUTTON, N_("&Edit - F4"), 0, 0, NULL, find_do_edit_file } +}; +/* *INDENT-ON* */ + +static const size_t fbuts_num = G_N_ELEMENTS (fbuts); +static const size_t quit_button = 4; /* index of "Quit" button */ + +static WListbox *find_list; /* Listbox with the file list */ + +static find_file_options_t options = { + TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, + TRUE, FALSE, FALSE, FALSE, FALSE, + FALSE, NULL +}; + +static char *in_start_dir = INPUT_LAST_TEXT; + +static mc_search_t *search_file_handle = NULL; +static mc_search_t *search_content_handle = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* don't use max macro to avoid double str_term_width1() call in widget length calculation */ +#undef max + +static int +max (int a, int b) +{ + return (a > b ? a : b); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +parse_ignore_dirs (const char *ignore_dirs) +{ + size_t r = 0, w = 0; /* read and write iterators */ + + if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0') + return; + + find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1); + + /* Values like '/foo::/bar: produce holes in list. + * Find and remove them */ + for (; find_ignore_dirs[r] != NULL; r++) + { + if (find_ignore_dirs[r][0] == '\0') + { + /* empty entry -- skip it */ + MC_PTR_FREE (find_ignore_dirs[r]); + continue; + } + + if (r != w) + { + /* copy entry to the previous free array cell */ + find_ignore_dirs[w] = find_ignore_dirs[r]; + find_ignore_dirs[r] = NULL; + } + + canonicalize_pathname (find_ignore_dirs[w]); + if (find_ignore_dirs[w][0] != '\0') + w++; + else + MC_PTR_FREE (find_ignore_dirs[w]); + } + + if (find_ignore_dirs[0] == NULL) + { + g_strfreev (find_ignore_dirs); + find_ignore_dirs = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_load_options (void) +{ + static gboolean loaded = FALSE; + + if (loaded) + return; + + loaded = TRUE; + + options.file_case_sens = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE); + options.file_pattern = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE); + options.find_recurs = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE); + options.follow_symlinks = + mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE); + options.skip_hidden = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE); + options.file_all_charsets = + mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE); + options.content_case_sens = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE); + options.content_regexp = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE); + options.content_first_hit = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE); + options.content_whole_words = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE); + options.content_all_charsets = + mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE); + options.ignore_dirs_enable = + mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE); + options.ignore_dirs = + mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", ""); + + if (options.ignore_dirs[0] == '\0') + MC_PTR_FREE (options.ignore_dirs); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_save_options (void) +{ + mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens", + options.file_case_sens); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern", + options.file_pattern); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs); + mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks", + options.follow_symlinks); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden); + mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets", + options.file_all_charsets); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens", + options.content_case_sens); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp", + options.content_regexp); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit", + options.content_first_hit); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words", + options.content_whole_words); + mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets", + options.content_all_charsets); + mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", + options.ignore_dirs_enable); + mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline char * +add_to_list (const char *text, void *data) +{ + return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +stop_idle (void *data) +{ + widget_idle (WIDGET (data), FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +status_update (const char *text) +{ + label_set_text (status_label, text); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +found_num_update (void) +{ + label_set_textv (found_num_label, _("Found: %lu"), matches); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +get_list_info (char **file, char **dir, gsize * start, gsize * end) +{ + find_match_location_t *location; + + listbox_get_current (find_list, file, (void **) &location); + if (location != NULL) + { + if (dir != NULL) + *dir = location->dir; + if (start != NULL) + *start = location->start; + if (end != NULL) + *end = location->end; + } + else + { + if (dir != NULL) + *dir = NULL; + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** check regular expression */ + +static gboolean +find_check_regexp (const char *r) +{ + mc_search_t *search; + gboolean regexp_ok = FALSE; + + search = mc_search_new (r, NULL); + + if (search != NULL) + { + search->search_type = MC_SEARCH_T_REGEX; + regexp_ok = mc_search_prepare (search); + mc_search_free (search); + } + + return regexp_ok; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_ignore_dirs (void) +{ + widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_params (void) +{ + gboolean disable = input_is_empty (in_name); + + widget_disable (WIDGET (file_pattern_cbox), disable); + widget_disable (WIDGET (file_case_sens_cbox), disable); +#ifdef HAVE_CHARSET + widget_disable (WIDGET (file_all_charsets_cbox), disable); +#endif +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_toggle_enable_content (void) +{ + widget_disable (WIDGET (content_regexp_cbox), content_is_empty); + widget_disable (WIDGET (content_case_sens_cbox), content_is_empty); +#ifdef HAVE_CHARSET + widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty); +#endif + widget_disable (WIDGET (content_whole_words_cbox), content_is_empty); + widget_disable (WIDGET (content_first_hit_cbox), content_is_empty); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Callback for the parameter dialog. + * Validate regex, prevent closing the dialog if it's invalid. + */ + +static cb_ret_t +find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies. + * Use this time moment to check input field content. We can't do that in MSG_INIT + * because history is not loaded yet. + * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or + * we could name it MSG_POST_INIT. + * + * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar + * purpose. We should remember to fix those places too when we introduce the new + * message. + */ + static gboolean first_draw = TRUE; + + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_INIT: + group_default_callback (w, NULL, MSG_INIT, 0, NULL); + first_draw = TRUE; + return MSG_HANDLED; + + case MSG_NOTIFY: + if (sender == WIDGET (ignore_dirs_cbox)) + { + find_toggle_enable_ignore_dirs (); + return MSG_HANDLED; + } + + return MSG_NOT_HANDLED; + + case MSG_VALIDATE: + if (h->ret_value != B_ENTER) + return MSG_HANDLED; + + /* check filename regexp */ + if (!file_pattern_cbox->state && !input_is_empty (in_name) + && !find_check_regexp (input_get_ctext (in_name))) + { + /* Don't stop the dialog */ + widget_set_state (w, WST_ACTIVE, TRUE); + message (D_ERROR, MSG_ERROR, _("Malformed regular expression")); + widget_select (WIDGET (in_name)); + return MSG_HANDLED; + } + + /* check content regexp */ + if (content_regexp_cbox->state && !content_is_empty + && !find_check_regexp (input_get_ctext (in_with))) + { + /* Don't stop the dialog */ + widget_set_state (w, WST_ACTIVE, TRUE); + message (D_ERROR, MSG_ERROR, _("Malformed regular expression")); + widget_select (WIDGET (in_with)); + return MSG_HANDLED; + } + + return MSG_HANDLED; + + case MSG_POST_KEY: + if (GROUP (h)->current->data == in_name) + find_toggle_enable_params (); + else if (GROUP (h)->current->data == in_with) + { + content_is_empty = input_is_empty (in_with); + find_toggle_enable_content (); + } + return MSG_HANDLED; + + case MSG_DRAW: + if (first_draw) + { + find_toggle_enable_ignore_dirs (); + find_toggle_enable_params (); + find_toggle_enable_content (); + } + + first_draw = FALSE; + MC_FALLTHROUGH; /* to call MSG_DRAW default handler */ + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * find_parameters: gets information from the user + * + * If the return value is TRUE, then the following holds: + * + * start_dir, ignore_dirs, pattern and content contain the information provided by the user. + * They are newly allocated strings and must be freed when unneeded. + * + * start_dir_len is -1 when user entered an absolute path, otherwise it is a length + * of start_dir (which is absolute). It is used to get a relative pats of find results. + */ + +static gboolean +find_parameters (WPanel * panel, char **start_dir, ssize_t * start_dir_len, + char **ignore_dirs, char **pattern, char **content) +{ + WGroup *g; + + /* Size of the find parameters window */ +#ifdef HAVE_CHARSET + const int lines = 19; +#else + const int lines = 18; +#endif + int cols = 68; + + gboolean return_value; + + /* file name */ + const char *file_name_label = N_("File name:"); + const char *file_recurs_label = N_("&Find recursively"); + const char *file_follow_symlinks = N_("Follow s&ymlinks"); + const char *file_pattern_label = N_("&Using shell patterns"); +#ifdef HAVE_CHARSET + const char *file_all_charsets_label = N_("&All charsets"); +#endif + const char *file_case_label = N_("Cas&e sensitive"); + const char *file_skip_hidden_label = N_("S&kip hidden"); + + /* file content */ + const char *content_content_label = N_("Content:"); + const char *content_use_label = N_("Sea&rch for content"); + const char *content_regexp_label = N_("Re&gular expression"); + const char *content_case_label = N_("Case sens&itive"); +#ifdef HAVE_CHARSET + const char *content_all_charsets_label = N_("A&ll charsets"); +#endif + const char *content_whole_words_label = N_("&Whole words"); + const char *content_first_hit_label = N_("Fir&st hit"); + + const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") }; + + /* button lengths */ + int b0, b1, b2, b12; + int y1, y2, x1, x2; + /* column width */ + int cw; + +#ifdef ENABLE_NLS + { + size_t i; + + file_name_label = _(file_name_label); + file_recurs_label = _(file_recurs_label); + file_follow_symlinks = _(file_follow_symlinks); + file_pattern_label = _(file_pattern_label); +#ifdef HAVE_CHARSET + file_all_charsets_label = _(file_all_charsets_label); +#endif + file_case_label = _(file_case_label); + file_skip_hidden_label = _(file_skip_hidden_label); + + /* file content */ + content_content_label = _(content_content_label); + content_use_label = _(content_use_label); + content_regexp_label = _(content_regexp_label); + content_case_label = _(content_case_label); +#ifdef HAVE_CHARSET + content_all_charsets_label = _(content_all_charsets_label); +#endif + content_whole_words_label = _(content_whole_words_label); + content_first_hit_label = _(content_first_hit_label); + + for (i = 0; i < G_N_ELEMENTS (buts); i++) + buts[i] = _(buts[i]); + } +#endif /* ENABLE_NLS */ + + /* calculate dialog width */ + + /* widget widths */ + cw = str_term_width1 (file_name_label); + cw = max (cw, str_term_width1 (file_recurs_label) + 4); + cw = max (cw, str_term_width1 (file_follow_symlinks) + 4); + cw = max (cw, str_term_width1 (file_pattern_label) + 4); +#ifdef HAVE_CHARSET + cw = max (cw, str_term_width1 (file_all_charsets_label) + 4); +#endif + cw = max (cw, str_term_width1 (file_case_label) + 4); + cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4); + + cw = max (cw, str_term_width1 (content_content_label) + 4); + cw = max (cw, str_term_width1 (content_use_label) + 4); + cw = max (cw, str_term_width1 (content_regexp_label) + 4); + cw = max (cw, str_term_width1 (content_case_label) + 4); +#ifdef HAVE_CHARSET + cw = max (cw, str_term_width1 (content_all_charsets_label) + 4); +#endif + cw = max (cw, str_term_width1 (content_whole_words_label) + 4); + cw = max (cw, str_term_width1 (content_first_hit_label) + 4); + + /* button width */ + b0 = str_term_width1 (buts[0]) + 3; + b1 = str_term_width1 (buts[1]) + 5; /* default button */ + b2 = str_term_width1 (buts[2]) + 3; + b12 = b1 + b2 + 1; + + cols = max (cols, max (b12, cw * 2 + 1) + 6); + + find_load_options (); + + if (in_start_dir == NULL) + in_start_dir = g_strdup ("."); + + find_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback, + NULL, "[Find File]", _("Find File")); + g = GROUP (find_dlg); + + x1 = 3; + x2 = cols / 2 + 1; + cw = (cols - 7) / 2; + y1 = 2; + + group_add_widget (g, label_new (y1++, x1, _("Start at:"))); + in_start = + input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start", + INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES); + group_add_widget (g, in_start); + + group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL)); + + ignore_dirs_cbox = + check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:")); + group_add_widget (g, ignore_dirs_cbox); + + in_ignore = + input_new (y1++, x1, input_colors, cols - 6, + options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs", + INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES); + group_add_widget (g, in_ignore); + + group_add_widget (g, hline_new (y1++, -1, -1)); + + y2 = y1; + + /* Start 1st column */ + group_add_widget (g, label_new (y1++, x1, file_name_label)); + in_name = + input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name", + INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD); + group_add_widget (g, in_name); + + /* Start 2nd column */ + content_label = label_new (y2++, x2, content_content_label); + group_add_widget (g, content_label); + in_with = + input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT, + MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE); + in_with->label = content_label; + group_add_widget (g, in_with); + + /* Continue 1st column */ + recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label); + group_add_widget (g, recursively_cbox); + + follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks); + group_add_widget (g, follow_sym_cbox); + + file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label); + group_add_widget (g, file_pattern_cbox); + + file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label); + group_add_widget (g, file_case_sens_cbox); + +#ifdef HAVE_CHARSET + file_all_charsets_cbox = + check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label); + group_add_widget (g, file_all_charsets_cbox); +#endif + + skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label); + group_add_widget (g, skip_hidden_cbox); + + /* Continue 2nd column */ + content_whole_words_cbox = + check_new (y2++, x2, options.content_whole_words, content_whole_words_label); + group_add_widget (g, content_whole_words_cbox); + + content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label); + group_add_widget (g, content_regexp_cbox); + + content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label); + group_add_widget (g, content_case_sens_cbox); + +#ifdef HAVE_CHARSET + content_all_charsets_cbox = + check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label); + group_add_widget (g, content_all_charsets_cbox); +#endif + + content_first_hit_cbox = + check_new (y2++, x2, options.content_first_hit, content_first_hit_label); + group_add_widget (g, content_first_hit_cbox); + + /* buttons */ + y1 = max (y1, y2); + x1 = (cols - b12) / 2; + group_add_widget (g, hline_new (y1++, -1, -1)); + group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL)); + group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL)); + + find_par_start: + widget_select (WIDGET (in_name)); + + switch (dlg_run (find_dlg)) + { + case B_CANCEL: + return_value = FALSE; + break; + + case B_TREE: + { + const char *start_cstr; + const char *temp_dir; + + start_cstr = input_get_ctext (in_start); + + if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr)) + temp_dir = vfs_path_as_str (panel->cwd_vpath); + else + temp_dir = start_cstr; + + if (in_start_dir != INPUT_LAST_TEXT) + g_free (in_start_dir); + in_start_dir = tree_box (temp_dir); + if (in_start_dir == NULL) + in_start_dir = g_strdup (temp_dir); + + input_assign_text (in_start, in_start_dir); + + /* Warning: Dreadful goto */ + goto find_par_start; + } + + default: + { + char *s; + +#ifdef HAVE_CHARSET + options.file_all_charsets = file_all_charsets_cbox->state; + options.content_all_charsets = content_all_charsets_cbox->state; +#endif + options.content_case_sens = content_case_sens_cbox->state; + options.content_regexp = content_regexp_cbox->state; + options.content_first_hit = content_first_hit_cbox->state; + options.content_whole_words = content_whole_words_cbox->state; + options.find_recurs = recursively_cbox->state; + options.follow_symlinks = follow_sym_cbox->state; + options.file_pattern = file_pattern_cbox->state; + options.file_case_sens = file_case_sens_cbox->state; + options.skip_hidden = skip_hidden_cbox->state; + options.ignore_dirs_enable = ignore_dirs_cbox->state; + g_free (options.ignore_dirs); + options.ignore_dirs = input_get_text (in_ignore); + + *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL; + if (input_is_empty (in_name)) + *pattern = g_strdup (options.file_pattern ? "*" : ".*"); + else + *pattern = input_get_text (in_name); + *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : "."); + if (in_start_dir != INPUT_LAST_TEXT) + g_free (in_start_dir); + in_start_dir = g_strdup (*start_dir); + + s = tilde_expand (*start_dir); + canonicalize_pathname (s); + + if (DIR_IS_DOT (s)) + { + *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath)); + /* FIXME: is panel->cwd_vpath canonicalized? */ + /* relative paths will be used in panelization */ + *start_dir_len = (ssize_t) strlen (*start_dir); + g_free (s); + } + else if (g_path_is_absolute (s)) + { + *start_dir = s; + *start_dir_len = -1; + } + else + { + /* relative paths will be used in panelization */ + *start_dir = + mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL); + *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath); + g_free (s); + } + + if (!options.ignore_dirs_enable || input_is_empty (in_ignore) + || DIR_IS_DOT (input_get_ctext (in_ignore))) + *ignore_dirs = NULL; + else + *ignore_dirs = input_get_text (in_ignore); + + find_save_options (); + + return_value = TRUE; + } + } + + widget_destroy (WIDGET (find_dlg)); + + return return_value; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void +push_directory (vfs_path_t * dir) +{ + g_queue_push_head (&dir_queue, (void *) dir); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline vfs_path_t * +pop_directory (void) +{ + return (vfs_path_t *) g_queue_pop_head (&dir_queue); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +queue_dir_free (gpointer data) +{ + vfs_path_free ((vfs_path_t *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** Remove all the items from the stack */ + +static void +clear_stack (void) +{ + g_queue_clear_full (&dir_queue, queue_dir_free); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +insert_file (const char *dir, const char *file, gsize start, gsize end) +{ + char *tmp_name = NULL; + static char *dirname = NULL; + find_match_location_t *location; + + while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1])) + dir++; + + if (old_dir != NULL) + { + if (strcmp (old_dir, dir) != 0) + { + g_free (old_dir); + old_dir = g_strdup (dir); + dirname = add_to_list (dir, NULL); + } + } + else + { + old_dir = g_strdup (dir); + dirname = add_to_list (dir, NULL); + } + + tmp_name = g_strdup_printf (" %s", file); + location = g_malloc (sizeof (*location)); + location->dir = dirname; + location->start = start; + location->end = end; + add_to_list (tmp_name, location); + g_free (tmp_name); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_add_match (const char *dir, const char *file, gsize start, gsize end) +{ + insert_file (dir, file, start, end); + + /* Don't scroll */ + if (matches == 0) + listbox_select_first (find_list); + widget_draw (WIDGET (find_list)); + + matches++; + found_num_update (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FindProgressStatus +check_find_events (WDialog * h) +{ + Gpm_Event event; + int c; + + event.x = -1; + c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE); + if (c != EV_NONE) + { + dlg_process_event (h, c, &event); + if (h->ret_value == B_ENTER + || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE) + { + /* dialog terminated */ + return FIND_ABORT; + } + if (!widget_get_state (WIDGET (h), WST_IDLE)) + { + /* searching suspended */ + return FIND_SUSPEND; + } + } + + return FIND_CONT; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * search_content: + * + * Search the content_pattern string in the DIRECTORY/FILE. + * It will add the found entries to the find listbox. + * + * returns FALSE if do_search should look for another file + * TRUE if do_search should exit and proceed to the event handler + */ + +static gboolean +search_content (WDialog * h, const char *directory, const char *filename) +{ + struct stat s; + char buffer[BUF_4K] = ""; /* raw input buffer */ + int file_fd; + gboolean ret_val = FALSE; + vfs_path_t *vpath; + gint64 tv; + gboolean status_updated = FALSE; + + vpath = vfs_path_build_filename (directory, filename, (char *) NULL); + + if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode)) + { + vfs_path_free (vpath, TRUE); + return FALSE; + } + + file_fd = mc_open (vpath, O_RDONLY); + vfs_path_free (vpath, TRUE); + + if (file_fd == -1) + return FALSE; + + /* get time elapsed from last refresh */ + tv = g_get_monotonic_time (); + + if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL) + { + g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename); + status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8)); + mc_refresh (); + last_refresh = tv; + status_updated = TRUE; + } + + tty_enable_interrupt_key (); + tty_got_interrupt (); + + { + int line = 1; + int pos = 0; + int n_read = 0; + off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */ + gboolean found = FALSE; + gsize found_len; + gsize found_start; + char result[BUF_MEDIUM]; + char *strbuf = NULL; /* buffer for fetched string */ + int strbuf_size = 0; + int i = -1; /* compensate for a newline we'll add when we first enter the loop */ + + if (resuming) + { + /* We've been previously suspended, start from the previous position */ + resuming = FALSE; + line = last_line; + pos = last_pos; + off = last_off; + i = last_i; + } + + while (!ret_val) + { + char ch = '\0'; + + off += i + 1; /* the previous line, plus a newline character */ + i = 0; + + /* read to buffer and get line from there */ + while (TRUE) + { + if (pos >= n_read) + { + pos = 0; + n_read = mc_read (file_fd, buffer, sizeof (buffer)); + if (n_read <= 0) + break; + } + + ch = buffer[pos++]; + if (ch == '\0') + { + /* skip possible leading zero(s) */ + if (i == 0) + { + off++; + continue; + } + break; + } + + if (i >= strbuf_size - 1) + { + strbuf_size += 128; + strbuf = g_realloc (strbuf, strbuf_size); + } + + /* Strip newline */ + if (ch == '\n') + break; + + strbuf[i++] = ch; + } + + if (i == 0) + { + if (ch == '\0') + break; + + /* if (ch == '\n'): do not search in empty strings */ + goto skip_search; + } + + strbuf[i] = '\0'; + + if (!found /* Search in binary line once */ + && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len)) + { + if (!status_updated) + { + /* if we add results for a file, we have to ensure that + name of this file is shown in status bar */ + g_snprintf (result, sizeof (result), _("Grepping in %s"), filename); + status_update (str_trunc (result, WIDGET (h)->rect.cols - 8)); + mc_refresh (); + last_refresh = tv; + status_updated = TRUE; + } + + g_snprintf (result, sizeof (result), "%d:%s", line, filename); + found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */ + find_add_match (directory, result, found_start, found_start + found_len); + found = TRUE; + } + + if (found && options.content_first_hit) + break; + + if (ch == '\n') + { + skip_search: + found = FALSE; + line++; + } + + if ((line & 0xff) == 0) + { + FindProgressStatus res; + + res = check_find_events (h); + switch (res) + { + case FIND_ABORT: + stop_idle (h); + ret_val = TRUE; + break; + case FIND_SUSPEND: + resuming = TRUE; + last_line = line; + last_pos = pos; + last_off = off; + last_i = i; + ret_val = TRUE; + break; + default: + break; + } + } + } + + g_free (strbuf); + } + + tty_disable_interrupt_key (); + mc_close (file_fd); + return ret_val; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + If dir is absolute, this means we're within dir and searching file here. + If dir is relative, this means we're going to add dir to the directory stack. +**/ +static gboolean +find_ignore_dir_search (const char *dir) +{ + if (find_ignore_dirs != NULL) + { + const size_t dlen = strlen (dir); + const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0; + + char **ignore_dir; + + for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++) + { + const size_t ilen = strlen (*ignore_dir); + const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0; + + /* ignore dir is too long -- skip it */ + if (dlen < ilen) + continue; + + /* handle absolute and relative paths */ + switch (iabs | dabs) + { + case 0: /* both paths are relative */ + case 3: /* both paths are absolute */ + /* if ignore dir is not a path of dir -- skip it */ + if (strncmp (dir, *ignore_dir, ilen) == 0) + { + /* be sure that ignore dir is not a part of dir like: + ignore dir is "h", dir is "home" */ + if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen])) + return TRUE; + } + break; + case 1: /* dir is absolute, ignore_dir is relative */ + { + char *d; + + d = strstr (dir, *ignore_dir); + if (d != NULL && IS_PATH_SEP (d[-1]) + && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen]))) + return TRUE; + } + break; + case 2: /* dir is relative, ignore_dir is absolute */ + /* FIXME: skip this case */ + break; + default: /* this cannot occurs */ + return FALSE; + } + } + } + + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_rotate_dash (const WDialog * h, gboolean show) +{ + static size_t pos = 0; + static const char rotating_dash[4] = "|/-\\"; + const Widget *w = CONST_WIDGET (h); + const int *colors; + + colors = widget_get_colors (w); + tty_setcolor (colors[DLG_COLOR_NORMAL]); + widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4); + tty_print_char (show ? rotating_dash[pos] : ' '); + pos = (pos + 1) % sizeof (rotating_dash); + mc_refresh (); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +do_search (WDialog * h) +{ + static struct vfs_dirent *dp = NULL; + static DIR *dirp = NULL; + static char *directory = NULL; + static gboolean pop_start_dir = TRUE; + struct stat tmp_stat; + gsize bytes_found; + unsigned short count; + + if (h == NULL) + { /* someone forces me to close dirp */ + if (dirp != NULL) + { + mc_closedir (dirp); + dirp = NULL; + } + MC_PTR_FREE (directory); + dp = NULL; + pop_start_dir = TRUE; + return 1; + } + + for (count = 0; count < 32; count++) + { + while (dp == NULL) + { + if (dirp != NULL) + { + mc_closedir (dirp); + dirp = NULL; + } + + while (dirp == NULL) + { + vfs_path_t *tmp_vpath = NULL; + + tty_setcolor (REVERSE_COLOR); + + while (TRUE) + { + tmp_vpath = pop_directory (); + if (tmp_vpath == NULL) + { + running = FALSE; + if (ignore_count == 0) + status_update (_("Finished")); + else + { + char msg[BUF_SMALL]; + + g_snprintf (msg, sizeof (msg), + ngettext ("Finished (ignored %zu directory)", + "Finished (ignored %zu directories)", + ignore_count), ignore_count); + status_update (msg); + } + if (verbose) + find_rotate_dash (h, FALSE); + stop_idle (h); + return 0; + } + + /* The start directory is the first one in the stack (see do_find() below). + Do not apply ignore_dir to it. */ + if (pop_start_dir) + { + pop_start_dir = FALSE; + break; + } + + pop_start_dir = FALSE; + + /* handle absolute ignore dirs here */ + if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath))) + break; + + vfs_path_free (tmp_vpath, TRUE); + ignore_count++; + } + + g_free (directory); + + if (verbose) + { + char buffer[BUF_MEDIUM]; + + directory = (char *) vfs_path_as_str (tmp_vpath); + g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory); + status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8)); + } + + dirp = mc_opendir (tmp_vpath); + directory = vfs_path_free (tmp_vpath, FALSE); + } /* while (!dirp) */ + + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + } /* while (!dp) */ + + if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name)) + { + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + + return 1; + } + + if (!(options.skip_hidden && (dp->d_name[0] == '.'))) + { + gboolean search_ok; + + if (options.find_recurs && (directory != NULL)) + { /* Can directory be NULL ? */ + /* handle relative ignore dirs here */ + if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name)) + ignore_count++; + else + { + vfs_path_t *tmp_vpath; + int stat_res; + + tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL); + + if (options.follow_symlinks) + stat_res = mc_stat (tmp_vpath, &tmp_stat); + else + stat_res = mc_lstat (tmp_vpath, &tmp_stat); + + if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode)) + push_directory (tmp_vpath); + else + vfs_path_free (tmp_vpath, TRUE); + } + } + + search_ok = mc_search_run (search_file_handle, dp->d_name, + 0, strlen (dp->d_name), &bytes_found); + + if (search_ok) + { + if (content_pattern == NULL) + find_add_match (directory, dp->d_name, 0, 0); + else if (search_content (h, directory, dp->d_name)) + return 1; + } + } + + /* skip invalid filenames */ + while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name)) + ; + } /* for */ + + if (verbose) + find_rotate_dash (h, TRUE); + + return 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +init_find_vars (void) +{ + MC_PTR_FREE (old_dir); + matches = 0; + ignore_count = 0; + + /* Remove all the items from the stack */ + clear_stack (); + + g_strfreev (find_ignore_dirs); + find_ignore_dirs = NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start, + off_t search_end) +{ + const char *filename = NULL; + int line; + vfs_path_t *fullname_vpath; + + if (content_pattern != NULL) + { + filename = strchr (file + 4, ':') + 1; + line = atoi (file + 4); + } + else + { + filename = file + 4; + line = 0; + } + + fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL); + if (edit) + edit_file_at_line (fullname_vpath, use_internal_edit, line); + else + view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start, + search_end); + vfs_path_free (fullname_vpath, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit) +{ + char *text = NULL; + find_match_location_t *location; + + listbox_get_current (find_list, &text, (void **) &location); + + if ((text == NULL) || (location == NULL) || (location->dir == NULL)) + return MSG_NOT_HANDLED; + + find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end); + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_calc_button_locations (const WDialog * h, gboolean all_buttons) +{ + const int cols = CONST_WIDGET (h)->rect.cols; + + int l1, l2; + + l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3; + l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2; + + fbuts[0].x = (cols - l1) / 2; + fbuts[1].x = fbuts[0].x + fbuts[0].len + 1; + fbuts[2].x = fbuts[1].x + fbuts[1].len + 1; + fbuts[3].x = fbuts[2].x; + fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1; + + if (all_buttons) + { + fbuts[5].x = (cols - l2) / 2; + fbuts[6].x = fbuts[5].x + fbuts[5].len + 1; + fbuts[7].x = fbuts[6].x + fbuts[6].len + 1; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_adjust_header (WDialog * h) +{ + char title[BUF_MEDIUM]; + int title_len; + + if (content_pattern != NULL) + g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern, + content_pattern); + else + g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern); + + title_len = str_term_width1 (title); + if (title_len > WIDGET (h)->rect.cols - 6) + { + /* title is too wide, truncate it */ + title_len = WIDGET (h)->rect.cols - 6; + title_len = str_column_to_pos (title, title_len); + title_len -= 3; /* reserve space for three dots */ + title_len = str_offset_to_pos (title, title_len); + /* mark that title is truncated */ + memmove (title + title_len, "...", 4); + } + + frame_set_title (FRAME (h->bg), title); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +find_relocate_buttons (const WDialog * h, gboolean all_buttons) +{ + size_t i; + + find_calc_button_locations (h, all_buttons); + + for (i = 0; i < fbuts_num; i++) + fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +find_resize (WDialog * h) +{ + Widget *w = WIDGET (h); + WRect r = w->rect; + + r.lines = LINES - 4; + r.cols = COLS - 16; + dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r); + find_adjust_header (h); + find_relocate_buttons (h, TRUE); + + return MSG_HANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ + +static cb_ret_t +find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) +{ + WDialog *h = DIALOG (w); + + switch (msg) + { + case MSG_INIT: + group_default_callback (w, NULL, MSG_INIT, 0, NULL); + find_adjust_header (h); + return MSG_HANDLED; + + case MSG_KEY: + if (parm == KEY_F (3) || parm == KEY_F (13)) + { + gboolean unparsed_view = (parm == KEY_F (13)); + + return view_edit_currently_selected_file (unparsed_view, FALSE); + } + if (parm == KEY_F (4)) + return view_edit_currently_selected_file (FALSE, TRUE); + return MSG_NOT_HANDLED; + + case MSG_RESIZE: + return find_resize (h); + + case MSG_IDLE: + do_search (h); + return MSG_HANDLED; + + default: + return dlg_default_callback (w, sender, msg, parm, data); + } +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handles the Stop/Start button in the find window */ + +static int +start_stop (WButton * button, int action) +{ + Widget *w = WIDGET (button); + + (void) action; + + running = is_start; + widget_idle (WIDGET (find_dlg), running); + is_start = !is_start; + + status_update (is_start ? _("Stopped") : _("Searching")); + button_set_text (button, fbuts[is_start ? 3 : 2].text); + + find_relocate_buttons (DIALOG (w->owner), FALSE); + widget_draw (WIDGET (w->owner)); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handle view command, when invoked as a button */ + +static int +find_do_view_file (WButton * button, int action) +{ + (void) button; + (void) action; + + view_edit_currently_selected_file (FALSE, FALSE); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Handle edit command, when invoked as a button */ + +static int +find_do_edit_file (WButton * button, int action) +{ + (void) button; + (void) action; + + view_edit_currently_selected_file (FALSE, TRUE); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +setup_gui (void) +{ + WGroup *g; + size_t i; + int lines, cols; + int y; + + static gboolean i18n_flag = FALSE; + + if (!i18n_flag) + { + for (i = 0; i < fbuts_num; i++) + { +#ifdef ENABLE_NLS + fbuts[i].text = _(fbuts[i].text); +#endif /* ENABLE_NLS */ + fbuts[i].len = str_term_width1 (fbuts[i].text) + 3; + if (fbuts[i].flags == DEFPUSH_BUTTON) + fbuts[i].len += 2; + } + + i18n_flag = TRUE; + } + + lines = LINES - 4; + cols = COLS - 16; + + find_dlg = + dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL, + "[Find File]", NULL); + g = GROUP (find_dlg); + + find_calc_button_locations (find_dlg, TRUE); + + y = 2; + find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL); + group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL); + y += WIDGET (find_list)->rect.lines; + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + found_num_label = label_new (y++, 4, NULL); + group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL); + + status_label = label_new (y++, 4, _("Searching")); + group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL); + + group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL); + + for (i = 0; i < fbuts_num; i++) + { + if (i == 3) + fbuts[3].button = fbuts[2].button; + else + { + fbuts[i].button = + WIDGET (button_new + (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text, + fbuts[i].callback)); + group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL); + } + + if (i == quit_button) + y++; + } + + widget_select (WIDGET (find_list)); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +run_process (void) +{ + int ret; + + search_content_handle = mc_search_new (content_pattern, NULL); + if (search_content_handle) + { + search_content_handle->search_type = + options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL; + search_content_handle->is_case_sensitive = options.content_case_sens; + search_content_handle->whole_words = options.content_whole_words; +#ifdef HAVE_CHARSET + search_content_handle->is_all_charsets = options.content_all_charsets; +#endif + } + search_file_handle = mc_search_new (find_pattern, NULL); + search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX; + search_file_handle->is_case_sensitive = options.file_case_sens; +#ifdef HAVE_CHARSET + search_file_handle->is_all_charsets = options.file_all_charsets; +#endif + search_file_handle->is_entire_line = options.file_pattern; + + resuming = FALSE; + + widget_idle (WIDGET (find_dlg), TRUE); + ret = dlg_run (find_dlg); + + mc_search_free (search_file_handle); + search_file_handle = NULL; + mc_search_free (search_content_handle); + search_content_handle = NULL; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +kill_gui (void) +{ + Widget *w = WIDGET (find_dlg); + + widget_idle (w, FALSE); + widget_destroy (w); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +do_find (WPanel * panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs, + char **dirname, char **filename) +{ + int return_value = 0; + char *dir_tmp = NULL, *file_tmp = NULL; + + setup_gui (); + + init_find_vars (); + parse_ignore_dirs (ignore_dirs); + push_directory (vfs_path_from_str (start_dir)); + + return_value = run_process (); + + /* Clear variables */ + init_find_vars (); + + get_list_info (&file_tmp, &dir_tmp, NULL, NULL); + + if (dir_tmp) + *dirname = g_strdup (dir_tmp); + if (file_tmp) + *filename = g_strdup (file_tmp); + + if (return_value == B_PANELIZE && *filename) + { + struct stat st; + GList *entry; + dir_list *list = &panel->dir; + char *name = NULL; + gboolean ok = TRUE; + + panel_clean_dir (panel); + dir_list_init (list); + + for (entry = listbox_get_first_link (find_list); entry != NULL && ok; + entry = g_list_next (entry)) + { + const char *lc_filename = NULL; + WLEntry *le = LENTRY (entry->data); + find_match_location_t *location = le->data; + char *p; + gboolean link_to_dir, stale_link; + + if ((le->text == NULL) || (location == NULL) || (location->dir == NULL)) + continue; + + if (!content_is_empty) + lc_filename = strchr (le->text + 4, ':') + 1; + else + lc_filename = le->text + 4; + + name = mc_build_filename (location->dir, lc_filename, (char *) NULL); + /* skip initial start dir */ + if (start_dir_len < 0) + p = name; + else + { + p = name + (size_t) start_dir_len; + if (IS_PATH_SEP (*p)) + p++; + } + + if (!handle_path (p, &st, &link_to_dir, &stale_link)) + { + g_free (name); + continue; + } + + /* don't add files more than once to the panel */ + if (!content_is_empty && list->len != 0 + && strcmp (list->list[list->len - 1].fname->str, p) == 0) + { + g_free (name); + continue; + } + + ok = dir_list_append (list, p, &st, link_to_dir, stale_link); + + g_free (name); + + if ((list->len & 15) == 0) + rotate_dash (TRUE); + } + + panel->is_panelized = TRUE; + panel_panelize_absolutize_if_needed (panel); + panel_panelize_save (panel); + } + + kill_gui (); + do_search (NULL); /* force do_search to release resources */ + MC_PTR_FREE (old_dir); + rotate_dash (FALSE); + + return return_value; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +find_cmd (WPanel * panel) +{ + char *start_dir = NULL, *ignore_dirs = NULL; + ssize_t start_dir_len; + + find_pattern = NULL; + content_pattern = NULL; + + while (find_parameters (panel, &start_dir, &start_dir_len, + &ignore_dirs, &find_pattern, &content_pattern)) + { + char *filename = NULL, *dirname = NULL; + int v = B_CANCEL; + + content_is_empty = content_pattern == NULL; + + if (find_pattern[0] != '\0') + { + last_refresh = 0; + + is_start = FALSE; + + if (!content_is_empty && !str_is_valid_string (content_pattern)) + MC_PTR_FREE (content_pattern); + + v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename); + } + + g_free (start_dir); + g_free (ignore_dirs); + MC_PTR_FREE (find_pattern); + + if (v == B_ENTER) + { + if (dirname != NULL) + { + vfs_path_t *dirname_vpath; + + dirname_vpath = vfs_path_from_str (dirname); + panel_cd (panel, dirname_vpath, cd_exact); + vfs_path_free (dirname_vpath, TRUE); + /* *INDENT-OFF* */ + if (filename != NULL) + panel_set_current_by_name (panel, + filename + (content_pattern != NULL + ? strchr (filename + 4, ':') - filename + 1 + : 4)); + /* *INDENT-ON* */ + } + else if (filename != NULL) + { + vfs_path_t *filename_vpath; + + filename_vpath = vfs_path_from_str (filename); + panel_cd (panel, filename_vpath, cd_exact); + vfs_path_free (filename_vpath, TRUE); + } + } + + MC_PTR_FREE (content_pattern); + g_free (dirname); + g_free (filename); + + if (v == B_ENTER || v == B_CANCEL) + break; + + if (v == B_PANELIZE) + { + panel_re_sort (panel); + panel_set_current_by_name (panel, NULL); + break; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ |