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