xref: /freebsd/contrib/less/search.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
1 /*
2  * Copyright (C) 1984-2024  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 
11 /*
12  * Routines to search a file for a pattern.
13  */
14 
15 #include "less.h"
16 #include "position.h"
17 #include "charset.h"
18 
19 #define MINPOS(a,b)     (((a) < (b)) ? (a) : (b))
20 #define MAXPOS(a,b)     (((a) > (b)) ? (a) : (b))
21 
22 extern int sigs;
23 extern int how_search;
24 extern int caseless;
25 extern int linenums;
26 extern int jump_sline;
27 extern int bs_mode;
28 extern int proc_backspace;
29 extern int proc_return;
30 extern int ctldisp;
31 extern int status_col;
32 extern void *ml_search;
33 extern POSITION start_attnpos;
34 extern POSITION end_attnpos;
35 extern int utf_mode;
36 extern int sc_width;
37 extern int sc_height;
38 extern int hshift;
39 extern int match_shift;
40 extern int nosearch_header_lines;
41 extern int nosearch_header_cols;
42 extern int header_lines;
43 extern int header_cols;
44 extern LWCHAR rscroll_char;
45 #if HILITE_SEARCH
46 extern int hilite_search;
47 extern size_t size_linebuf;
48 extern lbool squished;
49 extern int can_goto_line;
50 extern lbool no_eof_bell;
51 static lbool hide_hilite;
52 static POSITION prep_startpos;
53 static POSITION prep_endpos;
54 public POSITION header_start_pos = NULL_POSITION;
55 static POSITION header_end_pos;
56 public lbool search_wrapped = FALSE;
57 #if OSC8_LINK
58 public POSITION osc8_linepos = NULL_POSITION;
59 public POSITION osc8_match_start = NULL_POSITION;
60 public POSITION osc8_match_end = NULL_POSITION;
61 public POSITION osc8_params_start = NULL_POSITION;
62 public POSITION osc8_params_end = NULL_POSITION;
63 public POSITION osc8_uri_start = NULL_POSITION;
64 public POSITION osc8_uri_end = NULL_POSITION;
65 public POSITION osc8_text_start = NULL_POSITION;
66 public POSITION osc8_text_end = NULL_POSITION;
67 char *osc8_path = NULL;
68 char *osc8_uri = NULL;
69 constant char *osc8_search_param = NULL;
70 #endif
71 
72 /*
73  * Structures for maintaining a set of ranges for hilites and filtered-out
74  * lines. Each range is stored as a node within a red-black tree, and we
75  * try to extend existing ranges (without creating overlaps) rather than
76  * create new nodes if possible. We remember the last node found by a
77  * search for constant-time lookup if the next search is near enough to
78  * the previous. To aid that, we overlay a secondary doubly-linked list
79  * on top of the red-black tree so we can find the preceding/succeeding
80  * nodes also in constant time.
81  *
82  * Each node is allocated from a series of pools, each pool double the size
83  * of the previous (for amortised constant time allocation). Since our only
84  * tree operations are clear and node insertion, not node removal, we don't
85  * need to maintain a usage bitmap or freelist and can just return nodes
86  * from the pool in-order until capacity is reached.
87  */
88 struct hilite
89 {
90 	POSITION hl_startpos;
91 	POSITION hl_endpos;
92 	int hl_attr;
93 };
94 struct hilite_node
95 {
96 	struct hilite_node *parent;
97 	struct hilite_node *left;
98 	struct hilite_node *right;
99 	struct hilite_node *prev;
100 	struct hilite_node *next;
101 	int red;
102 	struct hilite r;
103 };
104 struct hilite_storage
105 {
106 	size_t capacity;
107 	size_t used;
108 	struct hilite_storage *next;
109 	struct hilite_node *nodes;
110 };
111 struct hilite_tree
112 {
113 	struct hilite_storage *first;
114 	struct hilite_storage *current;
115 	struct hilite_node *root;
116 	struct hilite_node *lookaside;
117 };
118 #define HILITE_INITIALIZER() { NULL, NULL, NULL, NULL }
119 #define HILITE_LOOKASIDE_STEPS 2
120 
121 static struct hilite_tree hilite_anchor = HILITE_INITIALIZER();
122 static struct hilite_tree filter_anchor = HILITE_INITIALIZER();
123 static struct pattern_info *filter_infos = NULL;
124 
125 #endif
126 
127 /*
128  * These are the static variables that represent the "remembered"
129  * search pattern and filter pattern.
130  */
131 struct pattern_info {
132 	PATTERN_TYPE compiled;
133 	char* text;
134 	int search_type;
135 	lbool is_ucase_pattern;
136 	struct pattern_info *next;
137 };
138 
139 #if NO_REGEX
140 #define info_compiled(info) ((void*)0)
141 #else
142 #define info_compiled(info) ((info)->compiled)
143 #endif
144 
145 static struct pattern_info search_info;
146 public int is_caseless;
147 
148 /*
149  * Are there any uppercase letters in this string?
150  */
151 static lbool is_ucase(constant char *str)
152 {
153 	constant char *str_end = str + strlen(str);
154 	LWCHAR ch;
155 
156 	while (str < str_end)
157 	{
158 		ch = step_charc(&str, +1, str_end);
159 		if (IS_UPPER(ch))
160 			return (TRUE);
161 	}
162 	return (FALSE);
163 }
164 
165 /*
166  * Discard a saved pattern.
167  */
168 static void clear_pattern(struct pattern_info *info)
169 {
170 	if (info->text != NULL)
171 		free(info->text);
172 	info->text = NULL;
173 #if !NO_REGEX
174 	uncompile_pattern(&info->compiled);
175 #endif
176 }
177 
178 /*
179  * Compile and save a search pattern.
180  */
181 static int set_pattern(struct pattern_info *info, constant char *pattern, int search_type, int show_error)
182 {
183 	/*
184 	 * Ignore case if -I is set OR
185 	 * -i is set AND the pattern is all lowercase.
186 	 */
187 	info->is_ucase_pattern = (pattern == NULL) ? FALSE : is_ucase(pattern);
188 	is_caseless = (info->is_ucase_pattern && caseless != OPT_ONPLUS) ? 0 : caseless;
189 #if !NO_REGEX
190 	if (pattern == NULL)
191 		SET_NULL_PATTERN(info->compiled);
192 	else if (compile_pattern(pattern, search_type, show_error, &info->compiled) < 0)
193 		return -1;
194 #endif
195 	/* Pattern compiled successfully; save the text too. */
196 	if (info->text != NULL)
197 		free(info->text);
198 	info->text = NULL;
199 	if (pattern != NULL)
200 	{
201 		info->text = (char *) ecalloc(1, strlen(pattern)+1);
202 		strcpy(info->text, pattern);
203 	}
204 	info->search_type = search_type;
205 	return 0;
206 }
207 
208 /*
209  * Initialize saved pattern to nothing.
210  */
211 static void init_pattern(struct pattern_info *info)
212 {
213 	SET_NULL_PATTERN(info->compiled);
214 	info->text = NULL;
215 	info->search_type = 0;
216 	info->next = NULL;
217 }
218 
219 /*
220  * Initialize search variables.
221  */
222 public void init_search(void)
223 {
224 	init_pattern(&search_info);
225 }
226 
227 /*
228  * Determine which text conversions to perform before pattern matching.
229  */
230 public int get_cvt_ops(int search_type)
231 {
232 	int ops = 0;
233 
234 	if (is_caseless && (!re_handles_caseless || (search_type & SRCH_NO_REGEX)))
235 		ops |= CVT_TO_LC;
236 	if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF))
237 		ops |= CVT_BS;
238 	if (proc_return == OPT_ON || (bs_mode != BS_CONTROL && proc_backspace == OPT_OFF))
239 		ops |= CVT_CRLF;
240 	if (ctldisp == OPT_ONPLUS)
241 		ops |= CVT_ANSI;
242 	return (ops);
243 }
244 
245 /*
246  * Is there a previous (remembered) search pattern?
247  */
248 static int prev_pattern(struct pattern_info *info)
249 {
250 #if !NO_REGEX
251 	if ((info->search_type & SRCH_NO_REGEX) == 0)
252 		return (!is_null_pattern(info->compiled));
253 #endif
254 	return (info->text != NULL);
255 }
256 
257 #if HILITE_SEARCH
258 /*
259  * Repaint the hilites currently displayed on the screen.
260  * Repaint each line which contains highlighted text.
261  * If on==0, force all hilites off.
262  */
263 public void repaint_hilite(lbool on)
264 {
265 	int sindex;
266 	POSITION pos;
267 	lbool save_hide_hilite;
268 
269 	if (squished)
270 		repaint();
271 
272 	save_hide_hilite = hide_hilite;
273 	if (!on)
274 	{
275 		if (hide_hilite)
276 			return;
277 		hide_hilite = TRUE;
278 	}
279 
280 	if (!can_goto_line)
281 	{
282 		repaint();
283 		hide_hilite = save_hide_hilite;
284 		return;
285 	}
286 
287 	for (sindex = TOP;  sindex < TOP + sc_height-1;  sindex++)
288 	{
289 		pos = position(sindex);
290 		if (pos == NULL_POSITION)
291 			continue;
292 		(void) forw_line(pos);
293 		goto_line(sindex);
294 		clear_eol();
295 		put_line();
296 	}
297 	overlay_header();
298 	lower_left();
299 	hide_hilite = save_hide_hilite;
300 }
301 #endif
302 
303 /*
304  * Clear the attn hilite.
305  */
306 public void clear_attn(void)
307 {
308 #if HILITE_SEARCH
309 	int sindex;
310 	POSITION old_start_attnpos;
311 	POSITION old_end_attnpos;
312 	POSITION pos;
313 	POSITION epos;
314 	int moved = 0;
315 
316 	if (start_attnpos == NULL_POSITION)
317 		return;
318 	old_start_attnpos = start_attnpos;
319 	old_end_attnpos = end_attnpos;
320 	start_attnpos = end_attnpos = NULL_POSITION;
321 
322 	if (!can_goto_line)
323 	{
324 		repaint();
325 		return;
326 	}
327 	if (squished)
328 		repaint();
329 
330 	for (sindex = TOP;  sindex < TOP + sc_height-1;  sindex++)
331 	{
332 		pos = position(sindex);
333 		if (pos == NULL_POSITION)
334 			continue;
335 		epos = position(sindex+1);
336 		if (pos <= old_end_attnpos &&
337 		     (epos == NULL_POSITION || epos > old_start_attnpos))
338 		{
339 			(void) forw_line(pos);
340 			goto_line(sindex);
341 			clear_eol();
342 			put_line();
343 			moved = 1;
344 		}
345 	}
346 	if (overlay_header())
347 		moved = 1;
348 	if (moved)
349 		lower_left();
350 #endif
351 }
352 
353 /*
354  * Toggle or clear search string highlighting.
355  */
356 public void undo_search(lbool clear)
357 {
358 	clear_pattern(&search_info);
359 #if OSC8_LINK
360 	osc8_linepos = NULL_POSITION;
361 #endif
362 #if HILITE_SEARCH
363 	if (clear)
364 	{
365 		clr_hilite();
366 	} else
367 	{
368 		if (hilite_anchor.first == NULL)
369 		{
370 			error("No previous regular expression", NULL_PARG);
371 			return;
372 		}
373 		hide_hilite = !hide_hilite;
374 	}
375 	repaint_hilite(TRUE);
376 #endif
377 }
378 
379 #if HILITE_SEARCH
380 /*
381  * Clear the hilite list.
382  */
383 public void clr_hlist(struct hilite_tree *anchor)
384 {
385 	struct hilite_storage *hls;
386 	struct hilite_storage *nexthls;
387 
388 	for (hls = anchor->first;  hls != NULL;  hls = nexthls)
389 	{
390 		nexthls = hls->next;
391 		free((void*)hls->nodes);
392 		free((void*)hls);
393 	}
394 	anchor->first = NULL;
395 	anchor->current = NULL;
396 	anchor->root = NULL;
397 
398 	anchor->lookaside = NULL;
399 
400 	prep_startpos = prep_endpos = NULL_POSITION;
401 }
402 
403 public void clr_hilite(void)
404 {
405 	clr_hlist(&hilite_anchor);
406 }
407 
408 public void clr_filter(void)
409 {
410 	clr_hlist(&filter_anchor);
411 }
412 
413 /*
414  * Find the node covering pos, or the node after it if no node covers it,
415  * or return NULL if pos is after the last range. Remember the found node,
416  * to speed up subsequent searches for the same or similar positions (if
417  * we return NULL, remember the last node.)
418  */
419 static struct hilite_node* hlist_find(struct hilite_tree *anchor, POSITION pos)
420 {
421 	struct hilite_node *n, *m;
422 
423 	if (anchor->lookaside)
424 	{
425 		int steps = 0;
426 		int hit = 0;
427 
428 		n = anchor->lookaside;
429 
430 		for (;;)
431 		{
432 			if (pos < n->r.hl_endpos)
433 			{
434 				if (n->prev == NULL || pos >= n->prev->r.hl_endpos)
435 				{
436 					hit = 1;
437 					break;
438 				}
439 			} else if (n->next == NULL)
440 			{
441 				n = NULL;
442 				hit = 1;
443 				break;
444 			}
445 
446 			/*
447 			 * If we don't find the right node within a small
448 			 * distance, don't keep doing a linear search!
449 			 */
450 			if (steps >= HILITE_LOOKASIDE_STEPS)
451 				break;
452 			steps++;
453 
454 			if (pos < n->r.hl_endpos)
455 				anchor->lookaside = n = n->prev;
456 			else
457 				anchor->lookaside = n = n->next;
458 		}
459 
460 		if (hit)
461 			return n;
462 	}
463 
464 	n = anchor->root;
465 	m = NULL;
466 
467 	while (n != NULL)
468 	{
469 		if (pos < n->r.hl_startpos)
470 		{
471 			if (n->left != NULL)
472 			{
473 				m = n;
474 				n = n->left;
475 				continue;
476 			}
477 			break;
478 		}
479 		if (pos >= n->r.hl_endpos)
480 		{
481 			if (n->right != NULL)
482 			{
483 				n = n->right;
484 				continue;
485 			}
486 			if (m != NULL)
487 			{
488 				n = m;
489 			} else
490 			{
491 				m = n;
492 				n = NULL;
493 			}
494 		}
495 		break;
496 	}
497 
498 	if (n != NULL)
499 		anchor->lookaside = n;
500 	else if (m != NULL)
501 		anchor->lookaside = m;
502 
503 	return n;
504 }
505 
506 /*
507  * Should any characters in a specified range be highlighted?
508  */
509 static int hilited_range_attr(POSITION pos, POSITION epos)
510 {
511 	struct hilite_node *n = hlist_find(&hilite_anchor, pos);
512 	if (n == NULL)
513 		return 0;
514 	if (epos != NULL_POSITION && epos <= n->r.hl_startpos)
515 		return 0;
516 	return n->r.hl_attr;
517 }
518 
519 /*
520  * Set header parameters.
521  */
522 public void set_header(POSITION pos)
523 {
524 	header_start_pos = (header_lines == 0) ? NULL_POSITION : pos;
525 	if (header_start_pos != NULL_POSITION)
526 	{
527 		int ln;
528 		for (ln = 0; ln < header_lines; ++ln)
529 		{
530 			pos = forw_raw_line(pos, NULL, NULL);
531 			if (pos == NULL_POSITION) break;
532 		}
533 		header_end_pos = pos;
534 	}
535 }
536 
537 /*
538  * Is a position within the header lines?
539  */
540 static int pos_in_header(POSITION pos)
541 {
542 	return (header_start_pos != NULL_POSITION &&
543 	        pos >= header_start_pos && pos < header_end_pos);
544 }
545 
546 /*
547  * Is a line "filtered" -- that is, should it be hidden?
548  */
549 public lbool is_filtered(POSITION pos)
550 {
551 	struct hilite_node *n;
552 
553 	if (ch_getflags() & CH_HELPFILE)
554 		return (FALSE);
555 	if (pos_in_header(pos))
556 		return (FALSE);
557 
558 	n = hlist_find(&filter_anchor, pos);
559 	return (n != NULL && pos >= n->r.hl_startpos);
560 }
561 
562 /*
563  * If pos is hidden, return the next position which isn't, otherwise
564  * just return pos.
565  */
566 public POSITION next_unfiltered(POSITION pos)
567 {
568 	struct hilite_node *n;
569 
570 	if (ch_getflags() & CH_HELPFILE)
571 		return (pos);
572 	if (pos_in_header(pos))
573 		return (pos);
574 
575 	n = hlist_find(&filter_anchor, pos);
576 	while (n != NULL && pos >= n->r.hl_startpos)
577 	{
578 		pos = n->r.hl_endpos;
579 		n = n->next;
580 	}
581 	return (pos);
582 }
583 
584 /*
585  * If pos is hidden, return the previous position which isn't or 0 if
586  * we're filtered right to the beginning, otherwise just return pos.
587  */
588 public POSITION prev_unfiltered(POSITION pos)
589 {
590 	struct hilite_node *n;
591 
592 	if (ch_getflags() & CH_HELPFILE)
593 		return (pos);
594 	if (pos_in_header(pos))
595 		return (pos);
596 
597 	n = hlist_find(&filter_anchor, pos);
598 	while (n != NULL && pos >= n->r.hl_startpos)
599 	{
600 		pos = n->r.hl_startpos;
601 		if (pos == 0)
602 			break;
603 		pos--;
604 		n = n->prev;
605 	}
606 	return (pos);
607 }
608 
609 /*
610  * Set the hshift for the line starting at line_pos so that the string
611  * between start_off and end_off is visible on the screen.
612  */
613 static void shift_visible(POSITION line_pos, size_t start_off, size_t end_off)
614 {
615 	POSITION start_pos = line_pos + start_off;
616 	POSITION end_pos = line_pos + end_off;
617 	int start_col = col_from_pos(line_pos, start_pos, NULL_POSITION, -1);
618 	int end_col = col_from_pos(line_pos, end_pos, start_pos, start_col);
619 	int swidth = sc_width - line_pfx_width() - (rscroll_char ? 1 : 0);
620 	int new_hshift;
621 	if (start_col < 0 || end_col < 0)
622 		return;
623 	if (end_col < swidth) /* whole string is in first screen */
624 		new_hshift = 0;
625 	else if (start_col > hshift && end_col < hshift + swidth)
626 		new_hshift = hshift; /* already visible; leave hshift unchanged */
627 	else
628 	{
629 		int eol_col = col_from_pos(line_pos, NULL_POSITION, end_pos, end_col) - swidth;
630 		if (start_col >= eol_col) /* whole string is in last screen */
631 			new_hshift = eol_col;
632 		else /* shift it to column match_shift */
633 			new_hshift = (start_col < match_shift) ? 0 : start_col - match_shift;
634 	}
635 	if (new_hshift != hshift)
636 	{
637 		hshift = new_hshift;
638 		screen_trashed();
639 	}
640 }
641 
642 /*
643  * Should any characters in a specified range be highlighted?
644  * If nohide is nonzero, don't consider hide_hilite.
645  */
646 public int is_hilited_attr(POSITION pos, POSITION epos, int nohide, int *p_matches)
647 {
648 	int attr;
649 
650 	if (p_matches != NULL)
651 		*p_matches = 0;
652 
653 	if (!status_col &&
654 	    start_attnpos != NULL_POSITION &&
655 	    pos <= end_attnpos &&
656 	     (epos == NULL_POSITION || epos > start_attnpos))
657 		/*
658 		 * The attn line overlaps this range.
659 		 */
660 		return (AT_HILITE|AT_COLOR_ATTN);
661 
662 #if OSC8_LINK
663 	if (osc8_linepos != NULL_POSITION &&
664 			pos <= osc8_text_end && (epos == NULL_POSITION || epos > osc8_text_start))
665 		return (AT_HILITE|AT_COLOR_SEARCH);
666 #endif
667 
668 	attr = hilited_range_attr(pos, epos);
669 	if (attr == 0)
670 		return (0);
671 
672 	if (p_matches == NULL)
673 		/*
674 		 * Kinda kludgy way to recognize that caller is checking for
675 		 * hilite in status column. In this case we want to return
676 		 * hilite status even if hiliting is disabled or hidden.
677 		 */
678 		return (attr);
679 
680 	/*
681 	 * Report matches, even if we're hiding highlights.
682 	 */
683 	*p_matches = 1;
684 
685 	if (hilite_search == 0)
686 		/*
687 		 * Not doing highlighting.
688 		 */
689 		return (0);
690 
691 	if (!nohide && hide_hilite)
692 		/*
693 		 * Highlighting is hidden.
694 		 */
695 		return (0);
696 
697 	return (attr);
698 }
699 
700 /*
701  * Tree node storage: get the current block of nodes if it has spare
702  * capacity, or create a new one if not.
703  */
704 static struct hilite_storage * hlist_getstorage(struct hilite_tree *anchor)
705 {
706 	size_t capacity = 1;
707 	struct hilite_storage *s;
708 
709 	if (anchor->current)
710 	{
711 		if (anchor->current->used < anchor->current->capacity)
712 			return anchor->current;
713 		capacity = anchor->current->capacity * 2;
714 	}
715 
716 	s = (struct hilite_storage *) ecalloc(1, sizeof(struct hilite_storage));
717 	s->nodes = (struct hilite_node *) ecalloc(capacity, sizeof(struct hilite_node));
718 	s->capacity = capacity;
719 	s->used = 0;
720 	s->next = NULL;
721 	if (anchor->current)
722 		anchor->current->next = s;
723 	else
724 		anchor->first = s;
725 	anchor->current = s;
726 	return s;
727 }
728 
729 /*
730  * Tree node storage: retrieve a new empty node to be inserted into the
731  * tree.
732  */
733 static struct hilite_node * hlist_getnode(struct hilite_tree *anchor)
734 {
735 	struct hilite_storage *s = hlist_getstorage(anchor);
736 	return &s->nodes[s->used++];
737 }
738 
739 /*
740  * Rotate the tree left around a pivot node.
741  */
742 static void hlist_rotate_left(struct hilite_tree *anchor, struct hilite_node *n)
743 {
744 	struct hilite_node *np = n->parent;
745 	struct hilite_node *nr = n->right;
746 	struct hilite_node *nrl = n->right->left;
747 
748 	if (np != NULL)
749 	{
750 		if (n == np->left)
751 			np->left = nr;
752 		else
753 			np->right = nr;
754 	} else
755 	{
756 		anchor->root = nr;
757 	}
758 	nr->left = n;
759 	n->right = nrl;
760 
761 	nr->parent = np;
762 	n->parent = nr;
763 	if (nrl != NULL)
764 		nrl->parent = n;
765 }
766 
767 /*
768  * Rotate the tree right around a pivot node.
769  */
770 static void hlist_rotate_right(struct hilite_tree *anchor, struct hilite_node *n)
771 {
772 	struct hilite_node *np = n->parent;
773 	struct hilite_node *nl = n->left;
774 	struct hilite_node *nlr = n->left->right;
775 
776 	if (np != NULL)
777 	{
778 		if (n == np->right)
779 			np->right = nl;
780 		else
781 			np->left = nl;
782 	} else
783 	{
784 		anchor->root = nl;
785 	}
786 	nl->right = n;
787 	n->left = nlr;
788 
789 	nl->parent = np;
790 	n->parent = nl;
791 	if (nlr != NULL)
792 		nlr->parent = n;
793 }
794 
795 
796 /*
797  * Add a new hilite to a hilite list.
798  */
799 static void add_hilite(struct hilite_tree *anchor, struct hilite *hl)
800 {
801 	struct hilite_node *p, *n, *u;
802 
803 	/* Ignore empty ranges. */
804 	if (hl->hl_startpos >= hl->hl_endpos)
805 		return;
806 
807 	p = anchor->root;
808 
809 	/* Inserting the very first node is trivial. */
810 	if (p == NULL)
811 	{
812 		n = hlist_getnode(anchor);
813 		n->r = *hl;
814 		anchor->root = n;
815 		anchor->lookaside = n;
816 		return;
817 	}
818 
819 	/*
820 	 * Find our insertion point. If we come across any overlapping
821 	 * or adjoining existing ranges, shrink our range and discard
822 	 * if it become empty.
823 	 */
824 	for (;;)
825 	{
826 		if (hl->hl_startpos < p->r.hl_startpos)
827 		{
828 			if (hl->hl_endpos > p->r.hl_startpos && hl->hl_attr == p->r.hl_attr)
829 				hl->hl_endpos = p->r.hl_startpos;
830 			if (p->left != NULL)
831 			{
832 				p = p->left;
833 				continue;
834 			}
835 			break;
836 		}
837 		if (hl->hl_startpos < p->r.hl_endpos && hl->hl_attr == p->r.hl_attr) {
838 			hl->hl_startpos = p->r.hl_endpos;
839 			if (hl->hl_startpos >= hl->hl_endpos)
840 				return;
841 		}
842 		if (p->right != NULL)
843 		{
844 			p = p->right;
845 			continue;
846 		}
847 		break;
848 	}
849 
850 	/*
851 	 * Now we're at the right leaf, again check for contiguous ranges
852 	 * and extend the existing node if possible to avoid the
853 	 * insertion. Otherwise insert a new node at the leaf.
854 	 */
855 	if (hl->hl_startpos < p->r.hl_startpos) {
856 		if (hl->hl_attr == p->r.hl_attr)
857 		{
858 			if (hl->hl_endpos == p->r.hl_startpos)
859 			{
860 				p->r.hl_startpos = hl->hl_startpos;
861 				return;
862 			}
863 			if (p->prev != NULL && p->prev->r.hl_endpos == hl->hl_startpos)
864 			{
865 				p->prev->r.hl_endpos = hl->hl_endpos;
866 				return;
867 			}
868 		}
869 		p->left = n = hlist_getnode(anchor);
870 		n->next = p;
871 		if (p->prev != NULL)
872 		{
873 			n->prev = p->prev;
874 			p->prev->next = n;
875 		}
876 		p->prev = n;
877 	} else {
878 		if (hl->hl_attr == p->r.hl_attr)
879 		{
880 			if (p->r.hl_endpos == hl->hl_startpos)
881 			{
882 				p->r.hl_endpos = hl->hl_endpos;
883 				return;
884 			}
885 			if (p->next != NULL && hl->hl_endpos == p->next->r.hl_startpos) {
886 				p->next->r.hl_startpos = hl->hl_startpos;
887 				return;
888 			}
889 		}
890 		p->right = n = hlist_getnode(anchor);
891 		n->prev = p;
892 		if (p->next != NULL)
893 		{
894 			n->next = p->next;
895 			p->next->prev = n;
896 		}
897 		p->next = n;
898 	}
899 	n->parent = p;
900 	n->red = 1;
901 	n->r = *hl;
902 
903 	/*
904 	 * The tree is in the correct order and covers the right ranges
905 	 * now, but may have become unbalanced. Rebalance it using the
906 	 * standard red-black tree constraints and operations.
907 	 */
908 	for (;;)
909 	{
910 		/* case 1 - current is root, root is always black */
911 		if (n->parent == NULL)
912 		{
913 			n->red = 0;
914 			break;
915 		}
916 
917 		/* case 2 - parent is black, we can always be red */
918 		if (!n->parent->red)
919 			break;
920 
921 		/*
922 		 * constraint: because the root must be black, if our
923 		 * parent is red it cannot be the root therefore we must
924 		 * have a grandparent
925 		 */
926 
927 		/*
928 		 * case 3 - parent and uncle are red, repaint them black,
929 		 * the grandparent red, and start again at the grandparent.
930 		 */
931 		u = n->parent->parent->left;
932 		if (n->parent == u)
933 			u = n->parent->parent->right;
934 		if (u != NULL && u->red)
935 		{
936 			n->parent->red = 0;
937 			u->red = 0;
938 			n = n->parent->parent;
939 			n->red = 1;
940 			continue;
941 		}
942 
943 		/*
944 		 * case 4 - parent is red but uncle is black, parent and
945 		 * grandparent on opposite sides. We need to start
946 		 * changing the structure now. This and case 5 will shorten
947 		 * our branch and lengthen the sibling, between them
948 		 * restoring balance.
949 		 */
950 		if (n == n->parent->right &&
951 		    n->parent == n->parent->parent->left)
952 		{
953 			hlist_rotate_left(anchor, n->parent);
954 			n = n->left;
955 		} else if (n == n->parent->left &&
956 			   n->parent == n->parent->parent->right)
957 		{
958 			hlist_rotate_right(anchor, n->parent);
959 			n = n->right;
960 		}
961 
962 		/*
963 		 * case 5 - parent is red but uncle is black, parent and
964 		 * grandparent on same side
965 		 */
966 		n->parent->red = 0;
967 		n->parent->parent->red = 1;
968 		if (n == n->parent->left)
969 			hlist_rotate_right(anchor, n->parent->parent);
970 		else
971 			hlist_rotate_left(anchor, n->parent->parent);
972 		break;
973 	}
974 }
975 
976 /*
977  * Highlight every character in a range of displayed characters.
978  */
979 static void create_hilites(POSITION linepos, constant char *line, constant char *sp, constant char *ep, int attr, int *chpos)
980 {
981 	size_t start_index = ptr_diff(sp, line); /*{{type-issue}}*/
982 	size_t end_index = ptr_diff(ep, line);
983 	struct hilite hl;
984 	size_t i;
985 
986 	/* Start the first hilite. */
987 	hl.hl_startpos = linepos + chpos[start_index];
988 	hl.hl_attr = attr;
989 
990 	/*
991 	 * Step through the displayed chars.
992 	 * If the source position (before cvt) of the char is one more
993 	 * than the source pos of the previous char (the usual case),
994 	 * just increase the size of the current hilite by one.
995 	 * Otherwise (there are backspaces or something involved),
996 	 * finish the current hilite and start a new one.
997 	 */
998 	for (i = start_index+1;  i <= end_index;  i++)
999 	{
1000 		if (chpos[i] != chpos[i-1] + 1 || i == end_index)
1001 		{
1002 			hl.hl_endpos = linepos + chpos[i-1] + 1;
1003 			add_hilite(&hilite_anchor, &hl);
1004 			/* Start new hilite unless this is the last char. */
1005 			if (i < end_index)
1006 			{
1007 				hl.hl_startpos = linepos + chpos[i];
1008 			}
1009 		}
1010 	}
1011 }
1012 
1013 /*
1014  * Make a hilite for each string in a physical line which matches
1015  * the current pattern.
1016  * sp,ep delimit the first match already found.
1017  */
1018 static void hilite_line(POSITION linepos, constant char *line, size_t line_len, int *chpos, constant char **sp, constant char **ep, int nsp)
1019 {
1020 	constant char *searchp;
1021 	constant char *line_end = line + line_len;
1022 
1023 	/*
1024 	 * sp[0] and ep[0] delimit the first match in the line.
1025 	 * Mark the corresponding file positions, then
1026 	 * look for further matches and mark them.
1027 	 * {{ This technique, of calling match_pattern on subsequent
1028 	 *    substrings of the line, may mark more than is correct
1029 	 *    if the pattern starts with "^".  This bug is fixed
1030 	 *    for those regex functions that accept a notbol parameter
1031 	 *    (currently POSIX, PCRE and V8-with-regexec2). }}
1032 	 * sp[i] and ep[i] for i>0 delimit subpattern matches.
1033 	 * Color each of them with its unique color.
1034 	 */
1035 	searchp = line;
1036 	do {
1037 		constant char *lep = sp[0];
1038 		int i;
1039 		if (sp[0] == NULL || ep[0] == NULL)
1040 			break;
1041 		for (i = 1;  i < nsp;  i++)
1042 		{
1043 			if (sp[i] == NULL || ep[i] == NULL)
1044 				break;
1045 			if (ep[i] > sp[i])
1046 			{
1047 				create_hilites(linepos, line, lep, sp[i],
1048 					AT_HILITE | AT_COLOR_SEARCH, chpos);
1049 				create_hilites(linepos, line, sp[i], ep[i],
1050 					AT_HILITE | AT_COLOR_SUBSEARCH(i), chpos);
1051 				lep = ep[i];
1052 			}
1053 		}
1054 		create_hilites(linepos, line, lep, ep[0],
1055 			AT_HILITE | AT_COLOR_SEARCH, chpos);
1056 
1057 		/*
1058 		 * If we matched more than zero characters,
1059 		 * move to the first char after the string we matched.
1060 		 * If we matched zero, just move to the next char.
1061 		 */
1062 		if (ep[0] > searchp)
1063 			searchp = ep[0];
1064 		else if (searchp != line_end)
1065 			searchp++;
1066 		else /* end of line */
1067 			break;
1068 	} while (match_pattern(info_compiled(&search_info), search_info.text,
1069 			searchp, ptr_diff(line_end, searchp), sp, ep, nsp, 1, search_info.search_type));
1070 }
1071 #endif
1072 
1073 #if HILITE_SEARCH
1074 /*
1075  * Find matching text which is currently on screen and highlight it.
1076  */
1077 static void hilite_screen(void)
1078 {
1079 	struct scrpos scrpos;
1080 
1081 	get_scrpos(&scrpos, TOP);
1082 	if (scrpos.pos == NULL_POSITION)
1083 		return;
1084 	prep_hilite(scrpos.pos, position(BOTTOM_PLUS_ONE), -1);
1085 	repaint_hilite(TRUE);
1086 }
1087 
1088 /*
1089  * Change highlighting parameters.
1090  */
1091 public void chg_hilite(void)
1092 {
1093 	/*
1094 	 * Erase any highlights currently on screen.
1095 	 */
1096 	clr_hilite();
1097 	hide_hilite = FALSE;
1098 
1099 	if (hilite_search == OPT_ONPLUS)
1100 		/*
1101 		 * Display highlights.
1102 		 */
1103 		hilite_screen();
1104 }
1105 #endif
1106 
1107 /*
1108  * Figure out where to start a search.
1109  */
1110 static POSITION search_pos(int search_type)
1111 {
1112 	POSITION pos;
1113 	int sindex;
1114 
1115 	if (empty_screen())
1116 	{
1117 		/*
1118 		 * Start at the beginning (or end) of the file.
1119 		 * The empty_screen() case is mainly for
1120 		 * command line initiated searches;
1121 		 * for example, "+/xyz" on the command line.
1122 		 * Also for multi-file (SRCH_PAST_EOF) searches.
1123 		 */
1124 		if (search_type & SRCH_FORW)
1125 		{
1126 			pos = ch_zero();
1127 		} else
1128 		{
1129 			pos = ch_length();
1130 			if (pos == NULL_POSITION)
1131 			{
1132 				(void) ch_end_seek();
1133 				pos = ch_length();
1134 			}
1135 		}
1136 		sindex = 0;
1137 	} else
1138 	{
1139 		lbool add_one = FALSE;
1140 
1141 		if (how_search == OPT_ON)
1142 		{
1143 			/*
1144 			 * Search does not include current screen.
1145 			 */
1146 			if (search_type & SRCH_FORW)
1147 				sindex = sc_height-1; /* BOTTOM_PLUS_ONE */
1148 			else
1149 				sindex = 0; /* TOP */
1150 		} else if (how_search == OPT_ONPLUS && !(search_type & SRCH_AFTER_TARGET))
1151 		{
1152 			/*
1153 			 * Search includes all of displayed screen.
1154 			 */
1155 			if (search_type & SRCH_FORW)
1156 				sindex = 0; /* TOP */
1157 			else
1158 				sindex = sc_height-1; /* BOTTOM_PLUS_ONE */
1159 		} else
1160 		{
1161 			/*
1162 			 * Search includes the part of current screen beyond the jump target.
1163 			 * It starts at the jump target (if searching backwards),
1164 			 * or at the jump target plus one (if forwards).
1165 			 */
1166 			sindex = sindex_from_sline(jump_sline);
1167 			if (search_type & SRCH_FORW)
1168 				add_one = TRUE;
1169 		}
1170 		pos = position(sindex);
1171 		if (add_one)
1172 			pos = forw_raw_line(pos, NULL, NULL);
1173 	}
1174 
1175 	/*
1176 	 * If the line is empty, look around for a plausible starting place.
1177 	 */
1178 	if (search_type & SRCH_FORW)
1179 	{
1180 		while (pos == NULL_POSITION)
1181 		{
1182 			if (++sindex >= sc_height)
1183 				break;
1184 			pos = position(sindex);
1185 		}
1186 	} else
1187 	{
1188 		while (pos == NULL_POSITION)
1189 		{
1190 			if (--sindex < 0)
1191 				break;
1192 			pos = position(sindex);
1193 		}
1194 	}
1195 	return (pos);
1196 }
1197 
1198 /*
1199  * Check to see if the line matches the filter pattern.
1200  * If so, add an entry to the filter list.
1201  */
1202 #if HILITE_SEARCH
1203 static int matches_filters(POSITION pos, char *cline, size_t line_len, int *chpos, POSITION linepos, constant char **sp, constant char **ep, int nsp)
1204 {
1205 	struct pattern_info *filter;
1206 
1207 	for (filter = filter_infos; filter != NULL; filter = filter->next)
1208 	{
1209 		int line_filter = match_pattern(info_compiled(filter), filter->text,
1210 			cline, line_len, sp, ep, nsp, 0, filter->search_type);
1211 		if (line_filter)
1212 		{
1213 			struct hilite hl;
1214 			hl.hl_startpos = linepos;
1215 			hl.hl_endpos = pos;
1216 			add_hilite(&filter_anchor, &hl);
1217 			free(cline);
1218 			free(chpos);
1219 			return (1);
1220 		}
1221 	}
1222 	return (0);
1223 }
1224 #endif
1225 
1226 /*
1227  * Get the position of the first char in the screen line which
1228  * puts tpos on screen.
1229  */
1230 static POSITION get_lastlinepos(POSITION pos, POSITION tpos, int sheight)
1231 {
1232 	int nlines;
1233 
1234 	for (nlines = 0;;  nlines++)
1235 	{
1236 		POSITION npos = forw_line(pos);
1237 		if (npos > tpos)
1238 		{
1239 			if (nlines < sheight)
1240 				return NULL_POSITION;
1241 			return pos;
1242 		}
1243 		pos = npos;
1244 	}
1245 }
1246 
1247 #if OSC8_LINK
1248 
1249 /*
1250  * osc8_parse_info points to the component fields in a parsed OSC8 sequence.
1251  */
1252 struct osc8_parse_info {
1253 	constant char *osc8_start;
1254 	constant char *osc8_end;
1255 	constant char *params_start;
1256 	constant char *params_end;
1257 	constant char *uri_start;
1258 	constant char *uri_end;
1259 };
1260 
1261 /*
1262  * Parse an OSC8 sequence in a string.
1263  */
1264 static lbool osc8_parse(constant char *line, constant char *line_end, struct osc8_parse_info *pop)
1265 {
1266 	constant char *oline;
1267 	pop->osc8_start = pop->osc8_end = pop->uri_start = pop->uri_end = pop->params_start = pop->params_end = NULL;
1268 
1269 	oline = line;
1270 	LWCHAR ch = step_charc(&line, +1, line_end);
1271 	/* oline points to character ch, line points to the one after it. */
1272 	struct ansi_state *pansi = ansi_start(ch);
1273 	if (pansi == NULL)
1274 		return FALSE;
1275 	pop->osc8_start = oline; /* start at the ESC */
1276 	for (;;)
1277 	{
1278 		ansi_state astate = ansi_step(pansi, ch);
1279 		osc8_state ostate = ansi_osc8_state(pansi);
1280 		if (ostate == OSC8_NOT)
1281 			break;
1282 		switch (ostate)
1283 		{
1284 		case OSC8_PARAMS:
1285 			if (pop->params_start == NULL)
1286 				pop->params_start = line;
1287 			break;
1288 		case OSC8_URI:
1289 			if (pop->uri_start == NULL)
1290 			{
1291 				pop->params_end = oline;
1292 				pop->uri_start = line;
1293 			}
1294 			break;
1295 		case OSC8_ST_ESC:
1296 			if (pop->uri_end == NULL)
1297 				pop->uri_end = oline;
1298 			break;
1299 		case OSC8_END:
1300 			ansi_done(pansi);
1301 			pop->osc8_end = line;
1302 			if (pop->uri_end == NULL) /* happens when ST is "\7" */
1303 				pop->uri_end = oline;
1304 			if (pop->params_end == NULL) /* should not happen */
1305 				pop->params_end = oline;
1306 			return TRUE;
1307 		default:
1308 			break;
1309 		}
1310 		if (astate != ANSI_MID || line >= line_end)
1311 			break;
1312 		oline = line;
1313 		ch = step_charc(&line, +1, line_end);
1314 	}
1315 	ansi_done(pansi);
1316 	return FALSE;
1317 }
1318 
1319 /*
1320  * Does an OSC8 sequence contain a specified parameter?
1321  */
1322 static lbool osc8_param_match(POSITION linepos, constant char *line, constant struct osc8_parse_info *op1, constant struct osc8_parse_info *op2, constant char *param, POSITION clickpos)
1323 {
1324 	size_t param_len;
1325 	constant char *p;
1326 
1327 	if (clickpos != NULL_POSITION)
1328 	{
1329 		return clickpos >= linepos + ptr_diff(op1->osc8_start, line) &&
1330 		       clickpos < linepos + ptr_diff(op2->osc8_end, line);
1331 	}
1332 	if (param == NULL)
1333 		return TRUE;
1334 	param_len = strlen(param);
1335 	/* Parameters are separated by colons. */
1336 	for (p = op1->params_start;  p + param_len <= op1->params_end; )
1337 	{
1338 		if (strncmp(p, param, param_len) == 0)
1339 		{
1340 		    p += param_len;
1341 			if (p == op1->params_end || *p == ':')
1342 				return TRUE;
1343 		}
1344 		while (p < op1->params_end && *p != ':')
1345 			++p;
1346 		while (p < op1->params_end && *p == ':')
1347 			++p;
1348 	}
1349 	return FALSE;
1350 }
1351 
1352 /*
1353  * Is the URI in an OSC8 sequence empty?
1354  * "Empty" means zero length, or equal to "#".
1355  */
1356 static lbool osc8_empty_uri(constant struct osc8_parse_info *op)
1357 {
1358 	return op->uri_end == op->uri_start ||
1359 	       (op->uri_end == op->uri_start+1 && op->uri_start[0] == '#');
1360 }
1361 
1362 /*
1363  * Find the next OSC8 hyperlink in a line.
1364  * A hyperlink is two OSC8 sequences (the first with a nonempty URI)
1365  * plus the non-empty text between them.
1366  * But if searching for a parameter, allow URI and/or text to be empty.
1367  */
1368 typedef enum { OSC8_NO_MATCH, OSC8_MATCH, OSC8_ALREADY } osc8_match;
1369 
1370 static osc8_match osc8_search_line1(int search_type, POSITION linepos, POSITION spos, constant char *line, size_t line_len, constant char *param, POSITION clickpos)
1371 {
1372 	constant char *line_end = &line[line_len];
1373 	struct osc8_parse_info op1;
1374 	struct osc8_parse_info op2;
1375 	constant char *linep;
1376 	constant size_t min_osc8_size = 6; /* "\e]8;;\7" */
1377 
1378 	if (search_type & SRCH_FORW)
1379 	{
1380 		for (linep = line; ; linep++)
1381 		{
1382 			if (linep + min_osc8_size > line_end)
1383 				return OSC8_NO_MATCH;
1384 			/* Find the first OSC8 sequence in the line with a nonempty URI,
1385 			 * which begins the hypertext. */
1386 			if (osc8_parse(linep, line_end, &op1) &&
1387 			    (!osc8_empty_uri(&op1) || param != NULL))
1388 			{
1389 				/* Now find the next OSC8 sequence, which ends the hypertext. */
1390 				constant char *linep2;
1391 				for (linep2 = op1.osc8_end; linep2 < line_end; linep2++)
1392 				{
1393 					if (osc8_parse(linep2, line_end, &op2))
1394 						break;
1395 				}
1396 				if (linep2 == line_end)
1397 					op2.osc8_end = op2.osc8_start = line_end;
1398 				if ((op2.osc8_start > op1.osc8_end || param != NULL) &&
1399 				    osc8_param_match(linepos, line, &op1, &op2, param, clickpos))
1400 					break;
1401 			}
1402 		}
1403 	} else
1404 	{
1405 		op2.osc8_end = op2.osc8_start = line_end;
1406 		for (linep = line_end - min_osc8_size; ; linep--)
1407 		{
1408 			if (linep < line)
1409 				return OSC8_NO_MATCH;
1410 			if (osc8_parse(linep, line_end, &op1))
1411 			{
1412 				if (((!osc8_empty_uri(&op1) && op2.osc8_start > op1.osc8_end) || param != NULL) &&
1413 				    osc8_param_match(linepos, line, &op1, &op2, param, clickpos))
1414 					break;
1415 				op2 = op1;
1416 			}
1417 		}
1418 	}
1419 	if (param != NULL)
1420 		/* Don't set osc8 globals if we're just searching for a parameter. */
1421 		return OSC8_MATCH;
1422 
1423 	if (osc8_linepos == linepos && osc8_match_start == spos + ptr_diff(op1.osc8_start, line))
1424 		return OSC8_ALREADY; /* already selected */
1425 
1426 	osc8_linepos = linepos;
1427 	osc8_match_start  = spos + ptr_diff(op1.osc8_start,   line);
1428 	osc8_match_end    = spos + ptr_diff(op2.osc8_start,   line);
1429 	osc8_params_start = spos + ptr_diff(op1.params_start, line);
1430 	osc8_params_end   = spos + ptr_diff(op1.params_end,   line);
1431 	osc8_uri_start    = spos + ptr_diff(op1.uri_start,    line);
1432 	osc8_uri_end      = spos + ptr_diff(op1.uri_end,      line);
1433 	osc8_text_start   = spos + ptr_diff(op1.osc8_end,     line);
1434 	osc8_text_end     = spos + ptr_diff(op2.osc8_start,   line);
1435 
1436 	/* Save URI for message in prompt(). */
1437 	osc8_uri = saven(op1.uri_start, ptr_diff(op1.uri_end, op1.uri_start));
1438 	return OSC8_MATCH;
1439 }
1440 
1441 /*
1442  * Find the N-th OSC8 hyperlink in a line.
1443  */
1444 static osc8_match osc8_search_line(int search_type, POSITION linepos, constant char *line, size_t line_len, constant char *param, POSITION clickpos, int *matches)
1445 {
1446 	while (*matches > 0)
1447 	{
1448 		POSITION spos = linepos;
1449 		constant char *sline = line;
1450 		size_t sline_len = line_len;
1451 		osc8_match r;
1452 		if (linepos == osc8_linepos && clickpos == NULL_POSITION)
1453 		{
1454 			/*
1455 			 * Already have a hyperlink selected.
1456 			 * Search for the next/previous one in the same line.
1457 			 */
1458 			if (search_type & SRCH_FORW)
1459 			{
1460 				size_t off = (size_t) (osc8_match_end - linepos);
1461 				spos += off;
1462 				sline += off;
1463 				sline_len -= off;
1464 			} else
1465 			{
1466 				sline_len = (size_t) (osc8_match_start - linepos);
1467 			}
1468 		}
1469 		r = osc8_search_line1(search_type, linepos, spos, sline, sline_len, param, clickpos);
1470 		if (r == OSC8_NO_MATCH)
1471 			break;
1472 		if (--*matches <= 0)
1473 			return r;
1474 	}
1475 	return OSC8_NO_MATCH;
1476 }
1477 
1478 /*
1479  * Shift display to make the currently selected OSC8 hyperlink visible.
1480  */
1481 static void osc8_shift_visible(void)
1482 {
1483 	if (chop_line())
1484 	{
1485 		size_t start_off = (size_t)(osc8_match_start - osc8_linepos);
1486 		size_t end_off = (size_t)(osc8_match_end - osc8_linepos);
1487 		shift_visible(osc8_linepos, start_off, end_off);
1488 	}
1489 	/* {{ What about the (plastlinepos != NULL) case in search_range? }} */
1490 }
1491 
1492 #endif /* OSC8_LINK */
1493 
1494 /*
1495  * Search a subset of the file, specified by start/end position.
1496  */
1497 static int search_range(POSITION pos, POSITION endpos, int search_type, int matches, int maxlines, POSITION *plinepos, POSITION *pendpos, POSITION *plastlinepos)
1498 {
1499 	constant char *line;
1500 	char *cline;
1501 	size_t line_len;
1502 	LINENUM linenum;
1503 	#define NSP (NUM_SEARCH_COLORS+2)
1504 	constant char *sp[NSP];
1505 	constant char *ep[NSP];
1506 	int line_match;
1507 	int cvt_ops;
1508 	size_t cvt_len;
1509 	int *chpos;
1510 	POSITION linepos, oldpos;
1511 	int skip_bytes = 0;
1512 	size_t swidth = (size_t) (sc_width - line_pfx_width()); /*{{type-issue}}*/
1513 	size_t sheight = (size_t) (sc_height - sindex_from_sline(jump_sline));
1514 
1515 	linenum = find_linenum(pos);
1516 	if (nosearch_header_lines && linenum <= header_lines)
1517 	{
1518 		linenum = header_lines + 1;
1519 		pos = find_pos(linenum);
1520 	}
1521 	if (pos == NULL_POSITION)
1522 		return (-1);
1523 	oldpos = pos;
1524 	/* When the search wraps around, end at starting position. */
1525 	if ((search_type & SRCH_WRAP) && endpos == NULL_POSITION)
1526 		endpos = pos;
1527 	for (;;)
1528 	{
1529 		/*
1530 		 * Get lines until we find a matching one or until
1531 		 * we hit end-of-file (or beginning-of-file if we're
1532 		 * going backwards), or until we hit the end position.
1533 		 */
1534 		if (ABORT_SIGS())
1535 		{
1536 			/*
1537 			 * A signal aborts the search.
1538 			 */
1539 			return (-1);
1540 		}
1541 
1542 		if ((endpos != NULL_POSITION && !(search_type & SRCH_WRAP) &&
1543 			(((search_type & SRCH_FORW) && pos >= endpos) ||
1544 			 ((search_type & SRCH_BACK) && pos <= endpos))) || maxlines == 0)
1545 		{
1546 			/*
1547 			 * Reached end position without a match.
1548 			 */
1549 			if (pendpos != NULL)
1550 				*pendpos = pos;
1551 			return (matches);
1552 		}
1553 		if (maxlines > 0)
1554 			maxlines--;
1555 
1556 		if (search_type & SRCH_FORW)
1557 		{
1558 			/*
1559 			 * Read the next line, and save the
1560 			 * starting position of that line in linepos.
1561 			 */
1562 			linepos = pos;
1563 			pos = forw_raw_line(pos, &line, &line_len);
1564 			if (linenum != 0)
1565 				linenum++;
1566 		} else
1567 		{
1568 			/*
1569 			 * Read the previous line and save the
1570 			 * starting position of that line in linepos.
1571 			 */
1572 			pos = back_raw_line(pos, &line, &line_len);
1573 			linepos = pos;
1574 			if (linenum != 0)
1575 				linenum--;
1576 		}
1577 
1578 		if (pos == NULL_POSITION)
1579 		{
1580 			/*
1581 			 * Reached EOF/BOF without a match.
1582 			 */
1583 			if (search_type & SRCH_WRAP)
1584 			{
1585 				/*
1586 				 * The search wraps around the current file, so
1587 				 * try to continue at BOF/EOF.
1588 				 */
1589 				if (search_type & SRCH_FORW)
1590 				{
1591 					pos = ch_zero();
1592 				} else
1593 				{
1594 					pos = ch_length();
1595 					if (pos == NULL_POSITION)
1596 					{
1597 						(void) ch_end_seek();
1598 						pos = ch_length();
1599 					}
1600 				}
1601 				if (pos != NULL_POSITION) {
1602 					/*
1603 					 * Wrap-around was successful. Clear
1604 					 * the flag so we don't wrap again, and
1605 					 * continue the search at new pos.
1606 					 */
1607 					search_wrapped = TRUE;
1608 					search_type &= ~SRCH_WRAP;
1609 					linenum = find_linenum(pos);
1610 					continue;
1611 				}
1612 			}
1613 			if (pendpos != NULL)
1614 				*pendpos = oldpos;
1615 			return (matches);
1616 		}
1617 
1618 		/*
1619 		 * If we're using line numbers, we might as well
1620 		 * remember the information we have now (the position
1621 		 * and line number of the current line).
1622 		 * Don't do it for every line because it slows down
1623 		 * the search.  Remember the line number only if
1624 		 * we're "far" from the last place we remembered it.
1625 		 */
1626 		if (linenums && abs((int)(pos - oldpos)) > 2048)
1627 			add_lnum(linenum, pos);
1628 		oldpos = pos;
1629 
1630 #if HILITE_SEARCH
1631 		if (is_filtered(linepos))
1632 			continue;
1633 #endif
1634 		if (nosearch_header_cols)
1635 			skip_bytes = skip_columns(header_cols, &line, &line_len);
1636 #if OSC8_LINK
1637 		if (search_type & SRCH_OSC8)
1638 		{
1639 			if (osc8_search_line(search_type, linepos, line, line_len, osc8_search_param, NULL_POSITION, &matches) != OSC8_NO_MATCH)
1640 			{
1641 				if (plinepos != NULL)
1642 					*plinepos = linepos;
1643 				osc8_shift_visible();
1644 				return (0);
1645 			}
1646 			continue;
1647 		}
1648 #endif
1649 		/*
1650 		 * If it's a caseless search, convert the line to lowercase.
1651 		 * If we're doing backspace processing, delete backspaces.
1652 		 */
1653 		cvt_ops = get_cvt_ops(search_type);
1654 		cvt_len = cvt_length(line_len, cvt_ops);
1655 		cline = (char *) ecalloc(1, cvt_len);
1656 		chpos = cvt_alloc_chpos(cvt_len);
1657 		cvt_text(cline, line, chpos, &line_len, cvt_ops);
1658 
1659 #if HILITE_SEARCH
1660 		/*
1661 		 * If any filters are in effect, ignore non-matching lines.
1662 		 */
1663 		if (filter_infos != NULL &&
1664 		   ((search_type & SRCH_FIND_ALL) ||
1665 		     prep_startpos == NULL_POSITION ||
1666 		     linepos < prep_startpos || linepos >= prep_endpos)) {
1667 			if (matches_filters(pos, cline, line_len, chpos, linepos, sp, ep, NSP))
1668 				continue;
1669 		}
1670 #endif
1671 
1672 		/*
1673 		 * Test the next line to see if we have a match.
1674 		 * We are successful if we either want a match and got one,
1675 		 * or if we want a non-match and got one.
1676 		 */
1677 		if (prev_pattern(&search_info))
1678 		{
1679 			line_match = match_pattern(info_compiled(&search_info), search_info.text,
1680 				cline, line_len, sp, ep, NSP, 0, search_type);
1681 			if (line_match)
1682 			{
1683 				/*
1684 				 * Got a match.
1685 				 */
1686 				if (search_type & SRCH_FIND_ALL)
1687 				{
1688 #if HILITE_SEARCH
1689 					/*
1690 					 * We are supposed to find all matches in the range.
1691 					 * Just add the matches in this line to the
1692 					 * hilite list and keep searching.
1693 					 */
1694 					hilite_line(linepos + skip_bytes, cline, line_len, chpos, sp, ep, NSP);
1695 #endif
1696 				} else if (--matches <= 0)
1697 				{
1698 					/*
1699 					 * Found the one match we're looking for.
1700 					 * Return it.
1701 					 */
1702 #if HILITE_SEARCH
1703 					if (hilite_search == OPT_ON)
1704 					{
1705 						/*
1706 						 * Clear the hilite list and add only
1707 						 * the matches in this one line.
1708 						 */
1709 						clr_hilite();
1710 						hilite_line(linepos + skip_bytes, cline, line_len, chpos, sp, ep, NSP);
1711 					}
1712 #endif
1713 					if (chop_line())
1714 					{
1715 						/*
1716 						 * If necessary, shift horizontally to make sure
1717 						 * search match is fully visible.
1718 						 */
1719 						if (sp[0] != NULL && ep[0] != NULL)
1720 						{
1721 							size_t start_off = ptr_diff(sp[0], cline);
1722 							size_t end_off = ptr_diff(ep[0], cline);
1723 							shift_visible(linepos, chpos[start_off], chpos[end_off]);
1724 						}
1725 					} else if (plastlinepos != NULL)
1726 					{
1727 						/*
1728 						 * If the line is so long that the highlighted match
1729 						 * won't be seen when the line is displayed normally
1730 						 * (starting at the first char) because it fills the whole
1731 						 * screen and more, scroll forward until the last char
1732 						 * of the match appears in the last line on the screen.
1733 						 * lastlinepos is the position of the first char of that last line.
1734 						 */
1735 						if (ep[0] != NULL)
1736 						{
1737 							size_t end_off = ptr_diff(ep[0], cline);
1738 							if (end_off >= swidth * sheight / 4) /* heuristic */
1739 								*plastlinepos = get_lastlinepos(linepos, linepos + chpos[end_off], (int) sheight);
1740 						}
1741 					}
1742 					free(cline);
1743 					free(chpos);
1744 					if (plinepos != NULL)
1745 						*plinepos = linepos;
1746 					return (0);
1747 				}
1748 			}
1749 		}
1750 		free(cline);
1751 		free(chpos);
1752 	}
1753 }
1754 
1755 #if OSC8_LINK
1756 
1757 /*
1758  * Search for and select the next OSC8 sequence, forward or backward.
1759  */
1760 public void osc8_search(int search_type, constant char *param, int matches)
1761 {
1762 	POSITION pos;
1763 	int match;
1764 
1765 	if (osc8_linepos != NULL_POSITION)
1766 	{
1767 		/* Continue search in same line as current match. */
1768 		constant char *line;
1769 		size_t line_len;
1770 		pos = forw_raw_line(osc8_linepos, &line, &line_len);
1771 		if (pos != NULL_POSITION)
1772 		{
1773 			if (osc8_search_line(search_type, osc8_linepos, line, line_len, param, NULL_POSITION, &matches) != OSC8_NO_MATCH)
1774 			{
1775 				no_eof_bell = TRUE;
1776 				jump_loc(osc8_linepos, jump_sline);
1777 				no_eof_bell = FALSE;
1778 				osc8_shift_visible();
1779 #if HILITE_SEARCH
1780 				repaint_hilite(TRUE);
1781 #endif
1782 				return;
1783 			}
1784 		}
1785 		search_type |= SRCH_AFTER_TARGET;
1786 	}
1787 	pos = search_pos(search_type);
1788 	if (pos == NULL_POSITION)
1789 	{
1790 		error("Nothing to search", NULL_PARG);
1791 		return;
1792 	}
1793 	osc8_search_param = param;
1794 	match = search_range(pos, NULL_POSITION, search_type | SRCH_OSC8, matches, -1, &pos, NULL, NULL);
1795 	osc8_search_param = NULL;
1796 	if (match != 0)
1797 	{
1798 		error("OSC 8 link not found", NULL_PARG);
1799 		return;
1800 	}
1801 	jump_loc(pos, jump_sline);
1802 #if HILITE_SEARCH
1803 	repaint_hilite(TRUE);
1804 #endif
1805 }
1806 
1807 /*
1808  * If a mouse click is on an OSC 8 link, select the link.
1809  */
1810 public lbool osc8_click(int sindex, int col)
1811 {
1812 #if OSC8_LINK
1813 	POSITION linepos = position(sindex);
1814 	POSITION clickpos;
1815 	constant char *line;
1816 	size_t line_len;
1817 	int matches = 1;
1818 	int r;
1819 
1820 	if (linepos == NULL_POSITION)
1821 		return FALSE;
1822 	clickpos = pos_from_col(linepos, col, NULL_POSITION, -1);
1823 	if (clickpos == NULL_POSITION)
1824 		return FALSE;
1825 	if (forw_raw_line(linepos, &line, &line_len) == NULL_POSITION)
1826 		return FALSE;
1827 	r = osc8_search_line(SRCH_FORW|SRCH_OSC8, linepos, line, line_len, NULL, clickpos, &matches);
1828 	if (r != OSC8_NO_MATCH)
1829 	{
1830 #if HILITE_SEARCH
1831 		repaint_hilite(TRUE);
1832 #endif
1833 		if (r == OSC8_ALREADY)
1834 			osc8_open();
1835 		return TRUE;
1836 	}
1837 #else
1838 	(void) sindex; (void) col;
1839 #endif /* OSC8_LINK */
1840 	return FALSE;
1841 }
1842 
1843 /*
1844  * Return the length of the scheme prefix in a URI.
1845  */
1846 static int scheme_length(constant char *uri, size_t uri_len)
1847 {
1848 	size_t plen;
1849 	for (plen = 0;  plen < uri_len;  plen++)
1850 		if (uri[plen] == ':')
1851 			return plen;
1852 	return 0;
1853 }
1854 
1855 /*
1856  * Does a URI contain any dangerous characters?
1857  */
1858 static lbool bad_uri(constant char *uri, size_t uri_len)
1859 {
1860 	size_t i;
1861 	for (i = 0;  i < uri_len;  i++)
1862 		if (strchr("'\"", uri[i]) != NULL)
1863 			return TRUE;
1864 	return FALSE;
1865 }
1866 
1867 /*
1868  * Re-read the line containing the selected OSC8 link.
1869  */
1870 static lbool osc8_read_selected(struct osc8_parse_info *op)
1871 {
1872 	constant char *line;
1873 	size_t line_len;
1874 	POSITION pos;
1875 
1876 	pos = forw_raw_line(osc8_linepos, &line, &line_len);
1877 	if (pos == NULL_POSITION)
1878 		return FALSE;
1879 	op->osc8_start    = &line[osc8_match_start - osc8_linepos];
1880 	op->osc8_end      = &line[osc8_match_end - osc8_linepos];
1881 	op->params_start  = &line[osc8_params_start - osc8_linepos];
1882 	op->params_end    = &line[osc8_params_end - osc8_linepos];
1883 	op->uri_start     = &line[osc8_uri_start - osc8_linepos];
1884 	op->uri_end       = &line[osc8_uri_end - osc8_linepos];
1885 	return TRUE;
1886 }
1887 
1888 /*
1889  * Open the currently selected OSC8 link.
1890  */
1891 public void osc8_open(void)
1892 {
1893 	struct osc8_parse_info op;
1894 	char env_name[64];
1895 	size_t scheme_len;
1896 	constant char *handler;
1897 	char *open_cmd;
1898 	size_t uri_len;
1899 	FILE *hf;
1900 	static constant char *env_name_pfx = "LESS_OSC8_";
1901 
1902 	if (osc8_linepos == NULL_POSITION)
1903 	{
1904 		error("No OSC8 link selected", NULL_PARG);
1905 		return;
1906 	}
1907 	if (!osc8_read_selected(&op))
1908 	{
1909 		error("Cannot find OSC8 link", NULL_PARG);
1910 		return;
1911 	}
1912 	/*
1913 	 * Read a "handler" shell cmd from environment variable "LESS_OSC8_scheme".
1914 	 * pr_expand the handler cmd (to expand %o -> osc8_path) and execute it.
1915 	 * Handler's stdout is an "opener" shell cmd; execute opener to open the link.
1916 	 */
1917 	uri_len = ptr_diff(op.uri_end, op.uri_start);
1918 	scheme_len = scheme_length(op.uri_start, uri_len);
1919 	if (scheme_len == 0 && op.uri_start[0] == '#')
1920 	{
1921 		/* Link to "id=" in same file. */
1922 		char *param = ecalloc(uri_len+3, sizeof(char));
1923 		strcpy(param, "id=");
1924 		strncpy(param+3, op.uri_start+1, uri_len-1);
1925 		param[uri_len+2] = '\0';
1926 		osc8_search(SRCH_FORW|SRCH_WRAP, param, 1);
1927 		free(param);
1928 		return;
1929 	}
1930 #if HAVE_POPEN
1931 	if (bad_uri(op.uri_start, uri_len))
1932 	{
1933 		error("Cannot open link containing dangerous characters", NULL_PARG);
1934 		return;
1935 	}
1936 	SNPRINTF3(env_name, sizeof(env_name), "%s%.*s", env_name_pfx, (int) scheme_len, op.uri_start);
1937 	handler = lgetenv(env_name);
1938 	if (isnullenv(handler) || strcmp(handler, "-") == 0)
1939         handler = lgetenv("LESS_OSC8_ANY");
1940 	if (isnullenv(handler))
1941 	{
1942 		PARG parg;
1943 		parg.p_string = env_name + strlen(env_name_pfx); /* {{ tricky }} */
1944 		error("No handler for \"%s\" link type", &parg);
1945 		return;
1946 	}
1947 	/* {{ ugly global osc8_path }} */
1948 	osc8_path = saven(op.uri_start, uri_len);
1949 	hf = popen(pr_expand(handler), "r");
1950 	free(osc8_path);
1951 	osc8_path = NULL;
1952 	if (hf == NULL)
1953 	{
1954 		PARG parg;
1955 		parg.p_string = env_name;
1956 		error("Cannot execute protocol handler in %s", &parg);
1957 		return;
1958 	}
1959 	open_cmd = readfd(hf);
1960 	pclose(hf);
1961 	if (strncmp(open_cmd, ":e", 2) == 0)
1962 	{
1963 		edit(skipsp(&open_cmd[2]));
1964 	} else
1965 	{
1966 		lsystem(open_cmd, "link done");
1967 	}
1968 	free(open_cmd);
1969 #else
1970 	error("Cannot open link because your system does not support popen", NULL_PARG);
1971 #endif /* HAVE_POPEN */
1972 }
1973 
1974 /*
1975  * Jump to the currently selected OSC8 link.
1976  */
1977 public void osc8_jump(void)
1978 {
1979 	if (osc8_linepos == NULL_POSITION)
1980 	{
1981 		error("No OSC8 link selected", NULL_PARG);
1982 		return;
1983 	}
1984 	jump_loc(osc8_linepos, jump_sline);
1985 }
1986 
1987 #endif /* OSC8_LINK */
1988 
1989 /*
1990  * search for a pattern in history. If found, compile that pattern.
1991  */
1992 static int hist_pattern(int search_type)
1993 {
1994 #if CMD_HISTORY
1995 	constant char *pattern;
1996 
1997 	set_mlist(ml_search, 0);
1998 	pattern = cmd_lastpattern();
1999 	if (pattern == NULL)
2000 		return (0);
2001 
2002 	if (set_pattern(&search_info, pattern, search_type, 1) < 0)
2003 		return (-1);
2004 
2005 #if HILITE_SEARCH
2006 	if (hilite_search == OPT_ONPLUS && !hide_hilite)
2007 		hilite_screen();
2008 #endif
2009 
2010 	return (1);
2011 #else /* CMD_HISTORY */
2012 	return (0);
2013 #endif /* CMD_HISTORY */
2014 }
2015 
2016 /*
2017  * Change the caseless-ness of searches.
2018  * Updates the internal search state to reflect a change in the -i flag.
2019  */
2020 public void chg_caseless(void)
2021 {
2022 	if (!search_info.is_ucase_pattern)
2023 	{
2024 		/*
2025 		 * Pattern did not have uppercase.
2026 		 * Set the search caselessness to the global caselessness.
2027 		 */
2028 		is_caseless = caseless;
2029 		/*
2030 		 * If regex handles caseless, we need to discard
2031 		 * the pattern which was compiled with the old caseless.
2032 		 */
2033 		if (!re_handles_caseless)
2034 			/* We handle caseless, so the pattern doesn't change. */
2035 			return;
2036 	}
2037 	/*
2038 	 * Regenerate the pattern using the new state.
2039 	 */
2040 	clear_pattern(&search_info);
2041 	(void) hist_pattern(search_info.search_type);
2042 }
2043 
2044 /*
2045  * Search for the n-th occurrence of a specified pattern,
2046  * either forward or backward.
2047  * Return the number of matches not yet found in this file
2048  * (that is, n minus the number of matches found).
2049  * Return -1 if the search should be aborted.
2050  * Caller may continue the search in another file
2051  * if less than n matches are found in this file.
2052  */
2053 public int search(int search_type, constant char *pattern, int n)
2054 {
2055 	POSITION pos;
2056 	POSITION opos;
2057 	POSITION lastlinepos = NULL_POSITION;
2058 
2059 	if (pattern == NULL || *pattern == '\0')
2060 	{
2061 		/*
2062 		 * A null pattern means use the previously compiled pattern.
2063 		 */
2064 		search_type |= SRCH_AFTER_TARGET;
2065 		if (!prev_pattern(&search_info))
2066 		{
2067 			int r = hist_pattern(search_type);
2068 			if (r == 0)
2069 				error("No previous regular expression", NULL_PARG);
2070 			if (r <= 0)
2071 				return (-1);
2072 		}
2073 		if ((search_type & SRCH_NO_REGEX) !=
2074 		      (search_info.search_type & SRCH_NO_REGEX))
2075 		{
2076 			error("Please re-enter search pattern", NULL_PARG);
2077 			return -1;
2078 		}
2079 #if HILITE_SEARCH
2080 		if (hilite_search == OPT_ON || status_col)
2081 		{
2082 			/*
2083 			 * Erase the highlights currently on screen.
2084 			 * If the search fails, we'll redisplay them later.
2085 			 */
2086 			repaint_hilite(FALSE);
2087 		}
2088 		if (hilite_search == OPT_ONPLUS && hide_hilite)
2089 		{
2090 			/*
2091 			 * Highlight any matches currently on screen,
2092 			 * before we actually start the search.
2093 			 */
2094 			hide_hilite = FALSE;
2095 			hilite_screen();
2096 		}
2097 		hide_hilite = FALSE;
2098 #endif
2099 	} else
2100 	{
2101 		/*
2102 		 * Compile the pattern.
2103 		 */
2104 		int show_error = !(search_type & SRCH_INCR);
2105 		if (set_pattern(&search_info, pattern, search_type, show_error) < 0)
2106 			return (-1);
2107 #if HILITE_SEARCH
2108 		if (hilite_search || status_col)
2109 		{
2110 			/*
2111 			 * Erase the highlights currently on screen.
2112 			 * Also permanently delete them from the hilite list.
2113 			 */
2114 			repaint_hilite(FALSE);
2115 			hide_hilite = FALSE;
2116 			clr_hilite();
2117 		}
2118 		if (hilite_search == OPT_ONPLUS || status_col)
2119 		{
2120 			/*
2121 			 * Highlight any matches currently on screen,
2122 			 * before we actually start the search.
2123 			 */
2124 			hilite_screen();
2125 		}
2126 #endif
2127 	}
2128 
2129 	/*
2130 	 * Figure out where to start the search.
2131 	 */
2132 	pos = search_pos(search_type);
2133 	opos = position(sindex_from_sline(jump_sline));
2134 	if (pos == NULL_POSITION)
2135 	{
2136 		/*
2137 		 * Can't find anyplace to start searching from.
2138 		 */
2139 		if (search_type & SRCH_PAST_EOF)
2140 			return (n);
2141 #if HILITE_SEARCH
2142 		if (hilite_search == OPT_ON || status_col)
2143 			repaint_hilite(TRUE);
2144 #endif
2145 		error("Nothing to search", NULL_PARG);
2146 		return (-1);
2147 	}
2148 
2149 	n = search_range(pos, NULL_POSITION, search_type, n, -1,
2150 			&pos, (POSITION*)NULL, &lastlinepos);
2151 	/*
2152 	 * This ABORT_SIGS check ensures that if the user presses interrupt,
2153 	 * we don't continue and complete the search.
2154 	 * That is, we leave the display unchanged.
2155 	 * {{ Is this true? Do we always want to abort the search on interrupt? }}
2156 	 */
2157 	if (ABORT_SIGS())
2158 		return (-1);
2159 	if (n != 0)
2160 	{
2161 		/*
2162 		 * Search was unsuccessful.
2163 		 */
2164 #if HILITE_SEARCH
2165 		if ((hilite_search == OPT_ON || status_col) && n > 0)
2166 			/*
2167 			 * Redisplay old hilites.
2168 			 */
2169 			repaint_hilite(TRUE);
2170 #endif
2171 		return (n);
2172 	}
2173 
2174 	if (!(search_type & SRCH_NO_MOVE))
2175 	{
2176 		/*
2177 		 * Go to the matching line.
2178 		 */
2179 		if (lastlinepos != NULL_POSITION)
2180 			jump_loc(lastlinepos, BOTTOM);
2181 		else if (pos != opos)
2182 			jump_loc(pos, jump_sline);
2183 	}
2184 
2185 #if HILITE_SEARCH
2186 	if (hilite_search == OPT_ON || status_col)
2187 		/*
2188 		 * Display new hilites in the matching line.
2189 		 */
2190 		repaint_hilite(TRUE);
2191 #endif
2192 	return (0);
2193 }
2194 
2195 #if HILITE_SEARCH
2196 /*
2197  * Prepare hilites in a given range of the file.
2198  *
2199  * The pair (prep_startpos,prep_endpos) delimits a contiguous region
2200  * of the file that has been "prepared"; that is, scanned for matches for
2201  * the current search pattern, and hilites have been created for such matches.
2202  * If prep_startpos == NULL_POSITION, the prep region is empty.
2203  * If prep_endpos == NULL_POSITION, the prep region extends to EOF.
2204  * prep_hilite asks that the range (spos,epos) be covered by the prep region.
2205  */
2206 public void prep_hilite(POSITION spos, POSITION epos, int maxlines)
2207 {
2208 	POSITION nprep_startpos = prep_startpos;
2209 	POSITION nprep_endpos = prep_endpos;
2210 	POSITION new_epos;
2211 	POSITION max_epos;
2212 	int result;
2213 	int i;
2214 
2215 	/*
2216 	 * Search beyond where we're asked to search, so the prep region covers
2217 	 * more than we need.  Do one big search instead of a bunch of small ones.
2218 	 */
2219 	POSITION SEARCH_MORE = (POSITION) (3*size_linebuf);
2220 
2221 	if (!prev_pattern(&search_info) && !is_filtering())
2222 		return;
2223 
2224 	/*
2225 	 * Make sure our prep region always starts at the beginning of
2226 	 * a line. (search_range takes care of the end boundary below.)
2227 	 */
2228 	spos = back_raw_line(spos+1, NULL, NULL);
2229 
2230 	/*
2231 	 * If we're limited to a max number of lines, figure out the
2232 	 * file position we should stop at.
2233 	 */
2234 	if (maxlines < 0)
2235 		max_epos = NULL_POSITION;
2236 	else
2237 	{
2238 		max_epos = spos;
2239 		for (i = 0;  i < maxlines;  i++)
2240 			max_epos = forw_raw_line(max_epos, NULL, NULL);
2241 	}
2242 
2243 	/*
2244 	 * Find two ranges:
2245 	 * The range that we need to search (spos,epos); and the range that
2246 	 * the "prep" region will then cover (nprep_startpos,nprep_endpos).
2247 	 */
2248 
2249 	if (prep_startpos == NULL_POSITION ||
2250 	    (epos != NULL_POSITION && epos < prep_startpos) ||
2251 	    spos > prep_endpos)
2252 	{
2253 		/*
2254 		 * New range is not contiguous with old prep region.
2255 		 * Discard the old prep region and start a new one.
2256 		 */
2257 		clr_hilite();
2258 		clr_filter();
2259 		if (epos != NULL_POSITION)
2260 			epos += SEARCH_MORE;
2261 		nprep_startpos = spos;
2262 	} else
2263 	{
2264 		/*
2265 		 * New range partially or completely overlaps old prep region.
2266 		 */
2267 		if (epos == NULL_POSITION)
2268 		{
2269 			/*
2270 			 * New range goes to end of file.
2271 			 */
2272 			;
2273 		} else if (epos > prep_endpos)
2274 		{
2275 			/*
2276 			 * New range ends after old prep region.
2277 			 * Extend prep region to end at end of new range.
2278 			 */
2279 			epos += SEARCH_MORE;
2280 		} else /* (epos <= prep_endpos) */
2281 		{
2282 			/*
2283 			 * New range ends within old prep region.
2284 			 * Truncate search to end at start of old prep region.
2285 			 */
2286 			epos = prep_startpos;
2287 		}
2288 
2289 		if (spos < prep_startpos)
2290 		{
2291 			/*
2292 			 * New range starts before old prep region.
2293 			 * Extend old prep region backwards to start at
2294 			 * start of new range.
2295 			 */
2296 			if (spos < SEARCH_MORE)
2297 				spos = 0;
2298 			else
2299 				spos -= SEARCH_MORE;
2300 			nprep_startpos = spos;
2301 		} else /* (spos >= prep_startpos) */
2302 		{
2303 			/*
2304 			 * New range starts within or after old prep region.
2305 			 * Trim search to start at end of old prep region.
2306 			 */
2307 			spos = prep_endpos;
2308 		}
2309 	}
2310 
2311 	if (epos != NULL_POSITION && max_epos != NULL_POSITION &&
2312 	    epos > max_epos)
2313 		/*
2314 		 * Don't go past the max position we're allowed.
2315 		 */
2316 		epos = max_epos;
2317 
2318 	if (epos == NULL_POSITION || epos > spos)
2319 	{
2320 		int search_type = SRCH_FORW | SRCH_FIND_ALL;
2321 		search_type |= (search_info.search_type & (SRCH_NO_REGEX|SRCH_SUBSEARCH_ALL));
2322 		for (;;)
2323 		{
2324 			result = search_range(spos, epos, search_type, 0, maxlines, (POSITION*)NULL, &new_epos, (POSITION*)NULL);
2325 			if (result < 0)
2326 				return;
2327 			if (prep_endpos == NULL_POSITION || new_epos > prep_endpos)
2328 				nprep_endpos = new_epos;
2329 
2330 			/*
2331 			 * Check both ends of the resulting prep region to
2332 			 * make sure they're not filtered. If they are,
2333 			 * keep going at least one more line until we find
2334 			 * something that isn't filtered, or hit the end.
2335 			 */
2336 			if (prep_endpos == NULL_POSITION || nprep_endpos > prep_endpos)
2337 			{
2338 				if (new_epos >= nprep_endpos && is_filtered(new_epos-1))
2339 				{
2340 					spos = nprep_endpos;
2341 					epos = forw_raw_line(nprep_endpos, NULL, NULL);
2342 					if (epos == NULL_POSITION)
2343 						break;
2344 					maxlines = 1;
2345 					continue;
2346 				}
2347 			}
2348 
2349 			if (prep_startpos == NULL_POSITION || nprep_startpos < prep_startpos)
2350 			{
2351 				if (nprep_startpos > 0 && is_filtered(nprep_startpos))
2352 				{
2353 					epos = nprep_startpos;
2354 					spos = back_raw_line(nprep_startpos, NULL, NULL);
2355 					if (spos == NULL_POSITION)
2356 						break;
2357 					nprep_startpos = spos;
2358 					maxlines = 1;
2359 					continue;
2360 				}
2361 			}
2362 			break;
2363 		}
2364 	}
2365 	prep_startpos = nprep_startpos;
2366 	prep_endpos = nprep_endpos;
2367 }
2368 
2369 /*
2370  * Set the pattern to be used for line filtering.
2371  */
2372 public void set_filter_pattern(constant char *pattern, int search_type)
2373 {
2374 	struct pattern_info *filter;
2375 
2376 	clr_filter();
2377 	if (pattern == NULL || *pattern == '\0')
2378 	{
2379 		/* Clear and free all filters. */
2380 		for (filter = filter_infos; filter != NULL; )
2381 		{
2382 			struct pattern_info *next_filter = filter->next;
2383 			clear_pattern(filter);
2384 			free(filter);
2385 			filter = next_filter;
2386 		}
2387 		filter_infos = NULL;
2388 	} else
2389 	{
2390 		/* Create a new filter and add it to the filter_infos list. */
2391 		filter = ecalloc(1, sizeof(struct pattern_info));
2392 		init_pattern(filter);
2393 		if (set_pattern(filter, pattern, search_type, 1) < 0)
2394 		{
2395 			free(filter);
2396 			return;
2397 		}
2398 		filter->next = filter_infos;
2399 		filter_infos = filter;
2400 	}
2401 	screen_trashed();
2402 }
2403 
2404 /*
2405  * Is there a line filter in effect?
2406  */
2407 public lbool is_filtering(void)
2408 {
2409 	if (ch_getflags() & CH_HELPFILE)
2410 		return (FALSE);
2411 	return (filter_infos != NULL);
2412 }
2413 #endif
2414 
2415 #if HAVE_V8_REGCOMP
2416 /*
2417  * This function is called by the V8 regcomp to report
2418  * errors in regular expressions.
2419  */
2420 public int reg_show_error = 1;
2421 
2422 void regerror(constant char *s)
2423 {
2424 	PARG parg;
2425 
2426 	if (!reg_show_error)
2427 		return;
2428 	parg.p_string = s;
2429 	error("%s", &parg);
2430 }
2431 #endif
2432 
2433