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(&regex, 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, &regex, 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(&regex);
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(&regex, doctmp, 1, &regmatch, 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(&regex);
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