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