1 /* Searching in the HTML document */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
5 #endif
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include <ctype.h> /* tolower(), isprint() */
12 #include <sys/types.h> /* FreeBSD needs this before regex.h */
13 #ifdef HAVE_REGEX_H
14 #include <regex.h>
15 #endif
16 #include <stdlib.h>
17 #include <string.h>
18
19 #include "elinks.h"
20
21 #include "bfu/dialog.h"
22 #include "config/kbdbind.h"
23 #include "document/document.h"
24 #include "document/view.h"
25 #include "intl/gettext/libintl.h"
26 #include "main/event.h"
27 #include "main/module.h"
28 #include "session/session.h"
29 #include "terminal/screen.h"
30 #include "terminal/terminal.h"
31 #include "util/color.h"
32 #include "util/error.h"
33 #include "util/memory.h"
34 #include "util/string.h"
35 #include "viewer/action.h"
36 #include "viewer/text/draw.h"
37 #include "viewer/text/link.h"
38 #include "viewer/text/search.h"
39 #include "viewer/text/view.h"
40 #include "viewer/text/vs.h"
41
42
43 #define SEARCH_HISTORY_FILENAME "searchhist"
44
45 static INIT_INPUT_HISTORY(search_history);
46
47
48 static inline void
add_srch_chr(struct document * document,unsigned char c,int x,int y,int nn)49 add_srch_chr(struct document *document, unsigned char c, int x, int y, int nn)
50 {
51 assert(document);
52 if_assert_failed return;
53
54 if (c == ' ' && !document->nsearch) return;
55
56 if (document->search) {
57 int n = document->nsearch;
58
59 if (c == ' ' && document->search[n - 1].c == ' ')
60 return;
61
62 document->search[n].c = c;
63 document->search[n].x = x;
64 document->search[n].y = y;
65 document->search[n].n = nn;
66 }
67
68 document->nsearch++;
69 }
70
71 static void
sort_srch(struct document * document)72 sort_srch(struct document *document)
73 {
74 int i;
75 int *min, *max;
76
77 assert(document);
78 if_assert_failed return;
79
80 document->slines1 = mem_calloc(document->height, sizeof(*document->slines1));
81 if (!document->slines1) return;
82
83 document->slines2 = mem_calloc(document->height, sizeof(*document->slines2));
84 if (!document->slines2) {
85 mem_free(document->slines1);
86 return;
87 }
88
89 min = mem_calloc(document->height, sizeof(*min));
90 if (!min) {
91 mem_free(document->slines1);
92 mem_free(document->slines2);
93 return;
94 }
95
96 max = mem_calloc(document->height, sizeof(*max));
97 if (!max) {
98 mem_free(document->slines1);
99 mem_free(document->slines2);
100 mem_free(min);
101 return;
102 }
103
104 for (i = 0; i < document->height; i++) {
105 min[i] = INT_MAX;
106 max[i] = 0;
107 }
108
109 for (i = 0; i < document->nsearch; i++) {
110 struct search *s = &document->search[i];
111 int sxn = s->x + s->n;
112
113 if (s->x < min[s->y]) {
114 min[s->y] = s->x;
115 document->slines1[s->y] = s;
116 }
117 if (sxn > max[s->y]) {
118 max[s->y] = sxn;
119 document->slines2[s->y] = s;
120 }
121 }
122
123 mem_free(min);
124 mem_free(max);
125 }
126
127 static int
get_srch(struct document * document)128 get_srch(struct document *document)
129 {
130 struct node *node;
131
132 assert(document && document->nsearch == 0);
133
134 if_assert_failed return 0;
135
136 foreachback (node, document->nodes) {
137 int x, y;
138 int height = int_min(node->box.y + node->box.height, document->height);
139
140 for (y = node->box.y; y < height; y++) {
141 int width = int_min(node->box.x + node->box.width,
142 document->data[y].length);
143
144 for (x = node->box.x;
145 x < width && document->data[y].chars[x].data <= ' ';
146 x++);
147
148 for (; x < width; x++) {
149 unsigned char c = document->data[y].chars[x].data;
150 int count = 0;
151 int xx;
152
153 if (document->data[y].chars[x].attr & SCREEN_ATTR_UNSEARCHABLE)
154 continue;
155
156 if (c > ' ') {
157 add_srch_chr(document, c, x, y, 1);
158 continue;
159 }
160
161 for (xx = x + 1; xx < width; xx++) {
162 if (document->data[y].chars[xx].data < ' ')
163 continue;
164 count = xx - x;
165 break;
166 }
167
168 add_srch_chr(document, ' ', x, y, count);
169 x = xx - 1;
170 }
171
172 add_srch_chr(document, ' ', x, y, 0);
173 }
174 }
175
176 return document->nsearch;
177 }
178
179 static void
get_search_data(struct document * document)180 get_search_data(struct document *document)
181 {
182 int n;
183
184 assert(document);
185 if_assert_failed return;
186
187 if (document->search) return;
188
189 n = get_srch(document);
190 if (!n) return;
191
192 document->nsearch = 0;
193
194 document->search = mem_alloc(n * sizeof(*document->search));
195 if (!document->search) return;
196
197 get_srch(document);
198 while (document->nsearch
199 && document->search[--document->nsearch].c == ' ');
200 sort_srch(document);
201 }
202
203 /* Assign s1 and s2 the first search node and the last search node needed to
204 * form the region starting at line y and ending at the greater of y + height
205 * and the end of the document, with allowance at the start to allow for
206 * multi-line matches that would otherwise be partially outside of the region.
207 *
208 * Returns -1 on assertion failure, 1 if s1 and s2 are not found,
209 * and 0 if they are found. */
210 static int
get_range(struct document * document,int y,int height,int l,struct search ** s1,struct search ** s2)211 get_range(struct document *document, int y, int height, int l,
212 struct search **s1, struct search **s2)
213 {
214 int i;
215
216 assert(document && s1 && s2);
217 if_assert_failed return -1;
218
219 *s1 = *s2 = NULL;
220 int_lower_bound(&y, 0);
221
222 /* Starting with line y, find the search node referencing the earliest
223 * point in the document text and the node referencing the last point,
224 * respectively s1 and s2.
225 */
226 for (i = y; i < y + height && i < document->height; i++) {
227 if (document->slines1[i] && (!*s1 || document->slines1[i] < *s1))
228 *s1 = document->slines1[i];
229 if (document->slines2[i] && (!*s2 || document->slines2[i] > *s2))
230 *s2 = document->slines2[i];
231 }
232 if (!*s1 || !*s2) return 1;
233
234 /* Skip back by l to facilitate multi-line matches where the match
235 * begins before the start of the search region but is still partly
236 * within. */
237 *s1 -= l;
238
239 if (*s1 < document->search)
240 *s1 = document->search;
241 if (*s2 > document->search + document->nsearch - l + 1)
242 *s2 = document->search + document->nsearch - l + 1;
243 if (*s1 > *s2)
244 *s1 = *s2 = NULL;
245 if (!*s1 || !*s2)
246 return 1;
247
248 return 0;
249 }
250
251 #ifdef HAVE_REGEX_H
252 /* Returns a string |doc| that is a copy of the text in the search nodes
253 * from |s1| to |s1 + doclen - 1| with the space at the end of each line
254 * converted to a new-line character (LF). */
255 static unsigned char *
get_search_region_from_search_nodes(struct search * s1,struct search * s2,int pattern_len,int * doclen)256 get_search_region_from_search_nodes(struct search *s1, struct search *s2,
257 int pattern_len, int *doclen)
258 {
259 unsigned char *doc;
260 int i;
261
262 *doclen = s2 - s1 + pattern_len;
263 if (!*doclen) return NULL;
264
265 doc = mem_alloc(*doclen + 1);
266 if (!doc) {
267 *doclen = -1;
268 return NULL;
269 }
270
271 for (i = 0; i < *doclen; i++) {
272 if (i > 0 && s1[i - 1].c == ' ' && s1[i - 1].y != s1[i].y) {
273 doc[i - 1] = '\n';
274 }
275 doc[i] = s1[i].c;
276 }
277
278 doc[*doclen] = 0;
279
280 return doc;
281 }
282
283 struct regex_match_context {
284 struct search *s1;
285 struct search *s2;
286 int textlen;
287 int y1;
288 int y2;
289 int found;
290 unsigned char *pattern;
291 };
292
293 static int
init_regex(regex_t * regex,unsigned char * pattern)294 init_regex(regex_t *regex, unsigned char *pattern)
295 {
296 int regex_flags = REG_NEWLINE;
297 int reg_err;
298
299 if (get_opt_int("document.browse.search.regex") == 2)
300 regex_flags |= REG_EXTENDED;
301
302 if (!get_opt_bool("document.browse.search.case"))
303 regex_flags |= REG_ICASE;
304
305 reg_err = regcomp(regex, pattern, regex_flags);
306 if (reg_err) {
307 regfree(regex);
308 return 0;
309 }
310
311 return 1;
312 }
313
314 static void
search_for_pattern(struct regex_match_context * common_ctx,void * data,void (* match)(struct regex_match_context *,void *))315 search_for_pattern(struct regex_match_context *common_ctx, void *data,
316 void (*match)(struct regex_match_context *, void *))
317 {
318 unsigned char *doc;
319 unsigned char *doctmp;
320 int doclen;
321 int regexec_flags = 0;
322 regex_t regex;
323 regmatch_t regmatch;
324 int pos = 0;
325 struct search *search_start = common_ctx->s1;
326 unsigned char save_c;
327
328 /* TODO: show error message */
329 /* XXX: This will probably require that reg_err be passed thru
330 * common_ctx to the caller. */
331 if (!init_regex(®ex, common_ctx->pattern)) {
332 #if 0
333 /* Where and how should we display the error dialog ? */
334 unsigned char regerror_string[MAX_STR_LEN];
335
336 regerror(reg_err, ®ex, regerror_string, sizeof(regerror_string));
337 #endif
338 common_ctx->found = -2;
339 return;
340 }
341
342 doc = get_search_region_from_search_nodes(common_ctx->s1, common_ctx->s2, common_ctx->textlen, &doclen);
343 if (!doc) {
344 regfree(®ex);
345 common_ctx->found = doclen;
346 return;
347 }
348
349 doctmp = doc;
350
351 find_next:
352 while (pos < doclen) {
353 int y = search_start[pos].y;
354
355 if (y >= common_ctx->y1 && y <= common_ctx->y2) break;
356 pos++;
357 }
358 doctmp = &doc[pos];
359 common_ctx->s1 = &search_start[pos];
360
361 while (pos < doclen) {
362 int y = search_start[pos].y;
363
364 if (y < common_ctx->y1 || y > common_ctx->y2) break;
365 pos++;
366 }
367 save_c = doc[pos];
368 doc[pos] = 0;
369
370 while (*doctmp && !regexec(®ex, doctmp, 1, ®match, regexec_flags)) {
371 regexec_flags = REG_NOTBOL;
372 common_ctx->textlen = regmatch.rm_eo - regmatch.rm_so;
373 if (!common_ctx->textlen) { doc[pos] = save_c; common_ctx->found = 1; goto free_stuff; }
374 common_ctx->s1 += regmatch.rm_so;
375 doctmp += regmatch.rm_so;
376
377 match(common_ctx, data);
378
379 doctmp += int_max(common_ctx->textlen, 1);
380 common_ctx->s1 += int_max(common_ctx->textlen, 1);
381 }
382
383 doc[pos] = save_c;
384 if (pos < doclen)
385 goto find_next;
386
387 free_stuff:
388 regfree(®ex);
389 mem_free(doc);
390 }
391
392 struct is_in_range_regex_context {
393 int y;
394 int *min;
395 int *max;
396 };
397
398 static void
is_in_range_regex_match(struct regex_match_context * common_ctx,void * data)399 is_in_range_regex_match(struct regex_match_context *common_ctx, void *data)
400 {
401 struct is_in_range_regex_context *ctx = data;
402 int i;
403
404 if (common_ctx->s1[common_ctx->textlen].y < ctx->y || common_ctx->s1[common_ctx->textlen].y >= common_ctx->y2)
405 return;
406
407 common_ctx->found = 1;
408
409 for (i = 0; i < common_ctx->textlen; i++) {
410 if (!common_ctx->s1[i].n) continue;
411
412 int_upper_bound(ctx->min, common_ctx->s1[i].x);
413 int_lower_bound(ctx->max, common_ctx->s1[i].x + common_ctx->s1[i].n);
414 }
415 }
416
417 static int
is_in_range_regex(struct document * document,int y,int height,unsigned char * text,int textlen,int * min,int * max,struct search * s1,struct search * s2)418 is_in_range_regex(struct document *document, int y, int height,
419 unsigned char *text, int textlen,
420 int *min, int *max,
421 struct search *s1, struct search *s2)
422 {
423 struct regex_match_context common_ctx;
424 struct is_in_range_regex_context ctx;
425
426 ctx.y = y;
427 ctx.min = min;
428 ctx.max = max;
429
430 common_ctx.found = 0;
431 common_ctx.textlen = textlen;
432 common_ctx.y1 = y - 1;
433 common_ctx.y2 = y + height;
434 common_ctx.pattern = text;
435 common_ctx.s1 = s1;
436 common_ctx.s2 = s2;
437
438 search_for_pattern(&common_ctx, &ctx, is_in_range_regex_match);
439
440 return common_ctx.found;
441 }
442 #endif /* HAVE_REGEX_H */
443
444 /* Returns an allocated string which is a lowered copy of passed one. */
445 static unsigned char *
lowered_string(unsigned char * text,int textlen)446 lowered_string(unsigned char *text, int textlen)
447 {
448 unsigned char *ret;
449
450 if (textlen < 0) textlen = strlen(text);
451
452 ret = mem_calloc(1, textlen + 1);
453 if (ret && textlen) {
454 do {
455 ret[textlen] = tolower(text[textlen]);
456 } while (textlen--);
457 }
458
459 return ret;
460 }
461
462 static int
is_in_range_plain(struct document * document,int y,int height,unsigned char * text,int textlen,int * min,int * max,struct search * s1,struct search * s2)463 is_in_range_plain(struct document *document, int y, int height,
464 unsigned char *text, int textlen,
465 int *min, int *max,
466 struct search *s1, struct search *s2)
467 {
468 int yy = y + height;
469 unsigned char *txt;
470 int found = 0;
471 int case_sensitive = get_opt_bool("document.browse.search.case");
472
473 txt = case_sensitive ? stracpy(text) : lowered_string(text, textlen);
474 if (!txt) return -1;
475
476 /* TODO: This is a great candidate for nice optimizations. Fresh CS
477 * graduates can use their knowledge of ie. KMP (should be quite
478 * trivial, probably a starter; very fast as well) or Turbo-BM (or
479 * maybe some other Boyer-Moore variant, I don't feel that strong in
480 * this area), hmm? >:) --pasky */
481
482 #define maybe_tolower(c) (case_sensitive ? (c) : tolower(c))
483
484 for (; s1 <= s2; s1++) {
485 int i;
486
487 if (maybe_tolower(s1->c) != txt[0]) {
488 srch_failed:
489 continue;
490 }
491
492 for (i = 1; i < textlen; i++)
493 if (maybe_tolower(s1[i].c) != txt[i])
494 goto srch_failed;
495
496 if (s1[i].y < y || s1[i].y >= yy)
497 continue;
498
499 found = 1;
500
501 for (i = 0; i < textlen; i++) {
502 if (!s1[i].n) continue;
503
504 int_upper_bound(min, s1[i].x);
505 int_lower_bound(max, s1[i].x + s1[i].n);
506 }
507 }
508
509 #undef maybe_tolower
510
511 mem_free(txt);
512
513 return found;
514 }
515
516 static int
is_in_range(struct document * document,int y,int height,unsigned char * text,int * min,int * max)517 is_in_range(struct document *document, int y, int height,
518 unsigned char *text, int *min, int *max)
519 {
520 struct search *s1, *s2;
521 int textlen;
522
523 assert(document && text && min && max);
524 if_assert_failed return -1;
525
526 *min = INT_MAX, *max = 0;
527 textlen = strlen(text);
528
529 if (get_range(document, y, height, textlen, &s1, &s2))
530 return 0;
531
532 #ifdef HAVE_REGEX_H
533 if (get_opt_int("document.browse.search.regex"))
534 return is_in_range_regex(document, y, height, text, textlen,
535 min, max, s1, s2);
536 #endif
537 return is_in_range_plain(document, y, height, text, textlen,
538 min, max, s1, s2);
539 }
540
541 #define realloc_points(pts, size) \
542 mem_align_alloc(pts, size, (size) + 1, struct point, 0xFF)
543
544 static void
get_searched_plain(struct document_view * doc_view,struct point ** pt,int * pl,int l,struct search * s1,struct search * s2)545 get_searched_plain(struct document_view *doc_view, struct point **pt, int *pl,
546 int l, struct search *s1, struct search *s2)
547 {
548 unsigned char *txt;
549 struct point *points = NULL;
550 struct box *box;
551 int xoffset, yoffset;
552 int len = 0;
553 int case_sensitive = get_opt_bool("document.browse.search.case");
554
555 txt = case_sensitive ? stracpy(*doc_view->search_word)
556 : lowered_string(*doc_view->search_word, l);
557 if (!txt) return;
558
559 box = &doc_view->box;
560 xoffset = box->x - doc_view->vs->x;
561 yoffset = box->y - doc_view->vs->y;
562
563 #define maybe_tolower(c) (case_sensitive ? (c) : tolower(c))
564
565 for (; s1 <= s2; s1++) {
566 int i;
567
568 if (maybe_tolower(s1[0].c) != txt[0]) {
569 srch_failed:
570 continue;
571 }
572
573 for (i = 1; i < l; i++)
574 if (maybe_tolower(s1[i].c) != txt[i])
575 goto srch_failed;
576
577 for (i = 0; i < l; i++) {
578 int j;
579 int y = s1[i].y + yoffset;
580
581 if (!row_is_in_box(box, y))
582 continue;
583
584 for (j = 0; j < s1[i].n; j++) {
585 int sx = s1[i].x + j;
586 int x = sx + xoffset;
587
588 if (!col_is_in_box(box, x))
589 continue;
590
591 if (!realloc_points(&points, len))
592 continue;
593
594 points[len].x = sx;
595 points[len++].y = s1[i].y;
596 }
597 }
598 }
599
600 #undef maybe_tolower
601
602 mem_free(txt);
603 *pt = points;
604 *pl = len;
605 }
606
607 #ifdef HAVE_REGEX_H
608 struct get_searched_regex_context {
609 int xoffset;
610 int yoffset;
611 struct box *box;
612 struct point *points;
613 int len;
614 };
615
616 static void
get_searched_regex_match(struct regex_match_context * common_ctx,void * data)617 get_searched_regex_match(struct regex_match_context *common_ctx, void *data)
618 {
619 struct get_searched_regex_context *ctx = data;
620 int i;
621
622 for (i = 0; i < common_ctx->textlen; i++) {
623 int j;
624 int y = common_ctx->s1[i].y + ctx->yoffset;
625
626 if (!row_is_in_box(ctx->box, y))
627 continue;
628
629 for (j = 0; j < common_ctx->s1[i].n; j++) {
630 int sx = common_ctx->s1[i].x + j;
631 int x = sx + ctx->xoffset;
632
633 if (!col_is_in_box(ctx->box, x))
634 continue;
635
636 if (!realloc_points(&ctx->points, ctx->len))
637 continue;
638
639 ctx->points[ctx->len].x = sx;
640 ctx->points[ctx->len++].y = common_ctx->s1[i].y;
641 }
642 }
643 }
644
645 static void
get_searched_regex(struct document_view * doc_view,struct point ** pt,int * pl,int textlen,struct search * s1,struct search * s2)646 get_searched_regex(struct document_view *doc_view, struct point **pt, int *pl,
647 int textlen, struct search *s1, struct search *s2)
648 {
649 struct regex_match_context common_ctx;
650 struct get_searched_regex_context ctx;
651
652 ctx.points = NULL;
653 ctx.len = 0;
654 ctx.box = &doc_view->box;
655 ctx.xoffset = ctx.box->x - doc_view->vs->x;
656 ctx.yoffset = ctx.box->y - doc_view->vs->y;
657
658 common_ctx.found = 0;
659 common_ctx.textlen = textlen;
660 common_ctx.y1 = doc_view->vs->y - 1;
661 common_ctx.y2 = doc_view->vs->y + ctx.box->height;
662 common_ctx.pattern = *doc_view->search_word;
663 common_ctx.s1 = s1;
664 common_ctx.s2 = s2;
665
666 search_for_pattern(&common_ctx, &ctx, get_searched_regex_match);
667
668 *pt = ctx.points;
669 *pl = ctx.len;
670 }
671 #endif /* HAVE_REGEX_H */
672
673 static void
get_searched(struct document_view * doc_view,struct point ** pt,int * pl)674 get_searched(struct document_view *doc_view, struct point **pt, int *pl)
675 {
676 struct search *s1, *s2;
677 int l;
678
679 assert(doc_view && doc_view->vs && pt && pl);
680 if_assert_failed return;
681
682 if (!has_search_word(doc_view))
683 return;
684
685 get_search_data(doc_view->document);
686 l = strlen(*doc_view->search_word);
687 if (get_range(doc_view->document, doc_view->vs->y,
688 doc_view->box.height, l, &s1, &s2)) {
689 *pt = NULL;
690 *pl = 0;
691
692 return;
693 }
694
695 #ifdef HAVE_REGEX_H
696 if (get_opt_int("document.browse.search.regex"))
697 get_searched_regex(doc_view, pt, pl, l, s1, s2);
698 else
699 #endif
700 get_searched_plain(doc_view, pt, pl, l, s1, s2);
701 }
702
703 /* Highlighting of searched strings. */
704 void
draw_searched(struct terminal * term,struct document_view * doc_view)705 draw_searched(struct terminal *term, struct document_view *doc_view)
706 {
707 struct point *pt = NULL;
708 int len = 0;
709
710 assert(term && doc_view);
711 if_assert_failed return;
712
713 if (!has_search_word(doc_view))
714 return;
715
716 get_searched(doc_view, &pt, &len);
717 if (len) {
718 int i;
719 struct color_pair *color = get_bfu_color(term, "searched");
720 int xoffset = doc_view->box.x - doc_view->vs->x;
721 int yoffset = doc_view->box.y - doc_view->vs->y;
722
723 for (i = 0; i < len; i++) {
724 int x = pt[i].x + xoffset;
725 int y = pt[i].y + yoffset;
726
727 /* TODO: We should take in account original colors and
728 * combine them with defined color. */
729 #if 0
730 /* This piece of code shows the old way of handling
731 * colors and screen char attributes. */
732 unsigned co = get_char(term, x, y);
733 co = ((co >> 3) & 0x0700) | ((co << 3) & 0x3800);
734 #endif
735
736 draw_char_color(term, x, y, color);
737 }
738 }
739
740 mem_free_if(pt);
741 }
742
743
744 enum find_error {
745 FIND_ERROR_NONE,
746 FIND_ERROR_NO_PREVIOUS_SEARCH,
747 FIND_ERROR_HIT_TOP,
748 FIND_ERROR_HIT_BOTTOM,
749 FIND_ERROR_NOT_FOUND,
750 FIND_ERROR_MEMORY,
751 FIND_ERROR_REGEX,
752 };
753
754 static enum find_error find_next_do(struct session *ses,
755 struct document_view *doc_view,
756 int direction);
757
758 static void print_find_error(struct session *ses, enum find_error find_error);
759
760 static enum find_error
search_for_do(struct session * ses,unsigned char * str,int direction,int report_errors)761 search_for_do(struct session *ses, unsigned char *str, int direction,
762 int report_errors)
763 {
764 struct document_view *doc_view;
765 enum find_error error;
766
767 assert(ses && str);
768 if_assert_failed return FIND_ERROR_NOT_FOUND;
769
770 doc_view = current_frame(ses);
771
772 assert(doc_view);
773 if_assert_failed return FIND_ERROR_NOT_FOUND;
774
775 mem_free_set(&ses->search_word, NULL);
776 mem_free_set(&ses->last_search_word, NULL);
777
778 if (!*str) return FIND_ERROR_NOT_FOUND;
779
780 /* We only set the last search word because we don.t want find_next()
781 * to try to find next link in search before the search data has been
782 * initialized. find_next() will set ses->search_word for us. */
783 ses->last_search_word = stracpy(str);
784 if (!ses->last_search_word) return FIND_ERROR_NOT_FOUND;
785
786 ses->search_direction = direction;
787
788 error = find_next_do(ses, doc_view, 1);
789
790 if (report_errors)
791 print_find_error(ses, error);
792
793 return error;
794 }
795
796 static void
search_for_back(struct session * ses,unsigned char * str)797 search_for_back(struct session *ses, unsigned char *str)
798 {
799 assert(ses && str);
800 if_assert_failed return;
801
802 search_for_do(ses, str, -1, 1);
803 }
804
805 static void
search_for(struct session * ses,unsigned char * str)806 search_for(struct session *ses, unsigned char *str)
807 {
808 assert(ses && str);
809 if_assert_failed return;
810
811 search_for_do(ses, str, 1, 1);
812 }
813
814
815 static inline int
point_intersect(struct point * p1,int l1,struct point * p2,int l2)816 point_intersect(struct point *p1, int l1, struct point *p2, int l2)
817 {
818 #define HASH_SIZE 4096
819 #define HASH(p) ((((p).y << 6) + (p).x) & (HASH_SIZE - 1))
820
821 int i;
822 static char hash[HASH_SIZE];
823 static int first_time = 1;
824
825 assert(p2);
826 if_assert_failed return 0;
827
828 if (first_time) memset(hash, 0, HASH_SIZE), first_time = 0;
829
830 for (i = 0; i < l1; i++) hash[HASH(p1[i])] = 1;
831
832 for (i = 0; i < l2; i++) {
833 int j;
834
835 if (!hash[HASH(p2[i])]) continue;
836
837 for (j = 0; j < l1; j++) {
838 if (p1[j].x != p2[i].x) continue;
839 if (p1[j].y != p2[i].y) continue;
840
841 for (i = 0; i < l1; i++)
842 hash[HASH(p1[i])] = 0;
843
844 return 1;
845 }
846 }
847
848 for (i = 0; i < l1; i++) hash[HASH(p1[i])] = 0;
849
850 return 0;
851
852 #undef HASH
853 #undef HASH_SIZE
854 }
855
856 static int
find_next_link_in_search(struct document_view * doc_view,int direction)857 find_next_link_in_search(struct document_view *doc_view, int direction)
858 {
859 assert(doc_view && doc_view->vs);
860 if_assert_failed return 0;
861
862 if (direction == -2 || direction == 2) {
863 direction /= 2;
864 if (direction < 0)
865 find_link_page_up(doc_view);
866 else
867 find_link_page_down(doc_view);
868
869 if (doc_view->vs->current_link == -1) return 1;
870 goto nt;
871 }
872
873 while (doc_view->vs->current_link != -1
874 && next_link_in_view(doc_view, doc_view->vs->current_link + direction,
875 direction)) {
876 struct point *pt = NULL;
877 struct link *link;
878 int len;
879
880 nt:
881 link = &doc_view->document->links[doc_view->vs->current_link];
882 get_searched(doc_view, &pt, &len);
883 if (point_intersect(pt, len, link->points, link->npoints)) {
884 mem_free(pt);
885 return 0;
886 }
887 mem_free_if(pt);
888 }
889
890 if (direction < 0)
891 find_link_page_up(doc_view);
892 else
893 find_link_page_down(doc_view);
894
895 return 1;
896 }
897
898 static enum find_error
find_next_do(struct session * ses,struct document_view * doc_view,int direction)899 find_next_do(struct session *ses, struct document_view *doc_view, int direction)
900 {
901 int p, min, max, c = 0;
902 int step, hit_bottom = 0, hit_top = 0;
903 int height;
904
905 assert(ses && ses->tab && ses->tab->term && doc_view && doc_view->vs
906 && direction);
907 if_assert_failed return FIND_ERROR_NONE;
908
909 direction *= ses->search_direction;
910 p = doc_view->vs->y;
911 height = doc_view->box.height;
912 step = direction * height;
913
914 if (ses->search_word) {
915 if (!find_next_link_in_search(doc_view, direction))
916 return FIND_ERROR_NONE;
917 p += step;
918 }
919
920 if (!ses->search_word) {
921 if (!ses->last_search_word) {
922 return FIND_ERROR_NO_PREVIOUS_SEARCH;
923 }
924 ses->search_word = stracpy(ses->last_search_word);
925 if (!ses->search_word) return FIND_ERROR_NONE;
926 }
927
928 get_search_data(doc_view->document);
929
930 do {
931 int in_range = is_in_range(doc_view->document, p, height,
932 ses->search_word, &min, &max);
933
934 if (in_range == -1) return FIND_ERROR_MEMORY;
935 if (in_range == -2) return FIND_ERROR_REGEX;
936 if (in_range) {
937 doc_view->vs->y = p;
938 if (max >= min)
939 doc_view->vs->x = int_min(int_max(doc_view->vs->x,
940 max - doc_view->box.width),
941 min);
942
943 set_link(doc_view);
944 find_next_link_in_search(doc_view, direction * 2);
945
946 if (hit_top)
947 return FIND_ERROR_HIT_TOP;
948
949 if (hit_bottom)
950 return FIND_ERROR_HIT_BOTTOM;
951
952 return FIND_ERROR_NONE;
953 }
954 p += step;
955 if (p > doc_view->document->height) {
956 hit_bottom = 1;
957 p = 0;
958 }
959 if (p < 0) {
960 hit_top = 1;
961 p = 0;
962 while (p < doc_view->document->height) p += height;
963 p -= height;
964 }
965 c += height;
966 } while (c < doc_view->document->height + height);
967
968 return FIND_ERROR_NOT_FOUND;
969 }
970
971 static void
print_find_error_not_found(struct session * ses,unsigned char * title,unsigned char * message,unsigned char * search_string)972 print_find_error_not_found(struct session *ses, unsigned char *title,
973 unsigned char *message, unsigned char *search_string)
974 {
975 switch (get_opt_int("document.browse.search.show_not_found")) {
976 case 2:
977 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
978 title, ALIGN_CENTER,
979 msg_text(ses->tab->term, message,
980 search_string));
981 break;
982
983 case 1:
984 beep_terminal(ses->tab->term);
985
986 default:
987 break;
988 }
989 }
990
991 static void
print_find_error(struct session * ses,enum find_error find_error)992 print_find_error(struct session *ses, enum find_error find_error)
993 {
994 int hit_top = 0;
995 unsigned char *message = NULL;
996
997 switch (find_error) {
998 case FIND_ERROR_HIT_TOP:
999 hit_top = 1;
1000 case FIND_ERROR_HIT_BOTTOM:
1001 if (!get_opt_bool("document.browse.search"
1002 ".show_hit_top_bottom"))
1003 break;
1004
1005 message = hit_top
1006 ? N_("Search hit top, continuing at bottom.")
1007 : N_("Search hit bottom, continuing at top.");
1008 break;
1009 case FIND_ERROR_NO_PREVIOUS_SEARCH:
1010 message = N_("No previous search");
1011 break;
1012 case FIND_ERROR_NOT_FOUND:
1013 print_find_error_not_found(ses, N_("Search"),
1014 N_("Search string"
1015 " '%s' not found"),
1016 ses->search_word);
1017
1018 break;
1019
1020 case FIND_ERROR_REGEX:
1021 print_find_error_not_found(ses, N_("Search"),
1022 N_("Could not compile"
1023 " regular expression"
1024 " '%s'"),
1025 ses->search_word);
1026
1027 break;
1028
1029 case FIND_ERROR_MEMORY:
1030 /* Why bother trying to create a msg_box?
1031 * We probably don't have the memory... */
1032 case FIND_ERROR_NONE:
1033 break;
1034 }
1035
1036 if (!message) return;
1037 info_box(ses->tab->term, 0, N_("Search"), ALIGN_CENTER, message);
1038 }
1039
1040 enum frame_event_status
find_next(struct session * ses,struct document_view * doc_view,int direction)1041 find_next(struct session *ses, struct document_view *doc_view, int direction)
1042 {
1043 print_find_error(ses, find_next_do(ses, doc_view, direction));
1044
1045 /* FIXME: Make this more fine-grained */
1046 return FRAME_EVENT_REFRESH;
1047 }
1048
1049
1050 /* Link typeahead */
1051
1052 enum typeahead_code {
1053 TYPEAHEAD_MATCHED,
1054 TYPEAHEAD_ERROR,
1055 TYPEAHEAD_ERROR_NO_FURTHER,
1056 TYPEAHEAD_CANCEL,
1057 };
1058
1059 static void
typeahead_error(struct session * ses,unsigned char * typeahead,int no_further)1060 typeahead_error(struct session *ses, unsigned char *typeahead, int no_further)
1061 {
1062 unsigned char *message;
1063
1064 if (no_further)
1065 message = N_("No further matches for '%s'.");
1066 else
1067 message = N_("Could not find a link with the text '%s'.");
1068
1069 print_find_error_not_found(ses, N_("Typeahead"), message, typeahead);
1070 }
1071
1072 static inline unsigned char *
get_link_typeahead_text(struct link * link)1073 get_link_typeahead_text(struct link *link)
1074 {
1075 unsigned char *name = get_link_name(link);
1076
1077 if (name) return name;
1078 if (link->where) return link->where;
1079 if (link->where_img) return link->where_img;
1080
1081 return "";
1082 }
1083
1084 static int
match_link_text(struct link * link,unsigned char * text,int textlen,int case_sensitive)1085 match_link_text(struct link *link, unsigned char *text, int textlen,
1086 int case_sensitive)
1087 {
1088 unsigned char *match = get_link_typeahead_text(link);
1089 unsigned char *matchpos;
1090
1091 if (link_is_form(link) || textlen > strlen(match))
1092 return -1;
1093
1094 matchpos = case_sensitive ? strstr(match, text)
1095 : strcasestr(match, text);
1096
1097 if (matchpos) {
1098 return matchpos - match;
1099 }
1100
1101 return -1;
1102 }
1103
1104 /* Searches the @document for a link with the given @text. takes the
1105 * current_link in the view, the link to start searching from @i and the
1106 * direction to search (1 is forward, -1 is back). */
1107 static inline int
search_link_text(struct document * document,int current_link,int i,unsigned char * text,int direction,int * offset)1108 search_link_text(struct document *document, int current_link, int i,
1109 unsigned char *text, int direction, int *offset)
1110 {
1111 int upper_link, lower_link;
1112 int case_sensitive = get_opt_bool("document.browse.search.case");
1113 int wraparound = get_opt_bool("document.browse.search.wraparound");
1114 int textlen = strlen(text);
1115
1116 assert(textlen && direction && offset);
1117
1118 /* The link interval in which we are currently searching */
1119 /* Set up the range of links that should be search in first attempt */
1120 if (direction > 0) {
1121 upper_link = document->nlinks;
1122 lower_link = i - 1;
1123 } else {
1124 upper_link = i + 1;
1125 lower_link = -1;
1126 }
1127
1128 for (; i > lower_link && i < upper_link; i += direction) {
1129 struct link *link = &document->links[i];
1130 int match_offset = match_link_text(link, text, textlen,
1131 case_sensitive);
1132
1133 if (match_offset >= 0) {
1134 *offset = match_offset;
1135 return i;
1136 }
1137
1138 if (!wraparound) continue;
1139
1140 /* Check if we are at the end of the first range.
1141 * Only wrap around one time. Initialize @i with
1142 * {+= direction} in mind. */
1143 if (direction > 0) {
1144 if (i == upper_link - 1) {
1145 upper_link = current_link + 1;
1146 lower_link = -1;
1147 i = lower_link;
1148 wraparound = 0;
1149 }
1150 } else {
1151 if (i == lower_link + 1) {
1152 upper_link = document->nlinks;
1153 lower_link = current_link - 1;
1154 i = upper_link;
1155 wraparound = 0;
1156 }
1157 }
1158 }
1159
1160 return -1;
1161 }
1162
1163 /* The typeahead input line takes up one of the viewed lines so we
1164 * might have to scroll if the link is under the input line. */
1165 static inline void
fixup_typeahead_match(struct session * ses,struct document_view * doc_view)1166 fixup_typeahead_match(struct session *ses, struct document_view *doc_view)
1167 {
1168 int current_link = doc_view->vs->current_link;
1169 struct link *link = &doc_view->document->links[current_link];
1170
1171 doc_view->box.height -= 1;
1172 set_pos_x(doc_view, link);
1173 set_pos_y(doc_view, link);
1174 doc_view->box.height += 1;
1175 }
1176
1177 static inline unsigned char
get_document_char(struct document * document,int x,int y)1178 get_document_char(struct document *document, int x, int y)
1179 {
1180 return (document->height > y && document->data[y].length > x)
1181 ? document->data[y].chars[x].data : 0;
1182 }
1183
1184 static void
draw_typeahead_match(struct terminal * term,struct document_view * doc_view,int chars,int offset)1185 draw_typeahead_match(struct terminal *term, struct document_view *doc_view,
1186 int chars, int offset)
1187 {
1188 struct color_pair *color = get_bfu_color(term, "searched");
1189 int xoffset = doc_view->box.x - doc_view->vs->x;
1190 int yoffset = doc_view->box.y - doc_view->vs->y;
1191 struct link *link = get_current_link(doc_view);
1192 unsigned char *text = get_link_typeahead_text(link);
1193 int end = offset + chars;
1194 int i, j;
1195
1196 for (i = 0, j = 0; text[j] && i < end; i++, j++) {
1197 int x = link->points[i].x;
1198 int y = link->points[i].y;
1199 unsigned char data = get_document_char(doc_view->document, x, y);
1200
1201 /* Text wrapping might remove space chars from the link
1202 * position array so try to align the matched typeahead text
1203 * with what is actually on the screen by shifting the link
1204 * position variables if the canvas data do not match. */
1205 if (data != text[j]) {
1206 i--;
1207 end--;
1208 offset--;
1209
1210 } else if (i >= offset) {
1211 /* TODO: We should take in account original colors and
1212 * combine them with defined color. */
1213 draw_char_color(term, xoffset + x, yoffset + y, color);
1214 }
1215 }
1216 }
1217
1218 static enum typeahead_code
do_typeahead(struct session * ses,struct document_view * doc_view,unsigned char * text,int action_id,int * offset)1219 do_typeahead(struct session *ses, struct document_view *doc_view,
1220 unsigned char *text, int action_id, int *offset)
1221 {
1222 int current = int_max(doc_view->vs->current_link, 0);
1223 int direction, match, i = current;
1224 struct document *document = doc_view->document;
1225
1226 switch (action_id) {
1227 case ACT_EDIT_PREVIOUS_ITEM:
1228 case ACT_EDIT_UP:
1229 direction = -1;
1230 i--;
1231 if (i >= 0) break;
1232 if (!get_opt_bool("document.browse.search.wraparound")) {
1233 search_hit_boundary:
1234 if (match_link_text(&document->links[current],
1235 text, strlen(text),
1236 get_opt_bool("document"
1237 ".browse"
1238 ".search"
1239 ".case"))
1240 >= 0) {
1241 return TYPEAHEAD_ERROR_NO_FURTHER;
1242 }
1243
1244 return TYPEAHEAD_ERROR;
1245 }
1246
1247 i = doc_view->document->nlinks - 1;
1248 break;
1249
1250 case ACT_EDIT_NEXT_ITEM:
1251 case ACT_EDIT_DOWN:
1252 direction = 1;
1253 i++;
1254 if (i < doc_view->document->nlinks) break;
1255 if (!get_opt_bool("document.browse.search.wraparound"))
1256 goto search_hit_boundary;
1257
1258 i = 0;
1259 break;
1260
1261 case ACT_EDIT_ENTER:
1262 goto_current_link(ses, doc_view, 0);
1263 return TYPEAHEAD_CANCEL;
1264
1265 default:
1266 direction = 1;
1267 }
1268
1269 match = search_link_text(document, current, i, text, direction, offset);
1270
1271 if (match == current && i != current)
1272 return TYPEAHEAD_ERROR_NO_FURTHER;
1273
1274 if (match < 0) {
1275 if (i != current)
1276 return TYPEAHEAD_ERROR_NO_FURTHER;
1277
1278 return TYPEAHEAD_ERROR;
1279 }
1280
1281 assert(match >= 0 && match < doc_view->document->nlinks);
1282
1283 doc_view->vs->current_link = match;
1284 return TYPEAHEAD_MATCHED;
1285 }
1286
1287
1288 /* Typeahead */
1289
1290 static enum input_line_code
text_typeahead_handler(struct input_line * line,int action_id)1291 text_typeahead_handler(struct input_line *line, int action_id)
1292 {
1293 struct session *ses = line->ses;
1294 unsigned char *buffer = line->buffer;
1295 struct document_view *doc_view = current_frame(ses);
1296 int direction = ((unsigned char *) line->data)[0] == '/' ? 1 : -1;
1297 int report_errors = action_id == -1;
1298 enum find_error error;
1299
1300 assertm(doc_view, "document not formatted");
1301 if_assert_failed return INPUT_LINE_CANCEL;
1302
1303 switch (action_id) {
1304 case ACT_EDIT_REDRAW:
1305 return INPUT_LINE_PROCEED;
1306
1307 case ACT_EDIT_ENTER:
1308 if (!*buffer) {
1309 /* This ensures that search-typeahead-text
1310 * followed immediately with enter
1311 * clears the last search. */
1312 search_for_do(ses, buffer, direction, 0);
1313 }
1314 goto_current_link(ses, doc_view, 0);
1315 return INPUT_LINE_CANCEL;
1316
1317 case ACT_EDIT_PREVIOUS_ITEM:
1318 find_next(ses, doc_view, -1);
1319 break;
1320
1321 case ACT_EDIT_NEXT_ITEM:
1322 find_next(ses, doc_view, 1);
1323 break;
1324
1325 case ACT_EDIT_SEARCH_TOGGLE_REGEX: {
1326 struct option *opt =
1327 get_opt_rec(config_options,
1328 "document.browse.search.regex");
1329
1330 opt->value.number = (opt->value.number + 1)
1331 % (opt->max + 1);
1332 option_changed(ses, opt, opt);
1333 }
1334 /* Fall thru */
1335
1336 default:
1337 error = search_for_do(ses, buffer, direction, 0);
1338
1339 if (error == FIND_ERROR_REGEX)
1340 break;
1341
1342 if (report_errors)
1343 print_find_error(ses, error);
1344
1345 /* We need to check |*buffer| here because
1346 * the input-line code will call this handler
1347 * even after it handles a back-space press. */
1348 if (error != FIND_ERROR_HIT_TOP
1349 && error != FIND_ERROR_HIT_BOTTOM
1350 && error != FIND_ERROR_NONE && *buffer)
1351 return INPUT_LINE_REWIND;
1352 }
1353
1354 draw_formatted(ses, 0);
1355 return INPUT_LINE_PROCEED;
1356 }
1357
1358 static enum input_line_code
link_typeahead_handler(struct input_line * line,int action_id)1359 link_typeahead_handler(struct input_line *line, int action_id)
1360 {
1361 struct session *ses = line->ses;
1362 unsigned char *buffer = line->buffer;
1363 struct document_view *doc_view = current_frame(ses);
1364 int offset = 0;
1365
1366 assertm(doc_view, "document not formatted");
1367 if_assert_failed return INPUT_LINE_CANCEL;
1368
1369 /* If there is nothing to match with don't start searching */
1370 if (!*buffer) {
1371 /* If something already were typed we need to redraw
1372 * in order to remove the coloring of the link text. */
1373 if (line->data) draw_formatted(ses, 0);
1374 return INPUT_LINE_PROCEED;
1375 }
1376
1377 if (action_id == ACT_EDIT_REDRAW) {
1378 int current = doc_view->vs->current_link;
1379 int offset, bufferlen;
1380
1381 if (current < 0) return INPUT_LINE_PROCEED;
1382
1383 bufferlen = strlen(buffer);
1384 offset = match_link_text(&doc_view->document->links[current],
1385 buffer, bufferlen,
1386 get_opt_bool("document.browse"
1387 ".search.case"));
1388
1389 if (offset >= 0) {
1390 draw_typeahead_match(ses->tab->term, doc_view,
1391 bufferlen, offset);
1392 }
1393
1394 return INPUT_LINE_PROCEED;
1395 }
1396
1397 /* Hack time .. should we change mode? */
1398 if (!line->data) {
1399 enum main_action action_id = ACT_MAIN_NONE;
1400
1401 switch (*buffer) {
1402 case '#':
1403 action_id = ACT_MAIN_SEARCH_TYPEAHEAD_LINK;
1404 break;
1405
1406 case '?':
1407 action_id = ACT_MAIN_SEARCH_TYPEAHEAD_TEXT_BACK;
1408 break;
1409
1410 case '/':
1411 action_id = ACT_MAIN_SEARCH_TYPEAHEAD_TEXT;
1412 break;
1413
1414 default:
1415 break;
1416 }
1417
1418 /* Should we reboot the input line .. (inefficient but easy) */
1419 if (action_id != ACT_MAIN_NONE) {
1420 search_typeahead(ses, doc_view, action_id);
1421 return INPUT_LINE_CANCEL;
1422 }
1423
1424 line->data = "#";
1425 }
1426
1427 switch (do_typeahead(ses, doc_view, buffer, action_id, &offset)) {
1428 case TYPEAHEAD_MATCHED:
1429 fixup_typeahead_match(ses, doc_view);
1430 draw_formatted(ses, 0);
1431 draw_typeahead_match(ses->tab->term, doc_view, strlen(buffer), offset);
1432 return INPUT_LINE_PROCEED;
1433
1434 case TYPEAHEAD_ERROR_NO_FURTHER:
1435 typeahead_error(ses, buffer, 1);
1436 draw_typeahead_match(ses->tab->term, doc_view, strlen(buffer), offset);
1437 return INPUT_LINE_PROCEED;
1438
1439 case TYPEAHEAD_ERROR:
1440 typeahead_error(ses, buffer, 0);
1441 return INPUT_LINE_REWIND;
1442
1443 case TYPEAHEAD_CANCEL:
1444 default:
1445 return INPUT_LINE_CANCEL;
1446 }
1447 }
1448
1449 enum frame_event_status
search_typeahead(struct session * ses,struct document_view * doc_view,action_id_T action_id)1450 search_typeahead(struct session *ses, struct document_view *doc_view,
1451 action_id_T action_id)
1452 {
1453 unsigned char *prompt = "#";
1454 unsigned char *data = NULL;
1455 input_line_handler_T handler = text_typeahead_handler;
1456 struct input_history *history = &search_history;
1457
1458 switch (action_id) {
1459 case ACT_MAIN_SEARCH_TYPEAHEAD_TEXT:
1460 prompt = data = "/";
1461 break;
1462
1463 case ACT_MAIN_SEARCH_TYPEAHEAD_TEXT_BACK:
1464 prompt = data = "?";
1465 break;
1466
1467 case ACT_MAIN_SEARCH_TYPEAHEAD_LINK:
1468 data = "#";
1469 /* Falling forward .. good punk rock */
1470 case ACT_MAIN_SEARCH_TYPEAHEAD:
1471 default:
1472 if (doc_view->document->nlinks) {
1473 handler = link_typeahead_handler;
1474 break;
1475 }
1476
1477 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
1478 N_("Typeahead"), ALIGN_CENTER,
1479 msg_text(ses->tab->term,
1480 N_("No links in current document")));
1481
1482 return FRAME_EVENT_OK;
1483 }
1484
1485 input_field_line(ses, prompt, data, history, handler);
1486 return FRAME_EVENT_OK;
1487 }
1488
1489
1490 /* The dialog functions are clones of input_field() ones. Gross code
1491 * duplication. */
1492 /* TODO: This is just hacked input_field(), containing a lot of generic crap
1493 * etc. The useless cruft should be blasted out. And it's quite ugly anyway,
1494 * a nice cleanup target ;-). --pasky */
1495
1496 enum search_option {
1497 #ifdef HAVE_REGEX_H
1498 SEARCH_OPT_REGEX,
1499 #endif
1500 SEARCH_OPT_CASE,
1501 SEARCH_OPTIONS,
1502 };
1503
1504 static struct option_resolver resolvers[] = {
1505 #ifdef HAVE_REGEX_H
1506 { SEARCH_OPT_REGEX, "regex" },
1507 #endif
1508 { SEARCH_OPT_CASE, "case" },
1509 };
1510
1511 struct search_dlg_hop {
1512 void *data;
1513 union option_value values[SEARCH_OPTIONS];
1514 };
1515
1516 static widget_handler_status_T
search_dlg_cancel(struct dialog_data * dlg_data,struct widget_data * widget_data)1517 search_dlg_cancel(struct dialog_data *dlg_data, struct widget_data *widget_data)
1518 {
1519 void (*fn)(void *) = widget_data->widget->data;
1520 struct search_dlg_hop *hop = dlg_data->dlg->udata2;
1521 void *data = hop->data;
1522
1523 if (fn) fn(data);
1524 return cancel_dialog(dlg_data, widget_data);
1525 }
1526
1527 static widget_handler_status_T
search_dlg_ok(struct dialog_data * dlg_data,struct widget_data * widget_data)1528 search_dlg_ok(struct dialog_data *dlg_data, struct widget_data *widget_data)
1529 {
1530 void (*fn)(void *, unsigned char *) = widget_data->widget->data;
1531 struct search_dlg_hop *hop = dlg_data->dlg->udata2;
1532 void *data = hop->data;
1533 unsigned char *text = dlg_data->widgets_data->cdata;
1534
1535 update_dialog_data(dlg_data);
1536
1537 commit_option_values(resolvers, get_opt_rec(config_options,
1538 "document.browse.search"),
1539 hop->values, SEARCH_OPTIONS);
1540
1541 if (check_dialog(dlg_data)) return EVENT_NOT_PROCESSED;
1542
1543 add_to_input_history(dlg_data->dlg->widgets->info.field.history, text, 1);
1544
1545 if (fn) fn(data, text);
1546
1547 return cancel_dialog(dlg_data, widget_data);
1548 }
1549
1550 /* XXX: @data is ignored. */
1551 static void
search_dlg_do(struct terminal * term,struct memory_list * ml,unsigned char * title,void * data,struct input_history * history,void (* fn)(void *,unsigned char *))1552 search_dlg_do(struct terminal *term, struct memory_list *ml,
1553 unsigned char *title, void *data,
1554 struct input_history *history,
1555 void (*fn)(void *, unsigned char *))
1556 {
1557 struct dialog *dlg;
1558 unsigned char *field;
1559 struct search_dlg_hop *hop;
1560 unsigned char *text = _("Search for text", term);
1561
1562 hop = mem_calloc(1, sizeof(*hop));
1563 if (!hop) return;
1564
1565 checkout_option_values(resolvers, get_opt_rec(config_options,
1566 "document.browse.search"),
1567 hop->values, SEARCH_OPTIONS);
1568 hop->data = data;
1569
1570 #ifdef HAVE_REGEX_H
1571 #define SEARCH_WIDGETS_COUNT 8
1572 #else
1573 #define SEARCH_WIDGETS_COUNT 5
1574 #endif
1575 dlg = calloc_dialog(SEARCH_WIDGETS_COUNT, MAX_STR_LEN);
1576 if (!dlg) {
1577 mem_free(hop);
1578 return;
1579 }
1580
1581 dlg->title = _(title, term);
1582 dlg->layouter = generic_dialog_layouter;
1583 dlg->layout.fit_datalen = 1;
1584 dlg->layout.float_groups = 1;
1585 dlg->udata = text;
1586 dlg->udata2 = hop;
1587
1588 add_to_ml(&ml, hop, NULL);
1589
1590 /* @field is automatically cleared by calloc() */
1591 field = get_dialog_offset(dlg, SEARCH_WIDGETS_COUNT);
1592 add_dlg_field(dlg, text, 0, 0, NULL, MAX_STR_LEN, field, history);
1593
1594 #ifdef HAVE_REGEX_H
1595 add_dlg_radio(dlg, _("Normal search", term), 1, 0, &hop->values[SEARCH_OPT_REGEX].number);
1596 add_dlg_radio(dlg, _("Regexp search", term), 1, 1, &hop->values[SEARCH_OPT_REGEX].number);
1597 add_dlg_radio(dlg, _("Extended regexp search", term), 1, 2, &hop->values[SEARCH_OPT_REGEX].number);
1598 #endif
1599 add_dlg_radio(dlg, _("Case sensitive", term), 2, 1, &hop->values[SEARCH_OPT_CASE].number);
1600 add_dlg_radio(dlg, _("Case insensitive", term), 2, 0, &hop->values[SEARCH_OPT_CASE].number);
1601
1602 add_dlg_button(dlg, _("~OK", term), B_ENTER, search_dlg_ok, fn);
1603 add_dlg_button(dlg, _("~Cancel", term), B_ESC, search_dlg_cancel, NULL);
1604
1605 add_dlg_end(dlg, SEARCH_WIDGETS_COUNT);
1606
1607 add_to_ml(&ml, dlg, NULL);
1608 do_dialog(term, dlg, ml);
1609 }
1610
1611 enum frame_event_status
search_dlg(struct session * ses,struct document_view * doc_view,int direction)1612 search_dlg(struct session *ses, struct document_view *doc_view, int direction)
1613 {
1614 unsigned char *title;
1615 void *search_function;
1616
1617 assert(direction);
1618 if_assert_failed return FRAME_EVENT_OK;
1619
1620 if (direction > 0) {
1621 title = N_("Search");
1622 search_function = search_for;
1623 } else {
1624 title = N_("Search backward");
1625 search_function = search_for_back;
1626 }
1627
1628 search_dlg_do(ses->tab->term, NULL,
1629 title, ses,
1630 &search_history,
1631 search_function);
1632
1633 return FRAME_EVENT_OK;
1634 }
1635
1636 static enum evhook_status
search_history_write_hook(va_list ap,void * data)1637 search_history_write_hook(va_list ap, void *data)
1638 {
1639 save_input_history(&search_history, SEARCH_HISTORY_FILENAME);
1640 return EVENT_HOOK_STATUS_NEXT;
1641 }
1642
1643 static struct event_hook_info search_history_hooks[] = {
1644 { "periodic-saving", 0, search_history_write_hook, NULL },
1645
1646 NULL_EVENT_HOOK_INFO,
1647 };
1648
1649 static void
init_search_history(struct module * module)1650 init_search_history(struct module *module)
1651 {
1652 load_input_history(&search_history, SEARCH_HISTORY_FILENAME);
1653 }
1654
1655 static void
done_search_history(struct module * module)1656 done_search_history(struct module *module)
1657 {
1658 save_input_history(&search_history, SEARCH_HISTORY_FILENAME);
1659 free_list(search_history.entries);
1660 }
1661
1662 struct module search_history_module = struct_module(
1663 /* name: */ N_("Search History"),
1664 /* options: */ NULL,
1665 /* hooks: */ search_history_hooks,
1666 /* submodules: */ NULL,
1667 /* data: */ NULL,
1668 /* init: */ init_search_history,
1669 /* done: */ done_search_history
1670 );
1671