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