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