xref: /freebsd/contrib/less/search.c (revision d9a42747950146bf03cda7f6e25d219253f8a57a)
1 /*
2  * Copyright (C) 1984-2022  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 sc_height;
27 extern int jump_sline;
28 extern int bs_mode;
29 extern int ctldisp;
30 extern int status_col;
31 extern void *ml_search;
32 extern POSITION start_attnpos;
33 extern POSITION end_attnpos;
34 extern int utf_mode;
35 extern int screen_trashed;
36 extern int sc_width;
37 extern int sc_height;
38 extern int hshift;
39 #if HILITE_SEARCH
40 extern int hilite_search;
41 extern int size_linebuf;
42 extern int squished;
43 extern int can_goto_line;
44 static int hide_hilite;
45 static POSITION prep_startpos;
46 static POSITION prep_endpos;
47 extern POSITION xxpos;
48 
49 /*
50  * Structures for maintaining a set of ranges for hilites and filtered-out
51  * lines. Each range is stored as a node within a red-black tree, and we
52  * try to extend existing ranges (without creating overlaps) rather than
53  * create new nodes if possible. We remember the last node found by a
54  * search for constant-time lookup if the next search is near enough to
55  * the previous. To aid that, we overlay a secondary doubly-linked list
56  * on top of the red-black tree so we can find the preceding/succeeding
57  * nodes also in constant time.
58  *
59  * Each node is allocated from a series of pools, each pool double the size
60  * of the previous (for amortised constant time allocation). Since our only
61  * tree operations are clear and node insertion, not node removal, we don't
62  * need to maintain a usage bitmap or freelist and can just return nodes
63  * from the pool in-order until capacity is reached.
64  */
65 struct hilite
66 {
67 	POSITION hl_startpos;
68 	POSITION hl_endpos;
69 };
70 struct hilite_node
71 {
72 	struct hilite_node *parent;
73 	struct hilite_node *left;
74 	struct hilite_node *right;
75 	struct hilite_node *prev;
76 	struct hilite_node *next;
77 	int red;
78 	struct hilite r;
79 };
80 struct hilite_storage
81 {
82 	int capacity;
83 	int used;
84 	struct hilite_storage *next;
85 	struct hilite_node *nodes;
86 };
87 struct hilite_tree
88 {
89 	struct hilite_storage *first;
90 	struct hilite_storage *current;
91 	struct hilite_node *root;
92 	struct hilite_node *lookaside;
93 };
94 #define HILITE_INITIALIZER() { NULL, NULL, NULL, NULL }
95 #define HILITE_LOOKASIDE_STEPS 2
96 
97 static struct hilite_tree hilite_anchor = HILITE_INITIALIZER();
98 static struct hilite_tree filter_anchor = HILITE_INITIALIZER();
99 static struct pattern_info *filter_infos = NULL;
100 
101 #endif
102 
103 /*
104  * These are the static variables that represent the "remembered"
105  * search pattern and filter pattern.
106  */
107 struct pattern_info {
108 	PATTERN_TYPE compiled;
109 	char* text;
110 	int search_type;
111 	struct pattern_info *next;
112 };
113 
114 #if NO_REGEX
115 #define info_compiled(info) ((void*)0)
116 #else
117 #define info_compiled(info) ((info)->compiled)
118 #endif
119 
120 static struct pattern_info search_info;
121 static int is_ucase_pattern;
122 public int is_caseless;
123 
124 /*
125  * Are there any uppercase letters in this string?
126  */
127 	static int
128 is_ucase(str)
129 	char *str;
130 {
131 	char *str_end = str + strlen(str);
132 	LWCHAR ch;
133 
134 	while (str < str_end)
135 	{
136 		ch = step_char(&str, +1, str_end);
137 		if (IS_UPPER(ch))
138 			return (1);
139 	}
140 	return (0);
141 }
142 
143 /*
144  * Discard a saved pattern.
145  */
146 	static void
147 clear_pattern(info)
148 	struct pattern_info *info;
149 {
150 	if (info->text != NULL)
151 		free(info->text);
152 	info->text = NULL;
153 #if !NO_REGEX
154 	uncompile_pattern(&info->compiled);
155 #endif
156 }
157 
158 /*
159  * Compile and save a search pattern.
160  */
161 	static int
162 set_pattern(info, pattern, search_type, show_error)
163 	struct pattern_info *info;
164 	char *pattern;
165 	int search_type;
166 	int show_error;
167 {
168 	/*
169 	 * Ignore case if -I is set OR
170 	 * -i is set AND the pattern is all lowercase.
171 	 */
172 	is_ucase_pattern = (pattern == NULL) ? FALSE : is_ucase(pattern);
173 	is_caseless = (is_ucase_pattern && caseless != OPT_ONPLUS) ? 0 : caseless;
174 #if !NO_REGEX
175 	if (pattern == NULL)
176 		SET_NULL_PATTERN(info->compiled);
177 	else if (compile_pattern(pattern, search_type, show_error, &info->compiled) < 0)
178 		return -1;
179 #endif
180 	/* Pattern compiled successfully; save the text too. */
181 	if (info->text != NULL)
182 		free(info->text);
183 	info->text = NULL;
184 	if (pattern != NULL)
185 	{
186 		info->text = (char *) ecalloc(1, strlen(pattern)+1);
187 		strcpy(info->text, pattern);
188 	}
189 	info->search_type = search_type;
190 	return 0;
191 }
192 
193 /*
194  * Initialize saved pattern to nothing.
195  */
196 	static void
197 init_pattern(info)
198 	struct pattern_info *info;
199 {
200 	SET_NULL_PATTERN(info->compiled);
201 	info->text = NULL;
202 	info->search_type = 0;
203 	info->next = NULL;
204 }
205 
206 /*
207  * Initialize search variables.
208  */
209 	public void
210 init_search(VOID_PARAM)
211 {
212 	init_pattern(&search_info);
213 }
214 
215 /*
216  * Determine which text conversions to perform before pattern matching.
217  */
218 	static int
219 get_cvt_ops(VOID_PARAM)
220 {
221 	int ops = 0;
222 
223 	if (is_caseless && !re_handles_caseless)
224 		ops |= CVT_TO_LC;
225 	if (bs_mode == BS_SPECIAL)
226 		ops |= CVT_BS;
227 	if (bs_mode != BS_CONTROL)
228 		ops |= CVT_CRLF;
229 	if (ctldisp == OPT_ONPLUS)
230 		ops |= CVT_ANSI;
231 	return (ops);
232 }
233 
234 /*
235  * Is there a previous (remembered) search pattern?
236  */
237 	static int
238 prev_pattern(info)
239 	struct pattern_info *info;
240 {
241 #if !NO_REGEX
242 	if ((info->search_type & SRCH_NO_REGEX) == 0)
243 		return (!is_null_pattern(info->compiled));
244 #endif
245 	return (info->text != NULL);
246 }
247 
248 #if HILITE_SEARCH
249 /*
250  * Repaint the hilites currently displayed on the screen.
251  * Repaint each line which contains highlighted text.
252  * If on==0, force all hilites off.
253  */
254 	public void
255 repaint_hilite(on)
256 	int on;
257 {
258 	int sindex;
259 	POSITION pos;
260 	int save_hide_hilite;
261 
262 	if (squished)
263 		repaint();
264 
265 	save_hide_hilite = hide_hilite;
266 	if (!on)
267 	{
268 		if (hide_hilite)
269 			return;
270 		hide_hilite = 1;
271 	}
272 
273 	if (!can_goto_line)
274 	{
275 		repaint();
276 		hide_hilite = save_hide_hilite;
277 		return;
278 	}
279 
280 	for (sindex = TOP;  sindex < TOP + sc_height-1;  sindex++)
281 	{
282 		pos = position(sindex);
283 		if (pos == NULL_POSITION)
284 			continue;
285 		(void) forw_line(pos);
286 		goto_line(sindex);
287 		put_line();
288 	}
289 	overlay_header();
290 	lower_left();
291 	hide_hilite = save_hide_hilite;
292 }
293 #endif
294 
295 /*
296  * Clear the attn hilite.
297  */
298 	public void
299 clear_attn(VOID_PARAM)
300 {
301 #if HILITE_SEARCH
302 	int sindex;
303 	POSITION old_start_attnpos;
304 	POSITION old_end_attnpos;
305 	POSITION pos;
306 	POSITION epos;
307 	int moved = 0;
308 
309 	if (start_attnpos == NULL_POSITION)
310 		return;
311 	old_start_attnpos = start_attnpos;
312 	old_end_attnpos = end_attnpos;
313 	start_attnpos = end_attnpos = NULL_POSITION;
314 
315 	if (!can_goto_line)
316 	{
317 		repaint();
318 		return;
319 	}
320 	if (squished)
321 		repaint();
322 
323 	for (sindex = TOP;  sindex < TOP + sc_height-1;  sindex++)
324 	{
325 		pos = position(sindex);
326 		if (pos == NULL_POSITION)
327 			continue;
328 		epos = position(sindex+1);
329 		if (pos <= old_end_attnpos &&
330 		     (epos == NULL_POSITION || epos > old_start_attnpos))
331 		{
332 			(void) forw_line(pos);
333 			goto_line(sindex);
334 			put_line();
335 			moved = 1;
336 		}
337 	}
338 	if (overlay_header())
339 		moved = 1;
340 	if (moved)
341 		lower_left();
342 #endif
343 }
344 
345 /*
346  * Toggle or clear search string highlighting.
347  */
348 	public void
349 undo_search(clear)
350 	int clear;
351 {
352 	clear_pattern(&search_info);
353 #if HILITE_SEARCH
354 	if (clear)
355 	{
356 		clr_hilite();
357 	} else
358 	{
359 		if (hilite_anchor.first == NULL)
360 		{
361 			error("No previous regular expression", NULL_PARG);
362 			return;
363 		}
364 		hide_hilite = !hide_hilite;
365 	}
366 	repaint_hilite(1);
367 #endif
368 }
369 
370 #if HILITE_SEARCH
371 /*
372  * Clear the hilite list.
373  */
374 	public void
375 clr_hlist(anchor)
376 	struct hilite_tree *anchor;
377 {
378 	struct hilite_storage *hls;
379 	struct hilite_storage *nexthls;
380 
381 	for (hls = anchor->first;  hls != NULL;  hls = nexthls)
382 	{
383 		nexthls = hls->next;
384 		free((void*)hls->nodes);
385 		free((void*)hls);
386 	}
387 	anchor->first = NULL;
388 	anchor->current = NULL;
389 	anchor->root = NULL;
390 
391 	anchor->lookaside = NULL;
392 
393 	prep_startpos = prep_endpos = NULL_POSITION;
394 }
395 
396 	public void
397 clr_hilite(VOID_PARAM)
398 {
399 	clr_hlist(&hilite_anchor);
400 }
401 
402 	public void
403 clr_filter(VOID_PARAM)
404 {
405 	clr_hlist(&filter_anchor);
406 }
407 
408 	struct hilite_node*
409 hlist_last(anchor)
410 	struct hilite_tree *anchor;
411 {
412 	struct hilite_node *n = anchor->root;
413 	while (n != NULL && n->right != NULL)
414 		n = n->right;
415 	return n;
416 }
417 
418 	struct hilite_node*
419 hlist_next(n)
420 	struct hilite_node *n;
421 {
422 	return n->next;
423 }
424 
425 	struct hilite_node*
426 hlist_prev(n)
427 	struct hilite_node *n;
428 {
429 	return n->prev;
430 }
431 
432 /*
433  * Find the node covering pos, or the node after it if no node covers it,
434  * or return NULL if pos is after the last range. Remember the found node,
435  * to speed up subsequent searches for the same or similar positions (if
436  * we return NULL, remember the last node.)
437  */
438 	struct hilite_node*
439 hlist_find(anchor, pos)
440 	struct hilite_tree *anchor;
441 	POSITION pos;
442 {
443 	struct hilite_node *n, *m;
444 
445 	if (anchor->lookaside)
446 	{
447 		int steps = 0;
448 		int hit = 0;
449 
450 		n = anchor->lookaside;
451 
452 		for (;;)
453 		{
454 			if (pos < n->r.hl_endpos)
455 			{
456 				if (n->prev == NULL || pos >= n->prev->r.hl_endpos)
457 				{
458 					hit = 1;
459 					break;
460 				}
461 			} else if (n->next == NULL)
462 			{
463 				n = NULL;
464 				hit = 1;
465 				break;
466 			}
467 
468 			/*
469 			 * If we don't find the right node within a small
470 			 * distance, don't keep doing a linear search!
471 			 */
472 			if (steps >= HILITE_LOOKASIDE_STEPS)
473 				break;
474 			steps++;
475 
476 			if (pos < n->r.hl_endpos)
477 				anchor->lookaside = n = n->prev;
478 			else
479 				anchor->lookaside = n = n->next;
480 		}
481 
482 		if (hit)
483 			return n;
484 	}
485 
486 	n = anchor->root;
487 	m = NULL;
488 
489 	while (n != NULL)
490 	{
491 		if (pos < n->r.hl_startpos)
492 		{
493 			if (n->left != NULL)
494 			{
495 				m = n;
496 				n = n->left;
497 				continue;
498 			}
499 			break;
500 		}
501 		if (pos >= n->r.hl_endpos)
502 		{
503 			if (n->right != NULL)
504 			{
505 				n = n->right;
506 				continue;
507 			}
508 			if (m != NULL)
509 			{
510 				n = m;
511 			} else
512 			{
513 				m = n;
514 				n = NULL;
515 			}
516 		}
517 		break;
518 	}
519 
520 	if (n != NULL)
521 		anchor->lookaside = n;
522 	else if (m != NULL)
523 		anchor->lookaside = m;
524 
525 	return n;
526 }
527 
528 /*
529  * Should any characters in a specified range be highlighted?
530  */
531 	static int
532 is_hilited_range(pos, epos)
533 	POSITION pos;
534 	POSITION epos;
535 {
536 	struct hilite_node *n = hlist_find(&hilite_anchor, pos);
537 	return (n != NULL && (epos == NULL_POSITION || epos > n->r.hl_startpos));
538 }
539 
540 /*
541  * Is a line "filtered" -- that is, should it be hidden?
542  */
543 	public int
544 is_filtered(pos)
545 	POSITION pos;
546 {
547 	struct hilite_node *n;
548 
549 	if (ch_getflags() & CH_HELPFILE)
550 		return (0);
551 
552 	n = hlist_find(&filter_anchor, pos);
553 	return (n != NULL && pos >= n->r.hl_startpos);
554 }
555 
556 /*
557  * If pos is hidden, return the next position which isn't, otherwise
558  * just return pos.
559  */
560 	public POSITION
561 next_unfiltered(pos)
562 	POSITION pos;
563 {
564 	struct hilite_node *n;
565 
566 	if (ch_getflags() & CH_HELPFILE)
567 		return (pos);
568 
569 	n = hlist_find(&filter_anchor, pos);
570 	while (n != NULL && pos >= n->r.hl_startpos)
571 	{
572 		pos = n->r.hl_endpos;
573 		n = n->next;
574 	}
575 	return (pos);
576 }
577 
578 /*
579  * If pos is hidden, return the previous position which isn't or 0 if
580  * we're filtered right to the beginning, otherwise just return pos.
581  */
582 	public POSITION
583 prev_unfiltered(pos)
584 	POSITION pos;
585 {
586 	struct hilite_node *n;
587 
588 	if (ch_getflags() & CH_HELPFILE)
589 		return (pos);
590 
591 	n = hlist_find(&filter_anchor, pos);
592 	while (n != NULL && pos >= n->r.hl_startpos)
593 	{
594 		pos = n->r.hl_startpos;
595 		if (pos == 0)
596 			break;
597 		pos--;
598 		n = n->prev;
599 	}
600 	return (pos);
601 }
602 
603 
604 /*
605  * Should any characters in a specified range be highlighted?
606  * If nohide is nonzero, don't consider hide_hilite.
607  */
608 	public int
609 is_hilited_attr(pos, epos, nohide, p_matches)
610 	POSITION pos;
611 	POSITION epos;
612 	int nohide;
613 	int *p_matches;
614 {
615 	int match;
616 
617 	if (p_matches != NULL)
618 		*p_matches = 0;
619 
620 	if (!status_col &&
621 	    start_attnpos != NULL_POSITION &&
622 	    pos <= end_attnpos &&
623 	     (epos == NULL_POSITION || epos >= start_attnpos))
624 		/*
625 		 * The attn line overlaps this range.
626 		 */
627 		return (AT_HILITE|AT_COLOR_ATTN);
628 
629 	match = is_hilited_range(pos, epos);
630 	if (!match)
631 		return (0);
632 
633 	if (p_matches == NULL)
634 		/*
635 		 * Kinda kludgy way to recognize that caller is checking for
636 		 * hilite in status column. In this case we want to return
637 		 * hilite status even if hiliting is disabled or hidden.
638 		 */
639 		return (AT_HILITE|AT_COLOR_SEARCH);
640 
641 	/*
642 	 * Report matches, even if we're hiding highlights.
643 	 */
644 	*p_matches = 1;
645 
646 	if (hilite_search == 0)
647 		/*
648 		 * Not doing highlighting.
649 		 */
650 		return (0);
651 
652 	if (!nohide && hide_hilite)
653 		/*
654 		 * Highlighting is hidden.
655 		 */
656 		return (0);
657 
658 	return (AT_HILITE|AT_COLOR_SEARCH);
659 }
660 
661 /*
662  * Tree node storage: get the current block of nodes if it has spare
663  * capacity, or create a new one if not.
664  */
665 	static struct hilite_storage*
666 hlist_getstorage(anchor)
667 	struct hilite_tree *anchor;
668 {
669 	int capacity = 1;
670 	struct hilite_storage *s;
671 
672 	if (anchor->current)
673 	{
674 		if (anchor->current->used < anchor->current->capacity)
675 			return anchor->current;
676 		capacity = anchor->current->capacity * 2;
677 	}
678 
679 	s = (struct hilite_storage *) ecalloc(1, sizeof(struct hilite_storage));
680 	s->nodes = (struct hilite_node *) ecalloc(capacity, sizeof(struct hilite_node));
681 	s->capacity = capacity;
682 	s->used = 0;
683 	s->next = NULL;
684 	if (anchor->current)
685 		anchor->current->next = s;
686 	else
687 		anchor->first = s;
688 	anchor->current = s;
689 	return s;
690 }
691 
692 /*
693  * Tree node storage: retrieve a new empty node to be inserted into the
694  * tree.
695  */
696 	static struct hilite_node*
697 hlist_getnode(anchor)
698 	struct hilite_tree *anchor;
699 {
700 	struct hilite_storage *s = hlist_getstorage(anchor);
701 	return &s->nodes[s->used++];
702 }
703 
704 /*
705  * Rotate the tree left around a pivot node.
706  */
707 	static void
708 hlist_rotate_left(anchor, n)
709 	struct hilite_tree *anchor;
710 	struct hilite_node *n;
711 {
712 	struct hilite_node *np = n->parent;
713 	struct hilite_node *nr = n->right;
714 	struct hilite_node *nrl = n->right->left;
715 
716 	if (np != NULL)
717 	{
718 		if (n == np->left)
719 			np->left = nr;
720 		else
721 			np->right = nr;
722 	} else
723 	{
724 		anchor->root = nr;
725 	}
726 	nr->left = n;
727 	n->right = nrl;
728 
729 	nr->parent = np;
730 	n->parent = nr;
731 	if (nrl != NULL)
732 		nrl->parent = n;
733 }
734 
735 /*
736  * Rotate the tree right around a pivot node.
737  */
738 	static void
739 hlist_rotate_right(anchor, n)
740 	struct hilite_tree *anchor;
741 	struct hilite_node *n;
742 {
743 	struct hilite_node *np = n->parent;
744 	struct hilite_node *nl = n->left;
745 	struct hilite_node *nlr = n->left->right;
746 
747 	if (np != NULL)
748 	{
749 		if (n == np->right)
750 			np->right = nl;
751 		else
752 			np->left = nl;
753 	} else
754 	{
755 		anchor->root = nl;
756 	}
757 	nl->right = n;
758 	n->left = nlr;
759 
760 	nl->parent = np;
761 	n->parent = nl;
762 	if (nlr != NULL)
763 		nlr->parent = n;
764 }
765 
766 
767 /*
768  * Add a new hilite to a hilite list.
769  */
770 	static void
771 add_hilite(anchor, hl)
772 	struct hilite_tree *anchor;
773 	struct hilite *hl;
774 {
775 	struct hilite_node *p, *n, *u;
776 
777 	/* Ignore empty ranges. */
778 	if (hl->hl_startpos >= hl->hl_endpos)
779 		return;
780 
781 	p = anchor->root;
782 
783 	/* Inserting the very first node is trivial. */
784 	if (p == NULL)
785 	{
786 		n = hlist_getnode(anchor);
787 		n->r = *hl;
788 		anchor->root = n;
789 		anchor->lookaside = n;
790 		return;
791 	}
792 
793 	/*
794 	 * Find our insertion point. If we come across any overlapping
795 	 * or adjoining existing ranges, shrink our range and discard
796 	 * if it become empty.
797 	 */
798 	for (;;)
799 	{
800 		if (hl->hl_startpos < p->r.hl_startpos)
801 		{
802 			if (hl->hl_endpos > p->r.hl_startpos)
803 				hl->hl_endpos = p->r.hl_startpos;
804 			if (p->left != NULL)
805 			{
806 				p = p->left;
807 				continue;
808 			}
809 			break;
810 		}
811 		if (hl->hl_startpos < p->r.hl_endpos) {
812 			hl->hl_startpos = p->r.hl_endpos;
813 			if (hl->hl_startpos >= hl->hl_endpos)
814 				return;
815 		}
816 		if (p->right != NULL)
817 		{
818 			p = p->right;
819 			continue;
820 		}
821 		break;
822 	}
823 
824 	/*
825 	 * Now we're at the right leaf, again check for contiguous ranges
826 	 * and extend the existing node if possible to avoid the
827 	 * insertion. Otherwise insert a new node at the leaf.
828 	 */
829 	if (hl->hl_startpos < p->r.hl_startpos) {
830 		if (hl->hl_endpos == p->r.hl_startpos)
831 		{
832 			p->r.hl_startpos = hl->hl_startpos;
833 			return;
834 		}
835 		if (p->prev != NULL && p->prev->r.hl_endpos == hl->hl_startpos)
836 		{
837 			p->prev->r.hl_endpos = hl->hl_endpos;
838 			return;
839 		}
840 
841 		p->left = n = hlist_getnode(anchor);
842 		n->next = p;
843 		if (p->prev != NULL)
844 		{
845 			n->prev = p->prev;
846 			p->prev->next = n;
847 		}
848 		p->prev = n;
849 	} else {
850 		if (p->r.hl_endpos == hl->hl_startpos)
851 		{
852 			p->r.hl_endpos = hl->hl_endpos;
853 			return;
854 		}
855 		if (p->next != NULL && hl->hl_endpos == p->next->r.hl_startpos) {
856 			p->next->r.hl_startpos = hl->hl_startpos;
857 			return;
858 		}
859 
860 		p->right = n = hlist_getnode(anchor);
861 		n->prev = p;
862 		if (p->next != NULL)
863 		{
864 			n->next = p->next;
865 			p->next->prev = n;
866 		}
867 		p->next = n;
868 	}
869 	n->parent = p;
870 	n->red = 1;
871 	n->r = *hl;
872 
873 	/*
874 	 * The tree is in the correct order and covers the right ranges
875 	 * now, but may have become unbalanced. Rebalance it using the
876 	 * standard red-black tree constraints and operations.
877 	 */
878 	for (;;)
879 	{
880 		/* case 1 - current is root, root is always black */
881 		if (n->parent == NULL)
882 		{
883 			n->red = 0;
884 			break;
885 		}
886 
887 		/* case 2 - parent is black, we can always be red */
888 		if (!n->parent->red)
889 			break;
890 
891 		/*
892 		 * constraint: because the root must be black, if our
893 		 * parent is red it cannot be the root therefore we must
894 		 * have a grandparent
895 		 */
896 
897 		/*
898 		 * case 3 - parent and uncle are red, repaint them black,
899 		 * the grandparent red, and start again at the grandparent.
900 		 */
901 		u = n->parent->parent->left;
902 		if (n->parent == u)
903 			u = n->parent->parent->right;
904 		if (u != NULL && u->red)
905 		{
906 			n->parent->red = 0;
907 			u->red = 0;
908 			n = n->parent->parent;
909 			n->red = 1;
910 			continue;
911 		}
912 
913 		/*
914 		 * case 4 - parent is red but uncle is black, parent and
915 		 * grandparent on opposite sides. We need to start
916 		 * changing the structure now. This and case 5 will shorten
917 		 * our branch and lengthen the sibling, between them
918 		 * restoring balance.
919 		 */
920 		if (n == n->parent->right &&
921 		    n->parent == n->parent->parent->left)
922 		{
923 			hlist_rotate_left(anchor, n->parent);
924 			n = n->left;
925 		} else if (n == n->parent->left &&
926 			   n->parent == n->parent->parent->right)
927 		{
928 			hlist_rotate_right(anchor, n->parent);
929 			n = n->right;
930 		}
931 
932 		/*
933 		 * case 5 - parent is red but uncle is black, parent and
934 		 * grandparent on same side
935 		 */
936 		n->parent->red = 0;
937 		n->parent->parent->red = 1;
938 		if (n == n->parent->left)
939 			hlist_rotate_right(anchor, n->parent->parent);
940 		else
941 			hlist_rotate_left(anchor, n->parent->parent);
942 		break;
943 	}
944 }
945 
946 /*
947  * Highlight every character in a range of displayed characters.
948  */
949 	static void
950 create_hilites(linepos, start_index, end_index, chpos)
951 	POSITION linepos;
952 	int start_index;
953 	int end_index;
954 	int *chpos;
955 {
956 	struct hilite hl;
957 	int i;
958 
959 	/* Start the first hilite. */
960 	hl.hl_startpos = linepos + chpos[start_index];
961 
962 	/*
963 	 * Step through the displayed chars.
964 	 * If the source position (before cvt) of the char is one more
965 	 * than the source pos of the previous char (the usual case),
966 	 * just increase the size of the current hilite by one.
967 	 * Otherwise (there are backspaces or something involved),
968 	 * finish the current hilite and start a new one.
969 	 */
970 	for (i = start_index+1;  i <= end_index;  i++)
971 	{
972 		if (chpos[i] != chpos[i-1] + 1 || i == end_index)
973 		{
974 			hl.hl_endpos = linepos + chpos[i-1] + 1;
975 			add_hilite(&hilite_anchor, &hl);
976 			/* Start new hilite unless this is the last char. */
977 			if (i < end_index)
978 			{
979 				hl.hl_startpos = linepos + chpos[i];
980 			}
981 		}
982 	}
983 }
984 
985 /*
986  * Make a hilite for each string in a physical line which matches
987  * the current pattern.
988  * sp,ep delimit the first match already found.
989  */
990 	static void
991 hilite_line(linepos, line, line_len, chpos, sp, ep, cvt_ops)
992 	POSITION linepos;
993 	char *line;
994 	int line_len;
995 	int *chpos;
996 	char *sp;
997 	char *ep;
998 	int cvt_ops;
999 {
1000 	char *searchp;
1001 	char *line_end = line + line_len;
1002 
1003 	/*
1004 	 * sp and ep delimit the first match in the line.
1005 	 * Mark the corresponding file positions, then
1006 	 * look for further matches and mark them.
1007 	 * {{ This technique, of calling match_pattern on subsequent
1008 	 *    substrings of the line, may mark more than is correct
1009 	 *    if the pattern starts with "^".  This bug is fixed
1010 	 *    for those regex functions that accept a notbol parameter
1011 	 *    (currently POSIX, PCRE and V8-with-regexec2). }}
1012 	 */
1013 	searchp = line;
1014 	do {
1015 		if (sp == NULL || ep == NULL)
1016 			return;
1017 		create_hilites(linepos, sp-line, ep-line, chpos);
1018 		/*
1019 		 * If we matched more than zero characters,
1020 		 * move to the first char after the string we matched.
1021 		 * If we matched zero, just move to the next char.
1022 		 */
1023 		if (ep > searchp)
1024 			searchp = ep;
1025 		else if (searchp != line_end)
1026 			searchp++;
1027 		else /* end of line */
1028 			break;
1029 	} while (match_pattern(info_compiled(&search_info), search_info.text,
1030 			searchp, line_end - searchp, &sp, &ep, 1, search_info.search_type));
1031 }
1032 #endif
1033 
1034 #if HILITE_SEARCH
1035 /*
1036  * Find matching text which is currently on screen and highlight it.
1037  */
1038 	static void
1039 hilite_screen(VOID_PARAM)
1040 {
1041 	struct scrpos scrpos;
1042 
1043 	get_scrpos(&scrpos, TOP);
1044 	if (scrpos.pos == NULL_POSITION)
1045 		return;
1046 	prep_hilite(scrpos.pos, position(BOTTOM_PLUS_ONE), -1);
1047 	repaint_hilite(1);
1048 }
1049 
1050 /*
1051  * Change highlighting parameters.
1052  */
1053 	public void
1054 chg_hilite(VOID_PARAM)
1055 {
1056 	/*
1057 	 * Erase any highlights currently on screen.
1058 	 */
1059 	clr_hilite();
1060 	hide_hilite = 0;
1061 
1062 	if (hilite_search == OPT_ONPLUS)
1063 		/*
1064 		 * Display highlights.
1065 		 */
1066 		hilite_screen();
1067 }
1068 #endif
1069 
1070 /*
1071  * Figure out where to start a search.
1072  */
1073 	static POSITION
1074 search_pos(search_type)
1075 	int search_type;
1076 {
1077 	POSITION pos;
1078 	int sindex;
1079 
1080 	if (empty_screen())
1081 	{
1082 		/*
1083 		 * Start at the beginning (or end) of the file.
1084 		 * The empty_screen() case is mainly for
1085 		 * command line initiated searches;
1086 		 * for example, "+/xyz" on the command line.
1087 		 * Also for multi-file (SRCH_PAST_EOF) searches.
1088 		 */
1089 		if (search_type & SRCH_FORW)
1090 		{
1091 			pos = ch_zero();
1092 		} else
1093 		{
1094 			pos = ch_length();
1095 			if (pos == NULL_POSITION)
1096 			{
1097 				(void) ch_end_seek();
1098 				pos = ch_length();
1099 			}
1100 		}
1101 		sindex = 0;
1102 	} else
1103 	{
1104 		int add_one = 0;
1105 
1106 		if (how_search == OPT_ON)
1107 		{
1108 			/*
1109 			 * Search does not include current screen.
1110 			 */
1111 			if (search_type & SRCH_FORW)
1112 				sindex = sc_height-1; /* BOTTOM_PLUS_ONE */
1113 			else
1114 				sindex = 0; /* TOP */
1115 		} else if (how_search == OPT_ONPLUS && !(search_type & SRCH_AFTER_TARGET))
1116 		{
1117 			/*
1118 			 * Search includes all of displayed screen.
1119 			 */
1120 			if (search_type & SRCH_FORW)
1121 				sindex = 0; /* TOP */
1122 			else
1123 				sindex = sc_height-1; /* BOTTOM_PLUS_ONE */
1124 		} else
1125 		{
1126 			/*
1127 			 * Search includes the part of current screen beyond the jump target.
1128 			 * It starts at the jump target (if searching backwards),
1129 			 * or at the jump target plus one (if forwards).
1130 			 */
1131 			sindex = sindex_from_sline(jump_sline);
1132 			if (search_type & SRCH_FORW)
1133 				add_one = 1;
1134 		}
1135 		pos = position(sindex);
1136 		if (add_one)
1137 			pos = forw_raw_line(pos, (char **)NULL, (int *)NULL);
1138 	}
1139 
1140 	/*
1141 	 * If the line is empty, look around for a plausible starting place.
1142 	 */
1143 	if (search_type & SRCH_FORW)
1144 	{
1145 		while (pos == NULL_POSITION)
1146 		{
1147 			if (++sindex >= sc_height)
1148 				break;
1149 			pos = position(sindex);
1150 		}
1151 	} else
1152 	{
1153 		while (pos == NULL_POSITION)
1154 		{
1155 			if (--sindex < 0)
1156 				break;
1157 			pos = position(sindex);
1158 		}
1159 	}
1160 	return (pos);
1161 }
1162 
1163 /*
1164  * Check to see if the line matches the filter pattern.
1165  * If so, add an entry to the filter list.
1166  */
1167 #if HILITE_SEARCH
1168 	static int
1169 matches_filters(pos, cline, line_len, chpos, linepos, sp, ep)
1170 	POSITION pos;
1171 	char *cline;
1172 	int line_len;
1173 	int *chpos;
1174 	POSITION linepos;
1175 	char **sp;
1176 	char **ep;
1177 {
1178 	struct pattern_info *filter;
1179 
1180 	for (filter = filter_infos; filter != NULL; filter = filter->next)
1181 	{
1182 		int line_filter = match_pattern(info_compiled(filter), filter->text,
1183 			cline, line_len, sp, ep, 0, filter->search_type);
1184 		if (line_filter)
1185 		{
1186 			struct hilite hl;
1187 			hl.hl_startpos = linepos;
1188 			hl.hl_endpos = pos;
1189 			add_hilite(&filter_anchor, &hl);
1190 			free(cline);
1191 			free(chpos);
1192 			return (1);
1193 		}
1194 	}
1195 	return (0);
1196 }
1197 #endif
1198 
1199 /*
1200  * Get the position of the first char in the screen line which
1201  * puts tpos on screen.
1202  */
1203 	static POSITION
1204 get_lastlinepos(pos, tpos, sheight)
1205 	POSITION pos;
1206 	POSITION tpos;
1207 	int sheight;
1208 {
1209 	int nlines;
1210 
1211 	for (nlines = 0;;  nlines++)
1212 	{
1213 		POSITION npos = forw_line(pos);
1214 		if (npos > tpos)
1215 		{
1216 			if (nlines < sheight)
1217 				return NULL_POSITION;
1218 			return pos;
1219 		}
1220 		pos = npos;
1221 	}
1222 }
1223 
1224 /*
1225  * Get the segment index of tpos in the line starting at pos.
1226  * A segment is a string of printable chars that fills the screen width.
1227  */
1228 	static int
1229 get_seg(pos, tpos)
1230 	POSITION pos;
1231 	POSITION tpos;
1232 {
1233 	int seg;
1234 
1235 	for (seg = 0;;  seg++)
1236 	{
1237 		POSITION npos = forw_line_seg(pos, FALSE, FALSE, TRUE);
1238 		if (npos > tpos)
1239 			return seg;
1240 		pos = npos;
1241 	}
1242 }
1243 
1244 /*
1245  * Search a subset of the file, specified by start/end position.
1246  */
1247 	static int
1248 search_range(pos, endpos, search_type, matches, maxlines, plinepos, pendpos, plastlinepos)
1249 	POSITION pos;
1250 	POSITION endpos;
1251 	int search_type;
1252 	int matches;
1253 	int maxlines;
1254 	POSITION *plinepos;
1255 	POSITION *pendpos;
1256 	POSITION *plastlinepos;
1257 {
1258 	char *line;
1259 	char *cline;
1260 	int line_len;
1261 	LINENUM linenum;
1262 	char *sp, *ep;
1263 	int line_match;
1264 	int cvt_ops;
1265 	int cvt_len;
1266 	int *chpos;
1267 	POSITION linepos, oldpos;
1268 	int swidth = sc_width - line_pfx_width();
1269 	int sheight = sc_height - sindex_from_sline(jump_sline);
1270 
1271 	linenum = find_linenum(pos);
1272 	oldpos = pos;
1273 	/* When the search wraps around, end at starting position. */
1274 	if ((search_type & SRCH_WRAP) && endpos == NULL_POSITION)
1275 		endpos = pos;
1276 	for (;;)
1277 	{
1278 		/*
1279 		 * Get lines until we find a matching one or until
1280 		 * we hit end-of-file (or beginning-of-file if we're
1281 		 * going backwards), or until we hit the end position.
1282 		 */
1283 		if (ABORT_SIGS())
1284 		{
1285 			/*
1286 			 * A signal aborts the search.
1287 			 */
1288 			return (-1);
1289 		}
1290 
1291 		if ((endpos != NULL_POSITION && !(search_type & SRCH_WRAP) &&
1292 			(((search_type & SRCH_FORW) && pos >= endpos) ||
1293 			 ((search_type & SRCH_BACK) && pos <= endpos))) || maxlines == 0)
1294 		{
1295 			/*
1296 			 * Reached end position without a match.
1297 			 */
1298 			if (pendpos != NULL)
1299 				*pendpos = pos;
1300 			return (matches);
1301 		}
1302 		if (maxlines > 0)
1303 			maxlines--;
1304 
1305 		if (search_type & SRCH_FORW)
1306 		{
1307 			/*
1308 			 * Read the next line, and save the
1309 			 * starting position of that line in linepos.
1310 			 */
1311 			linepos = pos;
1312 			pos = forw_raw_line(pos, &line, &line_len);
1313 			if (linenum != 0)
1314 				linenum++;
1315 		} else
1316 		{
1317 			/*
1318 			 * Read the previous line and save the
1319 			 * starting position of that line in linepos.
1320 			 */
1321 			pos = back_raw_line(pos, &line, &line_len);
1322 			linepos = pos;
1323 			if (linenum != 0)
1324 				linenum--;
1325 		}
1326 
1327 		if (pos == NULL_POSITION)
1328 		{
1329 			/*
1330 			 * Reached EOF/BOF without a match.
1331 			 */
1332 			if (search_type & SRCH_WRAP)
1333 			{
1334 				/*
1335 				 * The search wraps around the current file, so
1336 				 * try to continue at BOF/EOF.
1337 				 */
1338 				if (search_type & SRCH_FORW)
1339 				{
1340 					pos = ch_zero();
1341 				} else
1342 				{
1343 					pos = ch_length();
1344 					if (pos == NULL_POSITION)
1345 					{
1346 						(void) ch_end_seek();
1347 						pos = ch_length();
1348 					}
1349 				}
1350 				if (pos != NULL_POSITION) {
1351 					/*
1352 					 * Wrap-around was successful. Clear
1353 					 * the flag so we don't wrap again, and
1354 					 * continue the search at new pos.
1355 					 */
1356 					search_type &= ~SRCH_WRAP;
1357 					linenum = find_linenum(pos);
1358 					continue;
1359 				}
1360 			}
1361 			if (pendpos != NULL)
1362 				*pendpos = oldpos;
1363 			return (matches);
1364 		}
1365 
1366 		/*
1367 		 * If we're using line numbers, we might as well
1368 		 * remember the information we have now (the position
1369 		 * and line number of the current line).
1370 		 * Don't do it for every line because it slows down
1371 		 * the search.  Remember the line number only if
1372 		 * we're "far" from the last place we remembered it.
1373 		 */
1374 		if (linenums && abs((int)(pos - oldpos)) > 2048)
1375 			add_lnum(linenum, pos);
1376 		oldpos = pos;
1377 
1378 #if HILITE_SEARCH
1379 		if (is_filtered(linepos))
1380 			continue;
1381 #endif
1382 
1383 		/*
1384 		 * If it's a caseless search, convert the line to lowercase.
1385 		 * If we're doing backspace processing, delete backspaces.
1386 		 */
1387 		cvt_ops = get_cvt_ops();
1388 		cvt_len = cvt_length(line_len, cvt_ops);
1389 		cline = (char *) ecalloc(1, cvt_len);
1390 		chpos = cvt_alloc_chpos(cvt_len);
1391 		cvt_text(cline, line, chpos, &line_len, cvt_ops);
1392 
1393 #if HILITE_SEARCH
1394 		/*
1395 		 * If any filters are in effect, ignore non-matching lines.
1396 		 */
1397 		if (filter_infos != NULL &&
1398 		   ((search_type & SRCH_FIND_ALL) ||
1399 		     prep_startpos == NULL_POSITION ||
1400 		     linepos < prep_startpos || linepos >= prep_endpos)) {
1401 			if (matches_filters(pos, cline, line_len, chpos, linepos, &sp, &ep))
1402 				continue;
1403 		}
1404 #endif
1405 
1406 		/*
1407 		 * Test the next line to see if we have a match.
1408 		 * We are successful if we either want a match and got one,
1409 		 * or if we want a non-match and got one.
1410 		 */
1411 		if (prev_pattern(&search_info))
1412 		{
1413 			line_match = match_pattern(info_compiled(&search_info), search_info.text,
1414 				cline, line_len, &sp, &ep, 0, search_type);
1415 			if (line_match)
1416 			{
1417 				/*
1418 				 * Got a match.
1419 				 */
1420 				if (search_type & SRCH_FIND_ALL)
1421 				{
1422 #if HILITE_SEARCH
1423 					/*
1424 					 * We are supposed to find all matches in the range.
1425 					 * Just add the matches in this line to the
1426 					 * hilite list and keep searching.
1427 					 */
1428 					hilite_line(linepos, cline, line_len, chpos, sp, ep, cvt_ops);
1429 #endif
1430 				} else if (--matches <= 0)
1431 				{
1432 					/*
1433 					 * Found the one match we're looking for.
1434 					 * Return it.
1435 					 */
1436 #if HILITE_SEARCH
1437 					if (hilite_search == OPT_ON)
1438 					{
1439 						/*
1440 						 * Clear the hilite list and add only
1441 						 * the matches in this one line.
1442 						 */
1443 						clr_hilite();
1444 						hilite_line(linepos, cline, line_len, chpos, sp, ep, cvt_ops);
1445 					}
1446 #endif
1447 					if (chop_line())
1448 					{
1449 						/*
1450 						 * If necessary, shift horizontally to make sure
1451 						 * search match is fully visible.
1452 						 */
1453 						if (sp != NULL && ep != NULL)
1454 						{
1455 							int start_off = sp - cline;
1456 							int end_off = ep - cline;
1457 							int save_hshift = hshift;
1458 							int sshift;
1459 							int eshift;
1460 							hshift = 0; /* make get_seg count screen lines */
1461 							sshift = swidth * get_seg(linepos, linepos + chpos[start_off]);
1462 							eshift = swidth * get_seg(linepos, linepos + chpos[end_off]);
1463 							if (sshift >= save_hshift && eshift <= save_hshift)
1464 							{
1465 								hshift = save_hshift;
1466 							} else
1467 							{
1468 								hshift = sshift;
1469 								screen_trashed = 1;
1470 							}
1471 						}
1472 					} else if (plastlinepos != NULL)
1473 					{
1474 						/*
1475 						 * If the line is so long that the highlighted match
1476 						 * won't be seen when the line is displayed normally
1477 						 * (starting at the first char) because it fills the whole
1478 						 * screen and more, scroll forward until the last char
1479 						 * of the match appears in the last line on the screen.
1480 						 * lastlinepos is the position of the first char of that last line.
1481 						 */
1482 						if (ep != NULL)
1483 						{
1484 							int end_off = ep - cline;
1485 							if (end_off >= swidth * sheight / 4) /* heuristic */
1486 								*plastlinepos = get_lastlinepos(linepos, linepos + chpos[end_off], sheight);
1487 						}
1488 					}
1489 					free(cline);
1490 					free(chpos);
1491 					if (plinepos != NULL)
1492 						*plinepos = linepos;
1493 					return (0);
1494 				}
1495 			}
1496 		}
1497 		free(cline);
1498 		free(chpos);
1499 	}
1500 }
1501 
1502 /*
1503  * search for a pattern in history. If found, compile that pattern.
1504  */
1505 	static int
1506 hist_pattern(search_type)
1507 	int search_type;
1508 {
1509 #if CMD_HISTORY
1510 	char *pattern;
1511 
1512 	set_mlist(ml_search, 0);
1513 	pattern = cmd_lastpattern();
1514 	if (pattern == NULL)
1515 		return (0);
1516 
1517 	if (set_pattern(&search_info, pattern, search_type, 1) < 0)
1518 		return (-1);
1519 
1520 #if HILITE_SEARCH
1521 	if (hilite_search == OPT_ONPLUS && !hide_hilite)
1522 		hilite_screen();
1523 #endif
1524 
1525 	return (1);
1526 #else /* CMD_HISTORY */
1527 	return (0);
1528 #endif /* CMD_HISTORY */
1529 }
1530 
1531 /*
1532  * Change the caseless-ness of searches.
1533  * Updates the internal search state to reflect a change in the -i flag.
1534  */
1535 	public void
1536 chg_caseless(VOID_PARAM)
1537 {
1538 	if (!is_ucase_pattern)
1539 	{
1540 		/*
1541 		 * Pattern did not have uppercase.
1542 		 * Set the search caselessness to the global caselessness.
1543 		 */
1544 		is_caseless = caseless;
1545 		/*
1546 		 * If regex handles caseless, we need to discard
1547 		 * the pattern which was compiled with the old caseless.
1548 		 */
1549 		if (!re_handles_caseless)
1550 			/* We handle caseless, so the pattern doesn't change. */
1551 			return;
1552 	}
1553 	/*
1554 	 * Regenerate the pattern using the new state.
1555 	 */
1556 	clear_pattern(&search_info);
1557 	(void) hist_pattern(search_info.search_type);
1558 }
1559 
1560 /*
1561  * Search for the n-th occurrence of a specified pattern,
1562  * either forward or backward.
1563  * Return the number of matches not yet found in this file
1564  * (that is, n minus the number of matches found).
1565  * Return -1 if the search should be aborted.
1566  * Caller may continue the search in another file
1567  * if less than n matches are found in this file.
1568  */
1569 	public int
1570 search(search_type, pattern, n)
1571 	int search_type;
1572 	char *pattern;
1573 	int n;
1574 {
1575 	POSITION pos;
1576 	POSITION opos;
1577 	POSITION lastlinepos = NULL_POSITION;
1578 
1579 	if (pattern == NULL || *pattern == '\0')
1580 	{
1581 		/*
1582 		 * A null pattern means use the previously compiled pattern.
1583 		 */
1584 		search_type |= SRCH_AFTER_TARGET;
1585 		if (!prev_pattern(&search_info))
1586 		{
1587 			int r = hist_pattern(search_type);
1588 			if (r == 0)
1589 				error("No previous regular expression", NULL_PARG);
1590 			if (r <= 0)
1591 				return (-1);
1592 		}
1593 		if ((search_type & SRCH_NO_REGEX) !=
1594 		      (search_info.search_type & SRCH_NO_REGEX))
1595 		{
1596 			error("Please re-enter search pattern", NULL_PARG);
1597 			return -1;
1598 		}
1599 #if HILITE_SEARCH
1600 		if (hilite_search == OPT_ON || status_col)
1601 		{
1602 			/*
1603 			 * Erase the highlights currently on screen.
1604 			 * If the search fails, we'll redisplay them later.
1605 			 */
1606 			repaint_hilite(0);
1607 		}
1608 		if (hilite_search == OPT_ONPLUS && hide_hilite)
1609 		{
1610 			/*
1611 			 * Highlight any matches currently on screen,
1612 			 * before we actually start the search.
1613 			 */
1614 			hide_hilite = 0;
1615 			hilite_screen();
1616 		}
1617 		hide_hilite = 0;
1618 #endif
1619 	} else
1620 	{
1621 		/*
1622 		 * Compile the pattern.
1623 		 */
1624 		int show_error = !(search_type & SRCH_INCR);
1625 		if (set_pattern(&search_info, pattern, search_type, show_error) < 0)
1626 			return (-1);
1627 #if HILITE_SEARCH
1628 		if (hilite_search || status_col)
1629 		{
1630 			/*
1631 			 * Erase the highlights currently on screen.
1632 			 * Also permanently delete them from the hilite list.
1633 			 */
1634 			repaint_hilite(0);
1635 			hide_hilite = 0;
1636 			clr_hilite();
1637 		}
1638 		if (hilite_search == OPT_ONPLUS || status_col)
1639 		{
1640 			/*
1641 			 * Highlight any matches currently on screen,
1642 			 * before we actually start the search.
1643 			 */
1644 			hilite_screen();
1645 		}
1646 #endif
1647 	}
1648 
1649 	/*
1650 	 * Figure out where to start the search.
1651 	 */
1652 	pos = search_pos(search_type);
1653 	opos = position(sindex_from_sline(jump_sline));
1654 	if (pos == NULL_POSITION)
1655 	{
1656 		/*
1657 		 * Can't find anyplace to start searching from.
1658 		 */
1659 		if (search_type & SRCH_PAST_EOF)
1660 			return (n);
1661 #if HILITE_SEARCH
1662 		if (hilite_search == OPT_ON || status_col)
1663 			repaint_hilite(1);
1664 #endif
1665 		error("Nothing to search", NULL_PARG);
1666 		return (-1);
1667 	}
1668 
1669 	n = search_range(pos, NULL_POSITION, search_type, n, -1,
1670 			&pos, (POSITION*)NULL, &lastlinepos);
1671 	if (n != 0)
1672 	{
1673 		/*
1674 		 * Search was unsuccessful.
1675 		 */
1676 #if HILITE_SEARCH
1677 		if ((hilite_search == OPT_ON || status_col) && n > 0)
1678 			/*
1679 			 * Redisplay old hilites.
1680 			 */
1681 			repaint_hilite(1);
1682 #endif
1683 		return (n);
1684 	}
1685 
1686 	if (!(search_type & SRCH_NO_MOVE))
1687 	{
1688 		/*
1689 		 * Go to the matching line.
1690 		 */
1691 		if (lastlinepos != NULL_POSITION)
1692 			jump_loc(lastlinepos, BOTTOM);
1693 		else if (pos != opos)
1694 			jump_loc(pos, jump_sline);
1695 	}
1696 
1697 #if HILITE_SEARCH
1698 	if (hilite_search == OPT_ON || status_col)
1699 		/*
1700 		 * Display new hilites in the matching line.
1701 		 */
1702 		repaint_hilite(1);
1703 #endif
1704 	return (0);
1705 }
1706 
1707 #if HILITE_SEARCH
1708 /*
1709  * Prepare hilites in a given range of the file.
1710  *
1711  * The pair (prep_startpos,prep_endpos) delimits a contiguous region
1712  * of the file that has been "prepared"; that is, scanned for matches for
1713  * the current search pattern, and hilites have been created for such matches.
1714  * If prep_startpos == NULL_POSITION, the prep region is empty.
1715  * If prep_endpos == NULL_POSITION, the prep region extends to EOF.
1716  * prep_hilite asks that the range (spos,epos) be covered by the prep region.
1717  */
1718 	public void
1719 prep_hilite(spos, epos, maxlines)
1720 	POSITION spos;
1721 	POSITION epos;
1722 	int maxlines;
1723 {
1724 	POSITION nprep_startpos = prep_startpos;
1725 	POSITION nprep_endpos = prep_endpos;
1726 	POSITION new_epos;
1727 	POSITION max_epos;
1728 	int result;
1729 	int i;
1730 
1731 /*
1732  * Search beyond where we're asked to search, so the prep region covers
1733  * more than we need.  Do one big search instead of a bunch of small ones.
1734  */
1735 #define SEARCH_MORE (3*size_linebuf)
1736 
1737 	if (!prev_pattern(&search_info) && !is_filtering())
1738 		return;
1739 
1740 	/*
1741 	 * Make sure our prep region always starts at the beginning of
1742 	 * a line. (search_range takes care of the end boundary below.)
1743 	 */
1744 	spos = back_raw_line(spos+1, (char **)NULL, (int *)NULL);
1745 
1746 	/*
1747 	 * If we're limited to a max number of lines, figure out the
1748 	 * file position we should stop at.
1749 	 */
1750 	if (maxlines < 0)
1751 		max_epos = NULL_POSITION;
1752 	else
1753 	{
1754 		max_epos = spos;
1755 		for (i = 0;  i < maxlines;  i++)
1756 			max_epos = forw_raw_line(max_epos, (char **)NULL, (int *)NULL);
1757 	}
1758 
1759 	/*
1760 	 * Find two ranges:
1761 	 * The range that we need to search (spos,epos); and the range that
1762 	 * the "prep" region will then cover (nprep_startpos,nprep_endpos).
1763 	 */
1764 
1765 	if (prep_startpos == NULL_POSITION ||
1766 	    (epos != NULL_POSITION && epos < prep_startpos) ||
1767 	    spos > prep_endpos)
1768 	{
1769 		/*
1770 		 * New range is not contiguous with old prep region.
1771 		 * Discard the old prep region and start a new one.
1772 		 */
1773 		clr_hilite();
1774 		clr_filter();
1775 		if (epos != NULL_POSITION)
1776 			epos += SEARCH_MORE;
1777 		nprep_startpos = spos;
1778 	} else
1779 	{
1780 		/*
1781 		 * New range partially or completely overlaps old prep region.
1782 		 */
1783 		if (epos == NULL_POSITION)
1784 		{
1785 			/*
1786 			 * New range goes to end of file.
1787 			 */
1788 			;
1789 		} else if (epos > prep_endpos)
1790 		{
1791 			/*
1792 			 * New range ends after old prep region.
1793 			 * Extend prep region to end at end of new range.
1794 			 */
1795 			epos += SEARCH_MORE;
1796 		} else /* (epos <= prep_endpos) */
1797 		{
1798 			/*
1799 			 * New range ends within old prep region.
1800 			 * Truncate search to end at start of old prep region.
1801 			 */
1802 			epos = prep_startpos;
1803 		}
1804 
1805 		if (spos < prep_startpos)
1806 		{
1807 			/*
1808 			 * New range starts before old prep region.
1809 			 * Extend old prep region backwards to start at
1810 			 * start of new range.
1811 			 */
1812 			if (spos < SEARCH_MORE)
1813 				spos = 0;
1814 			else
1815 				spos -= SEARCH_MORE;
1816 			nprep_startpos = spos;
1817 		} else /* (spos >= prep_startpos) */
1818 		{
1819 			/*
1820 			 * New range starts within or after old prep region.
1821 			 * Trim search to start at end of old prep region.
1822 			 */
1823 			spos = prep_endpos;
1824 		}
1825 	}
1826 
1827 	if (epos != NULL_POSITION && max_epos != NULL_POSITION &&
1828 	    epos > max_epos)
1829 		/*
1830 		 * Don't go past the max position we're allowed.
1831 		 */
1832 		epos = max_epos;
1833 
1834 	if (epos == NULL_POSITION || epos > spos)
1835 	{
1836 		int search_type = SRCH_FORW | SRCH_FIND_ALL;
1837 		search_type |= (search_info.search_type & SRCH_NO_REGEX);
1838 		for (;;)
1839 		{
1840 			result = search_range(spos, epos, search_type, 0, maxlines, (POSITION*)NULL, &new_epos, (POSITION*)NULL);
1841 			if (result < 0)
1842 				return;
1843 			if (prep_endpos == NULL_POSITION || new_epos > prep_endpos)
1844 				nprep_endpos = new_epos;
1845 
1846 			/*
1847 			 * Check both ends of the resulting prep region to
1848 			 * make sure they're not filtered. If they are,
1849 			 * keep going at least one more line until we find
1850 			 * something that isn't filtered, or hit the end.
1851 			 */
1852 			if (prep_endpos == NULL_POSITION || nprep_endpos > prep_endpos)
1853 			{
1854 				if (new_epos >= nprep_endpos && is_filtered(new_epos-1))
1855 				{
1856 					spos = nprep_endpos;
1857 					epos = forw_raw_line(nprep_endpos, (char **)NULL, (int *)NULL);
1858 					if (epos == NULL_POSITION)
1859 						break;
1860 					maxlines = 1;
1861 					continue;
1862 				}
1863 			}
1864 
1865 			if (prep_startpos == NULL_POSITION || nprep_startpos < prep_startpos)
1866 			{
1867 				if (nprep_startpos > 0 && is_filtered(nprep_startpos))
1868 				{
1869 					epos = nprep_startpos;
1870 					spos = back_raw_line(nprep_startpos, (char **)NULL, (int *)NULL);
1871 					if (spos == NULL_POSITION)
1872 						break;
1873 					nprep_startpos = spos;
1874 					maxlines = 1;
1875 					continue;
1876 				}
1877 			}
1878 			break;
1879 		}
1880 	}
1881 	prep_startpos = nprep_startpos;
1882 	prep_endpos = nprep_endpos;
1883 }
1884 
1885 /*
1886  * Set the pattern to be used for line filtering.
1887  */
1888 	public void
1889 set_filter_pattern(pattern, search_type)
1890 	char *pattern;
1891 	int search_type;
1892 {
1893 	struct pattern_info *filter;
1894 
1895 	clr_filter();
1896 	if (pattern == NULL || *pattern == '\0')
1897 	{
1898 		/* Clear and free all filters. */
1899 		for (filter = filter_infos; filter != NULL; )
1900 		{
1901 			struct pattern_info *next_filter = filter->next;
1902 			clear_pattern(filter);
1903 			free(filter);
1904 			filter = next_filter;
1905 		}
1906 		filter_infos = NULL;
1907 	} else
1908 	{
1909 		/* Create a new filter and add it to the filter_infos list. */
1910 		filter = ecalloc(1, sizeof(struct pattern_info));
1911 		init_pattern(filter);
1912 		if (set_pattern(filter, pattern, search_type, 1) < 0)
1913 		{
1914 			free(filter);
1915 			return;
1916 		}
1917 		filter->next = filter_infos;
1918 		filter_infos = filter;
1919 	}
1920 	screen_trashed = 1;
1921 }
1922 
1923 /*
1924  * Is there a line filter in effect?
1925  */
1926 	public int
1927 is_filtering(VOID_PARAM)
1928 {
1929 	if (ch_getflags() & CH_HELPFILE)
1930 		return (0);
1931 	return (filter_infos != NULL);
1932 }
1933 #endif
1934 
1935 #if HAVE_V8_REGCOMP
1936 /*
1937  * This function is called by the V8 regcomp to report
1938  * errors in regular expressions.
1939  */
1940 public int reg_show_error = 1;
1941 
1942 	void
1943 regerror(s)
1944 	char *s;
1945 {
1946 	PARG parg;
1947 
1948 	if (!reg_show_error)
1949 		return;
1950 	parg.p_string = s;
1951 	error("%s", &parg);
1952 }
1953 #endif
1954 
1955