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