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