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