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