1 /*
2    Internal file viewer for the Midnight Commander
3    Function for search data
4 
5    Copyright (C) 1994-2021
6    Free Software Foundation, Inc.
7 
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, 2013
18    Ilia Maslakov <il.smind@gmail.com>, 2009
19 
20    This file is part of the Midnight Commander.
21 
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.
26 
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.
31 
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/>.
34  */
35 
36 #include <config.h>
37 
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"
44 
45 #include "src/setup.h"
46 
47 #include "internal.h"
48 
49 /*** global variables ****************************************************************************/
50 
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
57 };
58 
59 /*** file scope macro definitions ****************************************************************/
60 
61 /*** file scope type declarations ****************************************************************/
62 
63 typedef struct
64 {
65     simple_status_msg_t status_msg;     /* base class */
66 
67     gboolean first;
68     WView *view;
69     off_t offset;
70 } mcview_search_status_msg_t;
71 
72 /*** file scope variables ************************************************************************/
73 
74 static int search_cb_char_curr_index = -1;
75 static char search_cb_char_buffer[6];
76 
77 /* --------------------------------------------------------------------------------------------- */
78 /*** file scope functions ************************************************************************/
79 /* --------------------------------------------------------------------------------------------- */
80 
81 static int
mcview_search_status_update_cb(status_msg_t * sm)82 mcview_search_status_update_cb (status_msg_t * sm)
83 {
84     simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
85     mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) sm;
86     Widget *wd = WIDGET (sm->dlg);
87     int percent = -1;
88 
89     if (verbose)
90         percent = mcview_calc_percent (vsm->view, vsm->offset);
91 
92     if (percent >= 0)
93         label_set_textv (ssm->label, _("Searching %s: %3d%%"), vsm->view->last_search_string,
94                          percent);
95     else
96         label_set_textv (ssm->label, _("Searching %s"), vsm->view->last_search_string);
97 
98     if (vsm->first)
99     {
100         int wd_width;
101         Widget *lw = WIDGET (ssm->label);
102 
103         wd_width = MAX (wd->cols, lw->cols + 6);
104         widget_set_size (wd, wd->y, wd->x, wd->lines, wd_width);
105         widget_set_size (lw, lw->y, wd->x + (wd->cols - lw->cols) / 2, lw->lines, lw->cols);
106         vsm->first = FALSE;
107     }
108 
109     return status_msg_common_update (sm);
110 }
111 
112 /* --------------------------------------------------------------------------------------------- */
113 
114 static void
mcview_search_update_steps(WView * view)115 mcview_search_update_steps (WView * view)
116 {
117     off_t filesize;
118 
119     filesize = mcview_get_filesize (view);
120 
121     if (filesize != 0)
122         view->update_steps = filesize / 100;
123     else                        /* viewing a data stream, not a file */
124         view->update_steps = 40000;
125 
126     /* Do not update the percent display but every 20 kb */
127     if (view->update_steps < 20000)
128         view->update_steps = 20000;
129 
130     /* Make interrupt more responsive */
131     if (view->update_steps > 40000)
132         view->update_steps = 40000;
133 }
134 
135 /* --------------------------------------------------------------------------------------------- */
136 
137 static gboolean
mcview_find(mcview_search_status_msg_t * ssm,off_t search_start,off_t search_end,gsize * len)138 mcview_find (mcview_search_status_msg_t * ssm, off_t search_start, off_t search_end, gsize * len)
139 {
140     WView *view = ssm->view;
141 
142     view->search_numNeedSkipChar = 0;
143     search_cb_char_curr_index = -1;
144 
145     if (mcview_search_options.backwards)
146     {
147         search_end = mcview_get_filesize (view);
148         while (search_start >= 0)
149         {
150             gboolean ok;
151 
152             view->search_nroff_seq->index = search_start;
153             mcview_nroff_seq_info (view->search_nroff_seq);
154 
155             if (search_end > search_start + (off_t) view->search->original_len
156                 && mc_search_is_fixed_search_str (view->search))
157                 search_end = search_start + view->search->original_len;
158 
159             ok = mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
160             if (ok && view->search->normal_offset == search_start)
161             {
162                 if (view->mode_flags.nroff)
163                     view->search->normal_offset++;
164                 return TRUE;
165             }
166 
167             /* We abort the search in case of a pattern error, or if the user aborts
168                the search. In other words: in all cases except "string not found". */
169             if (!ok && view->search->error != MC_SEARCH_E_NOTFOUND)
170                 return FALSE;
171 
172             search_start--;
173         }
174 
175         mc_search_set_error (view->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
176         return FALSE;
177     }
178     view->search_nroff_seq->index = search_start;
179     mcview_nroff_seq_info (view->search_nroff_seq);
180 
181     return mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
182 }
183 
184 /* --------------------------------------------------------------------------------------------- */
185 
186 static void
mcview_search_show_result(WView * view,size_t match_len)187 mcview_search_show_result (WView * view, size_t match_len)
188 {
189     int nroff_len;
190 
191     nroff_len =
192         view->mode_flags.nroff
193         ? mcview__get_nroff_real_len (view, view->search->start_buffer,
194                                       view->search->normal_offset - view->search->start_buffer) : 0;
195     view->search_start = view->search->normal_offset + nroff_len;
196 
197     if (!view->mode_flags.hex)
198         view->search_start++;
199 
200     nroff_len =
201         view->mode_flags.nroff ? mcview__get_nroff_real_len (view, view->search_start - 1,
202                                                              match_len) : 0;
203     view->search_end = view->search_start + match_len + nroff_len;
204 
205     mcview_moveto_match (view);
206 }
207 
208 /* --------------------------------------------------------------------------------------------- */
209 /*** public functions ****************************************************************************/
210 /* --------------------------------------------------------------------------------------------- */
211 
212 gboolean
mcview_search_init(WView * view)213 mcview_search_init (WView * view)
214 {
215 #ifdef HAVE_CHARSET
216     view->search = mc_search_new (view->last_search_string, cp_source);
217 #else
218     view->search = mc_search_new (view->last_search_string, NULL);
219 #endif
220 
221     view->search_nroff_seq = mcview_nroff_seq_new (view);
222 
223     if (view->search == NULL)
224         return FALSE;
225 
226     view->search->search_type = mcview_search_options.type;
227 #ifdef HAVE_CHARSET
228     view->search->is_all_charsets = mcview_search_options.all_codepages;
229 #endif
230     view->search->is_case_sensitive = mcview_search_options.case_sens;
231     view->search->whole_words = mcview_search_options.whole_words;
232     view->search->search_fn = mcview_search_cmd_callback;
233     view->search->update_fn = mcview_search_update_cmd_callback;
234 
235     return TRUE;
236 }
237 
238 /* --------------------------------------------------------------------------------------------- */
239 
240 void
mcview_search_deinit(WView * view)241 mcview_search_deinit (WView * view)
242 {
243     mc_search_free (view->search);
244     g_free (view->last_search_string);
245     mcview_nroff_seq_free (&view->search_nroff_seq);
246 }
247 
248 /* --------------------------------------------------------------------------------------------- */
249 
250 mc_search_cbret_t
mcview_search_cmd_callback(const void * user_data,gsize char_offset,int * current_char)251 mcview_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
252 {
253     WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
254 
255     /*    view_read_continue (view, &view->search_onechar_info); *//* AB:FIXME */
256     if (!view->mode_flags.nroff)
257     {
258         mcview_get_byte (view, char_offset, current_char);
259         return MC_SEARCH_CB_OK;
260     }
261 
262     if (view->search_numNeedSkipChar != 0)
263     {
264         view->search_numNeedSkipChar--;
265         return MC_SEARCH_CB_SKIP;
266     }
267 
268     if (search_cb_char_curr_index == -1
269         || search_cb_char_curr_index >= view->search_nroff_seq->char_length)
270     {
271         if (search_cb_char_curr_index != -1)
272             mcview_nroff_seq_next (view->search_nroff_seq);
273 
274         search_cb_char_curr_index = 0;
275         if (view->search_nroff_seq->char_length > 1)
276             g_unichar_to_utf8 (view->search_nroff_seq->current_char, search_cb_char_buffer);
277         else
278             search_cb_char_buffer[0] = (char) view->search_nroff_seq->current_char;
279 
280         if (view->search_nroff_seq->type != NROFF_TYPE_NONE)
281         {
282             switch (view->search_nroff_seq->type)
283             {
284             case NROFF_TYPE_BOLD:
285                 view->search_numNeedSkipChar = 1 + view->search_nroff_seq->char_length; /* real char length and 0x8 */
286                 break;
287             case NROFF_TYPE_UNDERLINE:
288                 view->search_numNeedSkipChar = 2;       /* underline symbol and ox8 */
289                 break;
290             default:
291                 break;
292             }
293         }
294         return MC_SEARCH_CB_INVALID;
295     }
296 
297     *current_char = search_cb_char_buffer[search_cb_char_curr_index];
298     search_cb_char_curr_index++;
299 
300     return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
301 }
302 
303 /* --------------------------------------------------------------------------------------------- */
304 
305 mc_search_cbret_t
mcview_search_update_cmd_callback(const void * user_data,gsize char_offset)306 mcview_search_update_cmd_callback (const void *user_data, gsize char_offset)
307 {
308     status_msg_t *sm = STATUS_MSG (user_data);
309     mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
310     WView *view = vsm->view;
311     gboolean do_update = FALSE;
312     mc_search_cbret_t result = MC_SEARCH_CB_OK;
313 
314     vsm->offset = (off_t) char_offset;
315 
316     if (mcview_search_options.backwards)
317     {
318         if (vsm->offset <= view->update_activate)
319         {
320             view->update_activate -= view->update_steps;
321 
322             do_update = TRUE;
323         }
324     }
325     else
326     {
327         if (vsm->offset >= view->update_activate)
328         {
329             view->update_activate += view->update_steps;
330 
331             do_update = TRUE;
332         }
333     }
334 
335     if (do_update && sm->update (sm) == B_CANCEL)
336         result = MC_SEARCH_CB_ABORT;
337 
338     /* may be in future return from this callback will change current position in searching block. */
339 
340     return result;
341 }
342 
343 /* --------------------------------------------------------------------------------------------- */
344 
345 void
mcview_do_search(WView * view,off_t want_search_start)346 mcview_do_search (WView * view, off_t want_search_start)
347 {
348     mcview_search_status_msg_t vsm;
349 
350     off_t search_start = 0;
351     off_t orig_search_start = view->search_start;
352     gboolean found = FALSE;
353 
354     size_t match_len;
355 
356     view->search_start = want_search_start;
357     /* for avoid infinite search loop we need to increase or decrease start offset of search */
358 
359     if (view->search_start != 0)
360     {
361         if (!view->mode_flags.nroff)
362             search_start = view->search_start + (mcview_search_options.backwards ? -2 : 0);
363         else
364         {
365             if (mcview_search_options.backwards)
366             {
367                 mcview_nroff_t *nroff;
368 
369                 nroff = mcview_nroff_seq_new_num (view, view->search_start);
370                 if (mcview_nroff_seq_prev (nroff) != -1)
371                     search_start =
372                         -(mcview__get_nroff_real_len (view, nroff->index - 1, 2) +
373                           nroff->char_length + 1);
374                 else
375                     search_start = -2;
376 
377                 mcview_nroff_seq_free (&nroff);
378             }
379             else
380             {
381                 search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
382             }
383             search_start += view->search_start;
384         }
385     }
386 
387     if (mcview_search_options.backwards && search_start < 0)
388         search_start = 0;
389 
390     /* Compute the percent steps */
391     mcview_search_update_steps (view);
392 
393     view->update_activate = search_start;
394 
395     vsm.first = TRUE;
396     vsm.view = view;
397     vsm.offset = search_start;
398 
399     status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
400                      mcview_search_status_update_cb, NULL);
401 
402     do
403     {
404         off_t growbufsize;
405 
406         if (view->growbuf_in_use)
407             growbufsize = mcview_growbuf_filesize (view);
408         else
409             growbufsize = view->search->original_len;
410 
411         if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
412         {
413             mcview_search_show_result (view, match_len);
414             found = TRUE;
415             break;
416         }
417 
418         /* Search error is here.
419          * MC_SEARCH_E_NOTFOUND: continue search
420          * others: stop
421          */
422         if (view->search->error != MC_SEARCH_E_NOTFOUND)
423             break;
424 
425         search_start = growbufsize - view->search->original_len;
426     }
427     while (search_start > 0 && mcview_may_still_grow (view));
428 
429     /* After mcview_may_still_grow (view) == FALSE we have remained last chunk. Search there. */
430     if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
431         && !mcview_search_options.backwards
432         && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
433     {
434         mcview_search_show_result (view, match_len);
435         found = TRUE;
436     }
437 
438     status_msg_deinit (STATUS_MSG (&vsm));
439 
440     if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
441         && !mcview_search_options.backwards)
442     {
443         view->search_start = orig_search_start;
444         mcview_update (view);
445 
446         if (query_dialog
447             (_("Search done"), _("Continue from beginning?"), D_NORMAL, 2, _("&Yes"),
448              _("&No")) != 0)
449             found = TRUE;
450         else
451         {
452             /* continue search from beginning */
453             view->update_activate = 0;
454 
455             vsm.first = TRUE;
456             vsm.view = view;
457             vsm.offset = 0;
458 
459             status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
460                              mcview_search_status_update_cb, NULL);
461 
462             /* search from file begin up to initial search start position */
463             if (mcview_find (&vsm, 0, orig_search_start, &match_len))
464             {
465                 mcview_search_show_result (view, match_len);
466                 found = TRUE;
467             }
468 
469             status_msg_deinit (STATUS_MSG (&vsm));
470         }
471     }
472 
473     if (!found)
474     {
475         view->search_start = orig_search_start;
476         mcview_update (view);
477 
478         if (view->search->error == MC_SEARCH_E_NOTFOUND)
479             query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
480         else if (view->search->error_str != NULL)
481             query_dialog (_("Search"), view->search->error_str, D_NORMAL, 1, _("&Dismiss"));
482     }
483     view->dirty++;
484 }
485 
486 /* --------------------------------------------------------------------------------------------- */
487