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