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