(filevercmp): make inline.
[midnight-commander.git] / src / viewer / search.c
blob1887c2561d2c8239a4520c8da29d7b98b40a49f3
1 /*
2 Internal file viewer for the Midnight Commander
3 Function for search data
5 Copyright (C) 1994-2024
6 Free Software Foundation, Inc.
8 Written by:
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
11 Jakub Jelinek, 1995
12 Joseph M. Hinkle, 1996
13 Norbert Warmuth, 1997
14 Pavel Machek, 1998
15 Roland Illig <roland.illig@gmx.de>, 2004, 2005
16 Slava Zanko <slavazanko@google.com>, 2009
17 Andrew Borodin <aborodin@vmail.ru>, 2009-2022
18 Ilia Maslakov <il.smind@gmail.com>, 2009
20 This file is part of the Midnight Commander.
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <http://www.gnu.org/licenses/>.
36 #include <config.h>
38 #include "lib/global.h"
39 #include "lib/strutil.h"
40 #ifdef HAVE_CHARSET
41 #include "lib/charsets.h" /* cp_source */
42 #endif
43 #include "lib/widget.h"
45 #include "src/setup.h"
47 #include "internal.h"
49 /*** global variables ****************************************************************************/
51 mcview_search_options_t mcview_search_options = {
52 .type = MC_SEARCH_T_NORMAL,
53 .case_sens = FALSE,
54 .backwards = FALSE,
55 .whole_words = FALSE,
56 .all_codepages = FALSE
59 /*** file scope macro definitions ****************************************************************/
61 /*** file scope type declarations ****************************************************************/
63 typedef struct
65 simple_status_msg_t status_msg; /* base class */
67 gboolean first;
68 WView *view;
69 off_t offset;
70 } mcview_search_status_msg_t;
72 /*** forward declarations (file scope functions) *************************************************/
74 /*** file scope variables ************************************************************************/
76 static int search_cb_char_curr_index = -1;
77 static char search_cb_char_buffer[6];
79 /* --------------------------------------------------------------------------------------------- */
80 /*** file scope functions ************************************************************************/
81 /* --------------------------------------------------------------------------------------------- */
83 static int
84 mcview_search_status_update_cb (status_msg_t *sm)
86 simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
87 mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) sm;
88 Widget *wd = WIDGET (sm->dlg);
89 int percent = -1;
91 if (verbose)
92 percent = mcview_calc_percent (vsm->view, vsm->offset);
94 if (percent >= 0)
95 label_set_textv (ssm->label, _("Searching %s: %3d%%"), vsm->view->last_search_string,
96 percent);
97 else
98 label_set_textv (ssm->label, _("Searching %s"), vsm->view->last_search_string);
100 if (vsm->first)
102 Widget *lw = WIDGET (ssm->label);
103 WRect r;
105 r = wd->rect;
106 r.cols = MAX (r.cols, lw->rect.cols + 6);
107 widget_set_size_rect (wd, &r);
108 r = lw->rect;
109 r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
110 widget_set_size_rect (lw, &r);
111 vsm->first = FALSE;
114 return status_msg_common_update (sm);
117 /* --------------------------------------------------------------------------------------------- */
119 static void
120 mcview_search_update_steps (WView *view)
122 off_t filesize;
124 filesize = mcview_get_filesize (view);
126 if (filesize != 0)
127 view->update_steps = filesize / 100;
128 else /* viewing a data stream, not a file */
129 view->update_steps = 40000;
131 /* Do not update the percent display but every 20 kb */
132 if (view->update_steps < 20000)
133 view->update_steps = 20000;
135 /* Make interrupt more responsive */
136 if (view->update_steps > 40000)
137 view->update_steps = 40000;
140 /* --------------------------------------------------------------------------------------------- */
142 static gboolean
143 mcview_find (mcview_search_status_msg_t *ssm, off_t search_start, off_t search_end, gsize *len)
145 WView *view = ssm->view;
147 view->search_numNeedSkipChar = 0;
148 search_cb_char_curr_index = -1;
150 if (mcview_search_options.backwards)
152 search_end = mcview_get_filesize (view);
153 while (search_start >= 0)
155 gboolean ok;
157 view->search_nroff_seq->index = search_start;
158 mcview_nroff_seq_info (view->search_nroff_seq);
160 if (search_end > search_start + (off_t) view->search->original.str->len
161 && mc_search_is_fixed_search_str (view->search))
162 search_end = search_start + view->search->original.str->len;
164 ok = mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
165 if (ok && view->search->normal_offset == search_start)
167 if (view->mode_flags.nroff)
168 view->search->normal_offset++;
169 return TRUE;
172 /* We abort the search in case of a pattern error, or if the user aborts
173 the search. In other words: in all cases except "string not found". */
174 if (!ok && view->search->error != MC_SEARCH_E_NOTFOUND)
175 return FALSE;
177 search_start--;
180 mc_search_set_error (view->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
181 return FALSE;
183 view->search_nroff_seq->index = search_start;
184 mcview_nroff_seq_info (view->search_nroff_seq);
186 return mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
189 /* --------------------------------------------------------------------------------------------- */
191 static void
192 mcview_search_show_result (WView *view, size_t match_len)
194 int nroff_len;
196 nroff_len =
197 view->mode_flags.nroff
198 ? mcview__get_nroff_real_len (view, view->search->start_buffer,
199 view->search->normal_offset - view->search->start_buffer) : 0;
200 view->search_start = view->search->normal_offset + nroff_len;
202 if (!view->mode_flags.hex)
203 view->search_start++;
205 nroff_len =
206 view->mode_flags.nroff ? mcview__get_nroff_real_len (view, view->search_start - 1,
207 match_len) : 0;
208 view->search_end = view->search_start + match_len + nroff_len;
210 mcview_moveto_match (view);
213 /* --------------------------------------------------------------------------------------------- */
214 /*** public functions ****************************************************************************/
215 /* --------------------------------------------------------------------------------------------- */
217 gboolean
218 mcview_search_init (WView *view)
220 #ifdef HAVE_CHARSET
221 view->search = mc_search_new (view->last_search_string, cp_source);
222 #else
223 view->search = mc_search_new (view->last_search_string, NULL);
224 #endif
226 view->search_nroff_seq = mcview_nroff_seq_new (view);
228 if (view->search == NULL)
229 return FALSE;
231 view->search->search_type = mcview_search_options.type;
232 #ifdef HAVE_CHARSET
233 view->search->is_all_charsets = mcview_search_options.all_codepages;
234 #endif
235 view->search->is_case_sensitive = mcview_search_options.case_sens;
236 view->search->whole_words = mcview_search_options.whole_words;
237 view->search->search_fn = mcview_search_cmd_callback;
238 view->search->update_fn = mcview_search_update_cmd_callback;
240 return TRUE;
243 /* --------------------------------------------------------------------------------------------- */
245 void
246 mcview_search_deinit (WView *view)
248 mc_search_free (view->search);
249 g_free (view->last_search_string);
250 mcview_nroff_seq_free (&view->search_nroff_seq);
253 /* --------------------------------------------------------------------------------------------- */
255 mc_search_cbret_t
256 mcview_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
258 WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
260 /* view_read_continue (view, &view->search_onechar_info); *//* AB:FIXME */
261 if (!view->mode_flags.nroff)
263 mcview_get_byte (view, char_offset, current_char);
264 return MC_SEARCH_CB_OK;
267 if (view->search_numNeedSkipChar != 0)
269 view->search_numNeedSkipChar--;
270 return MC_SEARCH_CB_SKIP;
273 if (search_cb_char_curr_index == -1
274 || search_cb_char_curr_index >= view->search_nroff_seq->char_length)
276 if (search_cb_char_curr_index != -1)
277 mcview_nroff_seq_next (view->search_nroff_seq);
279 search_cb_char_curr_index = 0;
280 if (view->search_nroff_seq->char_length > 1)
281 g_unichar_to_utf8 (view->search_nroff_seq->current_char, search_cb_char_buffer);
282 else
283 search_cb_char_buffer[0] = (char) view->search_nroff_seq->current_char;
285 if (view->search_nroff_seq->type != NROFF_TYPE_NONE)
287 switch (view->search_nroff_seq->type)
289 case NROFF_TYPE_BOLD:
290 view->search_numNeedSkipChar = 1 + view->search_nroff_seq->char_length; /* real char length and 0x8 */
291 break;
292 case NROFF_TYPE_UNDERLINE:
293 view->search_numNeedSkipChar = 2; /* underline symbol and ox8 */
294 break;
295 default:
296 break;
299 return MC_SEARCH_CB_INVALID;
302 *current_char = search_cb_char_buffer[search_cb_char_curr_index];
303 search_cb_char_curr_index++;
305 return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
308 /* --------------------------------------------------------------------------------------------- */
310 mc_search_cbret_t
311 mcview_search_update_cmd_callback (const void *user_data, gsize char_offset)
313 status_msg_t *sm = STATUS_MSG (user_data);
314 mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
315 WView *view = vsm->view;
316 gboolean do_update = FALSE;
317 mc_search_cbret_t result = MC_SEARCH_CB_OK;
319 vsm->offset = (off_t) char_offset;
321 if (mcview_search_options.backwards)
323 if (vsm->offset <= view->update_activate)
325 view->update_activate -= view->update_steps;
327 do_update = TRUE;
330 else
332 if (vsm->offset >= view->update_activate)
334 view->update_activate += view->update_steps;
336 do_update = TRUE;
340 if (do_update && sm->update (sm) == B_CANCEL)
341 result = MC_SEARCH_CB_ABORT;
343 /* may be in future return from this callback will change current position in searching block. */
345 return result;
348 /* --------------------------------------------------------------------------------------------- */
350 void
351 mcview_do_search (WView *view, off_t want_search_start)
353 mcview_search_status_msg_t vsm;
355 off_t search_start = 0;
356 off_t orig_search_start = view->search_start;
357 gboolean found = FALSE;
359 size_t match_len;
361 view->search_start = want_search_start;
362 /* for avoid infinite search loop we need to increase or decrease start offset of search */
364 if (view->search_start != 0)
366 if (!view->mode_flags.nroff)
367 search_start = view->search_start + (mcview_search_options.backwards ? -2 : 0);
368 else
370 if (mcview_search_options.backwards)
372 mcview_nroff_t *nroff;
374 nroff = mcview_nroff_seq_new_num (view, view->search_start);
375 if (mcview_nroff_seq_prev (nroff) != -1)
376 search_start =
377 -(mcview__get_nroff_real_len (view, nroff->index - 1, 2) +
378 nroff->char_length + 1);
379 else
380 search_start = -2;
382 mcview_nroff_seq_free (&nroff);
384 else
386 search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
388 search_start += view->search_start;
392 if (mcview_search_options.backwards && search_start < 0)
393 search_start = 0;
395 /* Compute the percent steps */
396 mcview_search_update_steps (view);
398 view->update_activate = search_start;
400 vsm.first = TRUE;
401 vsm.view = view;
402 vsm.offset = search_start;
404 status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
405 mcview_search_status_update_cb, NULL);
409 off_t growbufsize;
411 if (view->growbuf_in_use)
412 growbufsize = mcview_growbuf_filesize (view);
413 else
414 growbufsize = view->search->original.str->len;
416 if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
418 mcview_search_show_result (view, match_len);
419 found = TRUE;
420 break;
423 /* Search error is here.
424 * MC_SEARCH_E_NOTFOUND: continue search
425 * others: stop
427 if (view->search->error != MC_SEARCH_E_NOTFOUND)
428 break;
430 search_start = growbufsize - view->search->original.str->len;
432 while (search_start > 0 && mcview_may_still_grow (view));
434 /* After mcview_may_still_grow (view) == FALSE we have remained last chunk. Search there. */
435 if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
436 && !mcview_search_options.backwards
437 && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
439 mcview_search_show_result (view, match_len);
440 found = TRUE;
443 status_msg_deinit (STATUS_MSG (&vsm));
445 if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
446 && !mcview_search_options.backwards)
448 view->search_start = orig_search_start;
449 mcview_update (view);
451 if (query_dialog
452 (_("Search done"), _("Continue from beginning?"), D_NORMAL, 2, _("&Yes"),
453 _("&No")) != 0)
454 found = TRUE;
455 else
457 /* continue search from beginning */
458 view->update_activate = 0;
460 vsm.first = TRUE;
461 vsm.view = view;
462 vsm.offset = 0;
464 status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
465 mcview_search_status_update_cb, NULL);
467 /* search from file begin up to initial search start position */
468 if (mcview_find (&vsm, 0, orig_search_start, &match_len))
470 mcview_search_show_result (view, match_len);
471 found = TRUE;
474 status_msg_deinit (STATUS_MSG (&vsm));
478 if (!found)
480 view->search_start = orig_search_start;
481 mcview_update (view);
483 if (view->search->error == MC_SEARCH_E_NOTFOUND)
484 query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
485 else if (view->search->error_str != NULL)
486 query_dialog (_("Search"), view->search->error_str, D_NORMAL, 1, _("&Dismiss"));
488 view->dirty++;
491 /* --------------------------------------------------------------------------------------------- */