xref: /openbsd/usr.bin/less/search.c (revision 404b540a)
1 /*
2  * Copyright (C) 1984-2002  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 about less, or for information on how to
8  * contact the author, see the README file.
9  */
10 
11 
12 /*
13  * Routines to search a file for a pattern.
14  */
15 
16 #include "less.h"
17 #include "position.h"
18 
19 #define	MINPOS(a,b)	(((a) < (b)) ? (a) : (b))
20 #define	MAXPOS(a,b)	(((a) > (b)) ? (a) : (b))
21 
22 #if HAVE_POSIX_REGCOMP
23 #include <regex.h>
24 #ifdef REG_EXTENDED
25 #define	REGCOMP_FLAG	REG_EXTENDED
26 #else
27 #define	REGCOMP_FLAG	0
28 #endif
29 #endif
30 #if HAVE_PCRE
31 #include <pcre.h>
32 #endif
33 #if HAVE_RE_COMP
34 char *re_comp();
35 int re_exec();
36 #endif
37 #if HAVE_REGCMP
38 char *regcmp();
39 char *regex();
40 extern char *__loc1;
41 #endif
42 #if HAVE_V8_REGCOMP
43 #include "regexp.h"
44 #endif
45 
46 static int match();
47 
48 extern int sigs;
49 extern int how_search;
50 extern int caseless;
51 extern int linenums;
52 extern int sc_height;
53 extern int jump_sline;
54 extern int bs_mode;
55 extern int ctldisp;
56 extern int status_col;
57 extern POSITION start_attnpos;
58 extern POSITION end_attnpos;
59 #if HILITE_SEARCH
60 extern int hilite_search;
61 extern int screen_trashed;
62 extern int size_linebuf;
63 extern int squished;
64 extern int can_goto_line;
65 static int hide_hilite;
66 static POSITION prep_startpos;
67 static POSITION prep_endpos;
68 
69 struct hilite
70 {
71 	struct hilite *hl_next;
72 	POSITION hl_startpos;
73 	POSITION hl_endpos;
74 };
75 static struct hilite hilite_anchor = { NULL, NULL_POSITION, NULL_POSITION };
76 #define	hl_first	hl_next
77 #endif
78 
79 /*
80  * These are the static variables that represent the "remembered"
81  * search pattern.
82  */
83 #if HAVE_POSIX_REGCOMP
84 static regex_t *regpattern = NULL;
85 #endif
86 #if HAVE_PCRE
87 pcre *regpattern = NULL;
88 #endif
89 #if HAVE_RE_COMP
90 int re_pattern = 0;
91 #endif
92 #if HAVE_REGCMP
93 static char *cpattern = NULL;
94 #endif
95 #if HAVE_V8_REGCOMP
96 static struct regexp *regpattern = NULL;
97 #endif
98 
99 static int is_caseless;
100 static int is_ucase_pattern;
101 static int last_search_type;
102 static char *last_pattern = NULL;
103 
104 /*
105  * Convert text.  Perform one or more of these transformations:
106  */
107 #define	CVT_TO_LC	01	/* Convert upper-case to lower-case */
108 #define	CVT_BS		02	/* Do backspace processing */
109 #define	CVT_CRLF	04	/* Remove CR after LF */
110 #define	CVT_ANSI	010	/* Remove ANSI escape sequences */
111 
112 	static void
113 cvt_text(odst, osrc, ops)
114 	char *odst;
115 	char *osrc;
116 	int ops;
117 {
118 	register char *dst;
119 	register char *src;
120 
121 	for (src = osrc, dst = odst;  *src != '\0';  src++)
122 	{
123 		if ((ops & CVT_TO_LC) && isupper((unsigned char) *src))
124 			/* Convert uppercase to lowercase. */
125 			*dst++ = tolower((unsigned char) *src);
126 		else if ((ops & CVT_BS) && *src == '\b' && dst > odst)
127 			/* Delete BS and preceding char. */
128 			dst--;
129 		else if ((ops & CVT_ANSI) && *src == ESC)
130 		{
131 			/* Skip to end of ANSI escape sequence. */
132 			while (src[1] != '\0')
133 				if (is_ansi_end(*++src))
134 					break;
135 		} else
136 			/* Just copy. */
137 			*dst++ = *src;
138 	}
139 	if ((ops & CVT_CRLF) && dst > odst && dst[-1] == '\r')
140 		dst--;
141 	*dst = '\0';
142 }
143 
144 /*
145  * Determine which conversions to perform.
146  */
147 	static int
148 get_cvt_ops()
149 {
150 	int ops = 0;
151 	if (is_caseless || bs_mode == BS_SPECIAL)
152 	{
153 		if (is_caseless)
154 			ops |= CVT_TO_LC;
155 		if (bs_mode == BS_SPECIAL)
156 			ops |= CVT_BS;
157 		if (bs_mode != BS_CONTROL)
158 			ops |= CVT_CRLF;
159 	} else if (bs_mode != BS_CONTROL)
160 	{
161 		ops |= CVT_CRLF;
162 	}
163 	if (ctldisp == OPT_ONPLUS)
164 		ops |= CVT_ANSI;
165 	return (ops);
166 }
167 
168 /*
169  * Are there any uppercase letters in this string?
170  */
171 	static int
172 is_ucase(s)
173 	char *s;
174 {
175 	register char *p;
176 
177 	for (p = s;  *p != '\0';  p++)
178 		if (isupper((unsigned char) *p))
179 			return (1);
180 	return (0);
181 }
182 
183 /*
184  * Is there a previous (remembered) search pattern?
185  */
186 	static int
187 prev_pattern()
188 {
189 	if (last_search_type & SRCH_NO_REGEX)
190 		return (last_pattern != NULL);
191 #if HAVE_POSIX_REGCOMP
192 	return (regpattern != NULL);
193 #endif
194 #if HAVE_PCRE
195 	return (regpattern != NULL);
196 #endif
197 #if HAVE_RE_COMP
198 	return (re_pattern != 0);
199 #endif
200 #if HAVE_REGCMP
201 	return (cpattern != NULL);
202 #endif
203 #if HAVE_V8_REGCOMP
204 	return (regpattern != NULL);
205 #endif
206 #if NO_REGEX
207 	return (last_pattern != NULL);
208 #endif
209 }
210 
211 #if HILITE_SEARCH
212 /*
213  * Repaint the hilites currently displayed on the screen.
214  * Repaint each line which contains highlighted text.
215  * If on==0, force all hilites off.
216  */
217 	public void
218 repaint_hilite(on)
219 	int on;
220 {
221 	int slinenum;
222 	POSITION pos;
223 	POSITION epos;
224 	int save_hide_hilite;
225 
226 	if (squished)
227 		repaint();
228 
229 	save_hide_hilite = hide_hilite;
230 	if (!on)
231 	{
232 		if (hide_hilite)
233 			return;
234 		hide_hilite = 1;
235 	}
236 
237 	if (!can_goto_line)
238 	{
239 		repaint();
240 		hide_hilite = save_hide_hilite;
241 		return;
242 	}
243 
244 	for (slinenum = TOP;  slinenum < TOP + sc_height-1;  slinenum++)
245 	{
246 		pos = position(slinenum);
247 		if (pos == NULL_POSITION)
248 			continue;
249 		epos = position(slinenum+1);
250 		/*
251 		 * If any character in the line is highlighted,
252 		 * repaint the line.
253 		 */
254 		if (is_hilited(pos, epos, 1))
255 		{
256 			(void) forw_line(pos);
257 			goto_line(slinenum);
258 			put_line();
259 		}
260 	}
261 	hide_hilite = save_hide_hilite;
262 }
263 
264 /*
265  * Clear the attn hilite.
266  */
267 	public void
268 clear_attn()
269 {
270 	int slinenum;
271 	POSITION old_start_attnpos;
272 	POSITION old_end_attnpos;
273 	POSITION pos;
274 	POSITION epos;
275 
276 	if (start_attnpos == NULL_POSITION)
277 		return;
278 	old_start_attnpos = start_attnpos;
279 	old_end_attnpos = end_attnpos;
280 	start_attnpos = end_attnpos = NULL_POSITION;
281 
282 	if (!can_goto_line)
283 	{
284 		repaint();
285 		return;
286 	}
287 	if (squished)
288 		repaint();
289 
290 	for (slinenum = TOP;  slinenum < TOP + sc_height-1;  slinenum++)
291 	{
292 		pos = position(slinenum);
293 		if (pos == NULL_POSITION)
294 			continue;
295 		epos = position(slinenum+1);
296 		if (pos < old_end_attnpos &&
297 		     (epos == NULL_POSITION || epos > old_start_attnpos))
298 		{
299 			(void) forw_line(pos);
300 			goto_line(slinenum);
301 			put_line();
302 		}
303 	}
304 }
305 #endif
306 
307 /*
308  * Hide search string highlighting.
309  */
310 	public void
311 undo_search()
312 {
313 	if (!prev_pattern())
314 	{
315 		error("No previous regular expression", NULL_PARG);
316 		return;
317 	}
318 #if HILITE_SEARCH
319 	hide_hilite = !hide_hilite;
320 	repaint_hilite(1);
321 #endif
322 }
323 
324 /*
325  * Compile a search pattern, for future use by match_pattern.
326  */
327 	static int
328 compile_pattern(pattern, search_type)
329 	char *pattern;
330 	int search_type;
331 {
332 	if ((search_type & SRCH_NO_REGEX) == 0)
333 	{
334 #if HAVE_POSIX_REGCOMP
335 		regex_t *s = (regex_t *) ecalloc(1, sizeof(regex_t));
336 		if (regcomp(s, pattern, REGCOMP_FLAG))
337 		{
338 			free(s);
339 			error("Invalid pattern", NULL_PARG);
340 			return (-1);
341 		}
342 		if (regpattern != NULL)
343 			regfree(regpattern);
344 		regpattern = s;
345 #endif
346 #if HAVE_PCRE
347 		pcre *comp;
348 		const char *errstring;
349 		int erroffset;
350 		PARG parg;
351 		comp = pcre_compile(pattern, 0,
352 				&errstring, &erroffset, NULL);
353 		if (comp == NULL)
354 		{
355 			parg.p_string = (char *) errstring;
356 			error("%s", &parg);
357 			return (-1);
358 		}
359 		regpattern = comp;
360 #endif
361 #if HAVE_RE_COMP
362 		PARG parg;
363 		if ((parg.p_string = re_comp(pattern)) != NULL)
364 		{
365 			error("%s", &parg);
366 			return (-1);
367 		}
368 		re_pattern = 1;
369 #endif
370 #if HAVE_REGCMP
371 		char *s;
372 		if ((s = regcmp(pattern, 0)) == NULL)
373 		{
374 			error("Invalid pattern", NULL_PARG);
375 			return (-1);
376 		}
377 		if (cpattern != NULL)
378 			free(cpattern);
379 		cpattern = s;
380 #endif
381 #if HAVE_V8_REGCOMP
382 		struct regexp *s;
383 		if ((s = regcomp(pattern)) == NULL)
384 		{
385 			/*
386 			 * regcomp has already printed an error message
387 			 * via regerror().
388 			 */
389 			return (-1);
390 		}
391 		if (regpattern != NULL)
392 			free(regpattern);
393 		regpattern = s;
394 #endif
395 	}
396 
397 	if (last_pattern != NULL)
398 		free(last_pattern);
399 	last_pattern = save(pattern);
400 
401 	last_search_type = search_type;
402 	return (0);
403 }
404 
405 /*
406  * Forget that we have a compiled pattern.
407  */
408 	static void
409 uncompile_pattern()
410 {
411 #if HAVE_POSIX_REGCOMP
412 	if (regpattern != NULL)
413 		regfree(regpattern);
414 	regpattern = NULL;
415 #endif
416 #if HAVE_PCRE
417 	if (regpattern != NULL)
418 		pcre_free(regpattern);
419 	regpattern = NULL;
420 #endif
421 #if HAVE_RE_COMP
422 	re_pattern = 0;
423 #endif
424 #if HAVE_REGCMP
425 	if (cpattern != NULL)
426 		free(cpattern);
427 	cpattern = NULL;
428 #endif
429 #if HAVE_V8_REGCOMP
430 	if (regpattern != NULL)
431 		free(regpattern);
432 	regpattern = NULL;
433 #endif
434 	last_pattern = NULL;
435 }
436 
437 /*
438  * Perform a pattern match with the previously compiled pattern.
439  * Set sp and ep to the start and end of the matched string.
440  */
441 	static int
442 match_pattern(line, sp, ep, notbol)
443 	char *line;
444 	char **sp;
445 	char **ep;
446 	int notbol;
447 {
448 	int matched;
449 
450 	if (last_search_type & SRCH_NO_REGEX)
451 		return (match(last_pattern, line, sp, ep));
452 
453 #if HAVE_POSIX_REGCOMP
454 	{
455 		regmatch_t rm;
456 		int flags = (notbol) ? REG_NOTBOL : 0;
457 		matched = !regexec(regpattern, line, 1, &rm, flags);
458 		if (!matched)
459 			return (0);
460 #ifndef __WATCOMC__
461 		*sp = line + rm.rm_so;
462 		*ep = line + rm.rm_eo;
463 #else
464 		*sp = rm.rm_sp;
465 		*ep = rm.rm_ep;
466 #endif
467 	}
468 #endif
469 #if HAVE_PCRE
470 	{
471 		int flags = (notbol) ? PCRE_NOTBOL : 0;
472 		int ovector[3];
473 		matched = pcre_exec(regpattern, NULL, line, strlen(line),
474 			0, flags, ovector, 3) >= 0;
475 		if (!matched)
476 			return (0);
477 		*sp = line + ovector[0];
478 		*ep = line + ovector[1];
479 	}
480 #endif
481 #if HAVE_RE_COMP
482 	matched = (re_exec(line) == 1);
483 	/*
484 	 * re_exec doesn't seem to provide a way to get the matched string.
485 	 */
486 	*sp = *ep = NULL;
487 #endif
488 #if HAVE_REGCMP
489 	*ep = regex(cpattern, line);
490 	matched = (*ep != NULL);
491 	if (!matched)
492 		return (0);
493 	*sp = __loc1;
494 #endif
495 #if HAVE_V8_REGCOMP
496 #if HAVE_REGEXEC2
497 	matched = regexec2(regpattern, line, notbol);
498 #else
499 	matched = regexec(regpattern, line);
500 #endif
501 	if (!matched)
502 		return (0);
503 	*sp = regpattern->startp[0];
504 	*ep = regpattern->endp[0];
505 #endif
506 #if NO_REGEX
507 	matched = match(last_pattern, line, sp, ep);
508 #endif
509 	return (matched);
510 }
511 
512 #if HILITE_SEARCH
513 /*
514  * Clear the hilite list.
515  */
516 	public void
517 clr_hilite()
518 {
519 	struct hilite *hl;
520 	struct hilite *nexthl;
521 
522 	for (hl = hilite_anchor.hl_first;  hl != NULL;  hl = nexthl)
523 	{
524 		nexthl = hl->hl_next;
525 		free((void*)hl);
526 	}
527 	hilite_anchor.hl_first = NULL;
528 	prep_startpos = prep_endpos = NULL_POSITION;
529 }
530 
531 /*
532  * Should any characters in a specified range be highlighted?
533  * If nohide is nonzero, don't consider hide_hilite.
534  */
535 	public int
536 is_hilited(pos, epos, nohide)
537 	POSITION pos;
538 	POSITION epos;
539 	int nohide;
540 {
541 	struct hilite *hl;
542 
543 	if (!status_col &&
544 	    start_attnpos != NULL_POSITION &&
545 	    pos < end_attnpos &&
546 	     (epos == NULL_POSITION || epos > start_attnpos))
547 		/*
548 		 * The attn line overlaps this range.
549 		 */
550 		return (1);
551 
552 	if (hilite_search == 0)
553 		/*
554 		 * Not doing highlighting.
555 		 */
556 		return (0);
557 
558 	if (!nohide && hide_hilite)
559 		/*
560 		 * Highlighting is hidden.
561 		 */
562 		return (0);
563 
564 	/*
565 	 * Look at each highlight and see if any part of it falls in the range.
566 	 */
567 	for (hl = hilite_anchor.hl_first;  hl != NULL;  hl = hl->hl_next)
568 	{
569 		if (hl->hl_endpos > pos &&
570 		    (epos == NULL_POSITION || epos > hl->hl_startpos))
571 			return (1);
572 	}
573 	return (0);
574 }
575 
576 /*
577  * Add a new hilite to a hilite list.
578  */
579 	static void
580 add_hilite(anchor, hl)
581 	struct hilite *anchor;
582 	struct hilite *hl;
583 {
584 	struct hilite *ihl;
585 
586 	/*
587 	 * Hilites are sorted in the list; find where new one belongs.
588 	 * Insert new one after ihl.
589 	 */
590 	for (ihl = anchor;  ihl->hl_next != NULL;  ihl = ihl->hl_next)
591 	{
592 		if (ihl->hl_next->hl_startpos > hl->hl_startpos)
593 			break;
594 	}
595 
596 	/*
597 	 * Truncate hilite so it doesn't overlap any existing ones
598 	 * above and below it.
599 	 */
600 	if (ihl != anchor)
601 		hl->hl_startpos = MAXPOS(hl->hl_startpos, ihl->hl_endpos);
602 	if (ihl->hl_next != NULL)
603 		hl->hl_endpos = MINPOS(hl->hl_endpos, ihl->hl_next->hl_startpos);
604 	if (hl->hl_startpos >= hl->hl_endpos)
605 	{
606 		/*
607 		 * Hilite was truncated out of existence.
608 		 */
609 		free(hl);
610 		return;
611 	}
612 	hl->hl_next = ihl->hl_next;
613 	ihl->hl_next = hl;
614 }
615 
616 /*
617  * Adjust hl_startpos & hl_endpos to account for backspace processing.
618  */
619 	static void
620 adj_hilite(anchor, linepos, cvt_ops)
621 	struct hilite *anchor;
622 	POSITION linepos;
623 	int cvt_ops;
624 {
625 	char *line;
626 	struct hilite *hl;
627 	int checkstart;
628 	POSITION opos;
629 	POSITION npos;
630 
631 	/*
632 	 * The line was already scanned and hilites were added (in hilite_line).
633 	 * But it was assumed that each char position in the line
634 	 * correponds to one char position in the file.
635 	 * This may not be true if there are backspaces in the line.
636 	 * Get the raw line again.  Look at each character.
637 	 */
638 	(void) forw_raw_line(linepos, &line);
639 	opos = npos = linepos;
640 	hl = anchor->hl_first;
641 	checkstart = TRUE;
642 	while (hl != NULL)
643 	{
644 		/*
645 		 * See if we need to adjust the current hl_startpos or
646 		 * hl_endpos.  After adjusting startpos[i], move to endpos[i].
647 		 * After adjusting endpos[i], move to startpos[i+1].
648 		 * The hilite list must be sorted thus:
649 		 * startpos[0] < endpos[0] <= startpos[1] < endpos[1] <= etc.
650 		 */
651 		if (checkstart && hl->hl_startpos == opos)
652 		{
653 			hl->hl_startpos = npos;
654 			checkstart = FALSE;
655 			continue; /* {{ not really necessary }} */
656 		} else if (!checkstart && hl->hl_endpos == opos)
657 		{
658 			hl->hl_endpos = npos;
659 			checkstart = TRUE;
660 			hl = hl->hl_next;
661 			continue; /* {{ necessary }} */
662 		}
663 		if (*line == '\0')
664 			break;
665 		if (cvt_ops & CVT_ANSI)
666 		{
667 			while (line[0] == ESC)
668 			{
669 				/*
670 				 * Found an ESC.  The file position moves
671 				 * forward past the entire ANSI escape sequence.
672 				 */
673 				line++;
674 				npos++;
675 				while (*line != '\0')
676 				{
677 					npos++;
678 					if (is_ansi_end(*line++))
679 						break;
680 				}
681 			}
682 		}
683 		opos++;
684 		npos++;
685 		line++;
686 		if (cvt_ops & CVT_BS)
687 		{
688 			while (line[0] == '\b' && line[1] != '\0')
689 			{
690 				/*
691 				 * Found a backspace.  The file position moves
692 				 * forward by 2 relative to the processed line
693 				 * which was searched in hilite_line.
694 				 */
695 				npos += 2;
696 				line += 2;
697 			}
698 		}
699 	}
700 }
701 
702 /*
703  * Make a hilite for each string in a physical line which matches
704  * the current pattern.
705  * sp,ep delimit the first match already found.
706  */
707 	static void
708 hilite_line(linepos, line, sp, ep, cvt_ops)
709 	POSITION linepos;
710 	char *line;
711 	char *sp;
712 	char *ep;
713 	int cvt_ops;
714 {
715 	char *searchp;
716 	struct hilite *hl;
717 	struct hilite hilites;
718 
719 	if (sp == NULL || ep == NULL)
720 		return;
721 	/*
722 	 * sp and ep delimit the first match in the line.
723 	 * Mark the corresponding file positions, then
724 	 * look for further matches and mark them.
725 	 * {{ This technique, of calling match_pattern on subsequent
726 	 *    substrings of the line, may mark more than is correct
727 	 *    if the pattern starts with "^".  This bug is fixed
728 	 *    for those regex functions that accept a notbol parameter
729 	 *    (currently POSIX and V8-with-regexec2). }}
730 	 */
731 	searchp = line;
732 	/*
733 	 * Put the hilites into a temporary list until they're adjusted.
734 	 */
735 	hilites.hl_first = NULL;
736 	do {
737 		if (ep > sp)
738 		{
739 			/*
740 			 * Assume that each char position in the "line"
741 			 * buffer corresponds to one char position in the file.
742 			 * This is not quite true; we need to adjust later.
743 			 */
744 			hl = (struct hilite *) ecalloc(1, sizeof(struct hilite));
745 			hl->hl_startpos = linepos + (sp-line);
746 			hl->hl_endpos = linepos + (ep-line);
747 			add_hilite(&hilites, hl);
748 		}
749 		/*
750 		 * If we matched more than zero characters,
751 		 * move to the first char after the string we matched.
752 		 * If we matched zero, just move to the next char.
753 		 */
754 		if (ep > searchp)
755 			searchp = ep;
756 		else if (*searchp != '\0')
757 			searchp++;
758 		else /* end of line */
759 			break;
760 	} while (match_pattern(searchp, &sp, &ep, 1));
761 
762 	/*
763 	 * If there were backspaces in the original line, they
764 	 * were removed, and hl_startpos/hl_endpos are not correct.
765 	 * {{ This is very ugly. }}
766 	 */
767 	adj_hilite(&hilites, linepos, cvt_ops);
768 
769 	/*
770 	 * Now put the hilites into the real list.
771 	 */
772 	while ((hl = hilites.hl_next) != NULL)
773 	{
774 		hilites.hl_next = hl->hl_next;
775 		add_hilite(&hilite_anchor, hl);
776 	}
777 }
778 #endif
779 
780 /*
781  * Change the caseless-ness of searches.
782  * Updates the internal search state to reflect a change in the -i flag.
783  */
784 	public void
785 chg_caseless()
786 {
787 	if (!is_ucase_pattern)
788 		/*
789 		 * Pattern did not have uppercase.
790 		 * Just set the search caselessness to the global caselessness.
791 		 */
792 		is_caseless = caseless;
793 	else
794 		/*
795 		 * Pattern did have uppercase.
796 		 * Discard the pattern; we can't change search caselessness now.
797 		 */
798 		uncompile_pattern();
799 }
800 
801 #if HILITE_SEARCH
802 /*
803  * Find matching text which is currently on screen and highlight it.
804  */
805 	static void
806 hilite_screen()
807 {
808 	struct scrpos scrpos;
809 
810 	get_scrpos(&scrpos);
811 	if (scrpos.pos == NULL_POSITION)
812 		return;
813 	prep_hilite(scrpos.pos, position(BOTTOM_PLUS_ONE), -1);
814 	repaint_hilite(1);
815 }
816 
817 /*
818  * Change highlighting parameters.
819  */
820 	public void
821 chg_hilite()
822 {
823 	/*
824 	 * Erase any highlights currently on screen.
825 	 */
826 	clr_hilite();
827 	hide_hilite = 0;
828 
829 	if (hilite_search == OPT_ONPLUS)
830 		/*
831 		 * Display highlights.
832 		 */
833 		hilite_screen();
834 }
835 #endif
836 
837 /*
838  * Figure out where to start a search.
839  */
840 	static POSITION
841 search_pos(search_type)
842 	int search_type;
843 {
844 	POSITION pos;
845 	int linenum;
846 
847 	if (empty_screen())
848 	{
849 		/*
850 		 * Start at the beginning (or end) of the file.
851 		 * The empty_screen() case is mainly for
852 		 * command line initiated searches;
853 		 * for example, "+/xyz" on the command line.
854 		 * Also for multi-file (SRCH_PAST_EOF) searches.
855 		 */
856 		if (search_type & SRCH_FORW)
857 		{
858 			return (ch_zero());
859 		} else
860 		{
861 			pos = ch_length();
862 			if (pos == NULL_POSITION)
863 			{
864 				(void) ch_end_seek();
865 				pos = ch_length();
866 			}
867 			return (pos);
868 		}
869 	}
870 	if (how_search)
871 	{
872 		/*
873 		 * Search does not include current screen.
874 		 */
875 		if (search_type & SRCH_FORW)
876 			linenum = BOTTOM_PLUS_ONE;
877 		else
878 			linenum = TOP;
879 		pos = position(linenum);
880 	} else
881 	{
882 		/*
883 		 * Search includes current screen.
884 		 * It starts at the jump target (if searching backwards),
885 		 * or at the jump target plus one (if forwards).
886 		 */
887 		linenum = adjsline(jump_sline);
888 		pos = position(linenum);
889 		if (search_type & SRCH_FORW)
890 		{
891 			pos = forw_raw_line(pos, (char **)NULL);
892 			while (pos == NULL_POSITION)
893 			{
894 				if (++linenum >= sc_height)
895 					break;
896 				pos = position(linenum);
897 			}
898 		} else
899 		{
900 			while (pos == NULL_POSITION)
901 			{
902 				if (--linenum < 0)
903 					break;
904 				pos = position(linenum);
905 			}
906 		}
907 	}
908 	return (pos);
909 }
910 
911 /*
912  * Search a subset of the file, specified by start/end position.
913  */
914 	static int
915 search_range(pos, endpos, search_type, matches, maxlines, plinepos, pendpos)
916 	POSITION pos;
917 	POSITION endpos;
918 	int search_type;
919 	int matches;
920 	int maxlines;
921 	POSITION *plinepos;
922 	POSITION *pendpos;
923 {
924 	char *line;
925 	LINENUM linenum;
926 	char *sp, *ep;
927 	int line_match;
928 	int cvt_ops;
929 	POSITION linepos, oldpos;
930 
931 	linenum = find_linenum(pos);
932 	oldpos = pos;
933 	for (;;)
934 	{
935 		/*
936 		 * Get lines until we find a matching one or until
937 		 * we hit end-of-file (or beginning-of-file if we're
938 		 * going backwards), or until we hit the end position.
939 		 */
940 		if (ABORT_SIGS())
941 		{
942 			/*
943 			 * A signal aborts the search.
944 			 */
945 			return (-1);
946 		}
947 
948 		if ((endpos != NULL_POSITION && pos >= endpos) || maxlines == 0)
949 		{
950 			/*
951 			 * Reached end position without a match.
952 			 */
953 			if (pendpos != NULL)
954 				*pendpos = pos;
955 			return (matches);
956 		}
957 		if (maxlines > 0)
958 			maxlines--;
959 
960 		if (search_type & SRCH_FORW)
961 		{
962 			/*
963 			 * Read the next line, and save the
964 			 * starting position of that line in linepos.
965 			 */
966 			linepos = pos;
967 			pos = forw_raw_line(pos, &line);
968 			if (linenum != 0)
969 				linenum++;
970 		} else
971 		{
972 			/*
973 			 * Read the previous line and save the
974 			 * starting position of that line in linepos.
975 			 */
976 			pos = back_raw_line(pos, &line);
977 			linepos = pos;
978 			if (linenum != 0)
979 				linenum--;
980 		}
981 
982 		if (pos == NULL_POSITION)
983 		{
984 			/*
985 			 * Reached EOF/BOF without a match.
986 			 */
987 			if (pendpos != NULL)
988 				*pendpos = oldpos;
989 			return (matches);
990 		}
991 
992 		/*
993 		 * If we're using line numbers, we might as well
994 		 * remember the information we have now (the position
995 		 * and line number of the current line).
996 		 * Don't do it for every line because it slows down
997 		 * the search.  Remember the line number only if
998 		 * we're "far" from the last place we remembered it.
999 		 */
1000 		if (linenums && abs((int)(pos - oldpos)) > 1024)
1001 			add_lnum(linenum, pos);
1002 		oldpos = pos;
1003 
1004 		/*
1005 		 * If it's a caseless search, convert the line to lowercase.
1006 		 * If we're doing backspace processing, delete backspaces.
1007 		 */
1008 		cvt_ops = get_cvt_ops();
1009 		cvt_text(line, line, cvt_ops);
1010 
1011 		/*
1012 		 * Test the next line to see if we have a match.
1013 		 * We are successful if we either want a match and got one,
1014 		 * or if we want a non-match and got one.
1015 		 */
1016 		line_match = match_pattern(line, &sp, &ep, 0);
1017 		line_match = (!(search_type & SRCH_NO_MATCH) && line_match) ||
1018 				((search_type & SRCH_NO_MATCH) && !line_match);
1019 		if (!line_match)
1020 			continue;
1021 		/*
1022 		 * Got a match.
1023 		 */
1024 		if (search_type & SRCH_FIND_ALL)
1025 		{
1026 #if HILITE_SEARCH
1027 			/*
1028 			 * We are supposed to find all matches in the range.
1029 			 * Just add the matches in this line to the
1030 			 * hilite list and keep searching.
1031 			 */
1032 			if (line_match)
1033 				hilite_line(linepos, line, sp, ep, cvt_ops);
1034 #endif
1035 		} else if (--matches <= 0)
1036 		{
1037 			/*
1038 			 * Found the one match we're looking for.
1039 			 * Return it.
1040 			 */
1041 #if HILITE_SEARCH
1042 			if (hilite_search == 1)
1043 			{
1044 				/*
1045 				 * Clear the hilite list and add only
1046 				 * the matches in this one line.
1047 				 */
1048 				clr_hilite();
1049 				if (line_match)
1050 					hilite_line(linepos, line, sp, ep, cvt_ops);
1051 			}
1052 #endif
1053 			if (plinepos != NULL)
1054 				*plinepos = linepos;
1055 			return (0);
1056 		}
1057 	}
1058 }
1059 
1060 /*
1061  * Search for the n-th occurrence of a specified pattern,
1062  * either forward or backward.
1063  * Return the number of matches not yet found in this file
1064  * (that is, n minus the number of matches found).
1065  * Return -1 if the search should be aborted.
1066  * Caller may continue the search in another file
1067  * if less than n matches are found in this file.
1068  */
1069 	public int
1070 search(search_type, pattern, n)
1071 	int search_type;
1072 	char *pattern;
1073 	int n;
1074 {
1075 	POSITION pos;
1076 	int ucase;
1077 
1078 	if (pattern == NULL || *pattern == '\0')
1079 	{
1080 		/*
1081 		 * A null pattern means use the previously compiled pattern.
1082 		 */
1083 		if (!prev_pattern())
1084 		{
1085 			error("No previous regular expression", NULL_PARG);
1086 			return (-1);
1087 		}
1088 		if ((search_type & SRCH_NO_REGEX) !=
1089 		    (last_search_type & SRCH_NO_REGEX))
1090 		{
1091 			error("Please re-enter search pattern", NULL_PARG);
1092 			return -1;
1093 		}
1094 #if HILITE_SEARCH
1095 		if (hilite_search == OPT_ON)
1096 		{
1097 			/*
1098 			 * Erase the highlights currently on screen.
1099 			 * If the search fails, we'll redisplay them later.
1100 			 */
1101 			repaint_hilite(0);
1102 		}
1103 		if (hilite_search == OPT_ONPLUS && hide_hilite)
1104 		{
1105 			/*
1106 			 * Highlight any matches currently on screen,
1107 			 * before we actually start the search.
1108 			 */
1109 			hide_hilite = 0;
1110 			hilite_screen();
1111 		}
1112 		hide_hilite = 0;
1113 #endif
1114 	} else
1115 	{
1116 		/*
1117 		 * Compile the pattern.
1118 		 */
1119 		ucase = is_ucase(pattern);
1120 		if (caseless == OPT_ONPLUS)
1121 			cvt_text(pattern, pattern, CVT_TO_LC);
1122 		if (compile_pattern(pattern, search_type) < 0)
1123 			return (-1);
1124 		/*
1125 		 * Ignore case if -I is set OR
1126 		 * -i is set AND the pattern is all lowercase.
1127 		 */
1128 		is_ucase_pattern = ucase;
1129 		if (is_ucase_pattern && caseless != OPT_ONPLUS)
1130 			is_caseless = 0;
1131 		else
1132 			is_caseless = caseless;
1133 #if HILITE_SEARCH
1134 		if (hilite_search)
1135 		{
1136 			/*
1137 			 * Erase the highlights currently on screen.
1138 			 * Also permanently delete them from the hilite list.
1139 			 */
1140 			repaint_hilite(0);
1141 			hide_hilite = 0;
1142 			clr_hilite();
1143 		}
1144 		if (hilite_search == OPT_ONPLUS)
1145 		{
1146 			/*
1147 			 * Highlight any matches currently on screen,
1148 			 * before we actually start the search.
1149 			 */
1150 			hilite_screen();
1151 		}
1152 #endif
1153 	}
1154 
1155 	/*
1156 	 * Figure out where to start the search.
1157 	 */
1158 	pos = search_pos(search_type);
1159 	if (pos == NULL_POSITION)
1160 	{
1161 		/*
1162 		 * Can't find anyplace to start searching from.
1163 		 */
1164 		if (search_type & SRCH_PAST_EOF)
1165 			return (n);
1166 		/* repaint(); -- why was this here? */
1167 		error("Nothing to search", NULL_PARG);
1168 		return (-1);
1169 	}
1170 
1171 	n = search_range(pos, NULL_POSITION, search_type, n, -1,
1172 			&pos, (POSITION*)NULL);
1173 	if (n != 0)
1174 	{
1175 		/*
1176 		 * Search was unsuccessful.
1177 		 */
1178 #if HILITE_SEARCH
1179 		if (hilite_search == OPT_ON && n > 0)
1180 			/*
1181 			 * Redisplay old hilites.
1182 			 */
1183 			repaint_hilite(1);
1184 #endif
1185 		return (n);
1186 	}
1187 
1188 	if (!(search_type & SRCH_NO_MOVE))
1189 	{
1190 		/*
1191 		 * Go to the matching line.
1192 		 */
1193 		jump_loc(pos, jump_sline);
1194 	}
1195 
1196 #if HILITE_SEARCH
1197 	if (hilite_search == OPT_ON)
1198 		/*
1199 		 * Display new hilites in the matching line.
1200 		 */
1201 		repaint_hilite(1);
1202 #endif
1203 	return (0);
1204 }
1205 
1206 
1207 #if HILITE_SEARCH
1208 /*
1209  * Prepare hilites in a given range of the file.
1210  *
1211  * The pair (prep_startpos,prep_endpos) delimits a contiguous region
1212  * of the file that has been "prepared"; that is, scanned for matches for
1213  * the current search pattern, and hilites have been created for such matches.
1214  * If prep_startpos == NULL_POSITION, the prep region is empty.
1215  * If prep_endpos == NULL_POSITION, the prep region extends to EOF.
1216  * prep_hilite asks that the range (spos,epos) be covered by the prep region.
1217  */
1218 	public void
1219 prep_hilite(spos, epos, maxlines)
1220 	POSITION spos;
1221 	POSITION epos;
1222 	int maxlines;
1223 {
1224 	POSITION nprep_startpos = prep_startpos;
1225 	POSITION nprep_endpos = prep_endpos;
1226 	POSITION new_epos;
1227 	POSITION max_epos;
1228 	int result;
1229 	int i;
1230 /*
1231  * Search beyond where we're asked to search, so the prep region covers
1232  * more than we need.  Do one big search instead of a bunch of small ones.
1233  */
1234 #define	SEARCH_MORE (3*size_linebuf)
1235 
1236 	if (!prev_pattern())
1237 		return;
1238 
1239 	/*
1240 	 * If we're limited to a max number of lines, figure out the
1241 	 * file position we should stop at.
1242 	 */
1243 	if (maxlines < 0)
1244 		max_epos = NULL_POSITION;
1245 	else
1246 	{
1247 		max_epos = spos;
1248 		for (i = 0;  i < maxlines;  i++)
1249 			max_epos = forw_raw_line(max_epos, (char **)NULL);
1250 	}
1251 
1252 	/*
1253 	 * Find two ranges:
1254 	 * The range that we need to search (spos,epos); and the range that
1255 	 * the "prep" region will then cover (nprep_startpos,nprep_endpos).
1256 	 */
1257 
1258 	if (prep_startpos == NULL_POSITION ||
1259 	    (epos != NULL_POSITION && epos < prep_startpos) ||
1260 	    spos > prep_endpos)
1261 	{
1262 		/*
1263 		 * New range is not contiguous with old prep region.
1264 		 * Discard the old prep region and start a new one.
1265 		 */
1266 		clr_hilite();
1267 		if (epos != NULL_POSITION)
1268 			epos += SEARCH_MORE;
1269 		nprep_startpos = spos;
1270 	} else
1271 	{
1272 		/*
1273 		 * New range partially or completely overlaps old prep region.
1274 		 */
1275 		if (epos == NULL_POSITION)
1276 		{
1277 			/*
1278 			 * New range goes to end of file.
1279 			 */
1280 			;
1281 		} else if (epos > prep_endpos)
1282 		{
1283 			/*
1284 			 * New range ends after old prep region.
1285 			 * Extend prep region to end at end of new range.
1286 			 */
1287 			epos += SEARCH_MORE;
1288 		} else /* (epos <= prep_endpos) */
1289 		{
1290 			/*
1291 			 * New range ends within old prep region.
1292 			 * Truncate search to end at start of old prep region.
1293 			 */
1294 			epos = prep_startpos;
1295 		}
1296 
1297 		if (spos < prep_startpos)
1298 		{
1299 			/*
1300 			 * New range starts before old prep region.
1301 			 * Extend old prep region backwards to start at
1302 			 * start of new range.
1303 			 */
1304 			if (spos < SEARCH_MORE)
1305 				spos = 0;
1306 			else
1307 				spos -= SEARCH_MORE;
1308 			nprep_startpos = spos;
1309 		} else /* (spos >= prep_startpos) */
1310 		{
1311 			/*
1312 			 * New range starts within or after old prep region.
1313 			 * Trim search to start at end of old prep region.
1314 			 */
1315 			spos = prep_endpos;
1316 		}
1317 	}
1318 
1319 	if (epos != NULL_POSITION && max_epos != NULL_POSITION &&
1320 	    epos > max_epos)
1321 		/*
1322 		 * Don't go past the max position we're allowed.
1323 		 */
1324 		epos = max_epos;
1325 
1326 	if (epos == NULL_POSITION || epos > spos)
1327 	{
1328 		result = search_range(spos, epos, SRCH_FORW|SRCH_FIND_ALL, 0,
1329 				maxlines, (POSITION*)NULL, &new_epos);
1330 		if (result < 0)
1331 			return;
1332 		if (prep_endpos == NULL_POSITION || new_epos > prep_endpos)
1333 			nprep_endpos = new_epos;
1334 	}
1335 	prep_startpos = nprep_startpos;
1336 	prep_endpos = nprep_endpos;
1337 }
1338 #endif
1339 
1340 /*
1341  * Simple pattern matching function.
1342  * It supports no metacharacters like *, etc.
1343  */
1344 	static int
1345 match(pattern, buf, pfound, pend)
1346 	char *pattern, *buf;
1347 	char **pfound, **pend;
1348 {
1349 	register char *pp, *lp;
1350 
1351 	for ( ;  *buf != '\0';  buf++)
1352 	{
1353 		for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
1354 			if (*pp == '\0' || *lp == '\0')
1355 				break;
1356 		if (*pp == '\0')
1357 		{
1358 			if (pfound != NULL)
1359 				*pfound = buf;
1360 			if (pend != NULL)
1361 				*pend = lp;
1362 			return (1);
1363 		}
1364 	}
1365 	return (0);
1366 }
1367 
1368 #if HAVE_V8_REGCOMP
1369 /*
1370  * This function is called by the V8 regcomp to report
1371  * errors in regular expressions.
1372  */
1373 	void
1374 regerror(s)
1375 	char *s;
1376 {
1377 	PARG parg;
1378 
1379 	parg.p_string = s;
1380 	error("%s", &parg);
1381 }
1382 #endif
1383 
1384