1 /* $OpenBSD: search.c,v 1.47 2018/07/11 12:21:37 krw Exp $ */ 2 3 /* This file is in the public domain. */ 4 5 /* 6 * Search commands. 7 * The functions in this file implement the search commands (both plain and 8 * incremental searches are supported) and the query-replace command. 9 * 10 * The plain old search code is part of the original MicroEMACS "distribution". 11 * The incremental search code and the query-replace code is by Rich Ellison. 12 */ 13 14 #include <sys/queue.h> 15 #include <ctype.h> 16 #include <signal.h> 17 #include <stdio.h> 18 #include <string.h> 19 20 #include "def.h" 21 #include "macro.h" 22 23 #define SRCH_BEGIN (0) /* Search sub-codes. */ 24 #define SRCH_FORW (-1) 25 #define SRCH_BACK (-2) 26 #define SRCH_NOPR (-3) 27 #define SRCH_ACCM (-4) 28 #define SRCH_MARK (-5) 29 30 struct srchcom { 31 int s_code; 32 struct line *s_dotp; 33 int s_doto; 34 int s_dotline; 35 }; 36 37 static int isearch(int); 38 static void is_cpush(int); 39 static void is_lpush(void); 40 static void is_pop(void); 41 static int is_peek(void); 42 static void is_undo(int *, int *); 43 static int is_find(int); 44 static void is_prompt(int, int, int); 45 static void is_dspl(char *, int); 46 static int eq(int, int, int); 47 48 static struct srchcom cmds[NSRCH]; 49 static int cip; 50 51 int srch_lastdir = SRCH_NOPR; /* Last search flags. */ 52 53 /* 54 * Search forward. Get a search string from the user, and search for it 55 * starting at ".". If found, "." gets moved to just after the matched 56 * characters, and display does all the hard stuff. If not found, it just 57 * prints a message. 58 */ 59 /* ARGSUSED */ 60 int 61 forwsearch(int f, int n) 62 { 63 int s; 64 65 if ((s = readpattern("Search")) != TRUE) 66 return (s); 67 if (forwsrch() == FALSE) { 68 dobeep(); 69 ewprintf("Search failed: \"%s\"", pat); 70 return (FALSE); 71 } 72 srch_lastdir = SRCH_FORW; 73 return (TRUE); 74 } 75 76 /* 77 * Reverse search. Get a search string from the user, and search, starting 78 * at "." and proceeding toward the front of the buffer. If found "." is 79 * left pointing at the first character of the pattern [the last character 80 * that was matched]. 81 */ 82 /* ARGSUSED */ 83 int 84 backsearch(int f, int n) 85 { 86 int s; 87 88 if ((s = readpattern("Search backward")) != TRUE) 89 return (s); 90 if (backsrch() == FALSE) { 91 dobeep(); 92 ewprintf("Search failed: \"%s\"", pat); 93 return (FALSE); 94 } 95 srch_lastdir = SRCH_BACK; 96 return (TRUE); 97 } 98 99 /* 100 * Search again, using the same search string and direction as the last 101 * search command. The direction has been saved in "srch_lastdir", so you 102 * know which way to go. 103 */ 104 /* ARGSUSED */ 105 int 106 searchagain(int f, int n) 107 { 108 if (srch_lastdir == SRCH_FORW) { 109 if (forwsrch() == FALSE) { 110 dobeep(); 111 ewprintf("Search failed: \"%s\"", pat); 112 return (FALSE); 113 } 114 return (TRUE); 115 } 116 if (srch_lastdir == SRCH_BACK) { 117 if (backsrch() == FALSE) { 118 dobeep(); 119 ewprintf("Search failed: \"%s\"", pat); 120 return (FALSE); 121 } 122 return (TRUE); 123 } 124 dobeep(); 125 ewprintf("No last search"); 126 return (FALSE); 127 } 128 129 /* 130 * Use incremental searching, initially in the forward direction. 131 * isearch ignores any explicit arguments. 132 */ 133 /* ARGSUSED */ 134 int 135 forwisearch(int f, int n) 136 { 137 if (macrodef || inmacro) 138 /* We can't isearch in macro. Use search instead */ 139 return (forwsearch(f,n)); 140 else 141 return (isearch(SRCH_FORW)); 142 } 143 144 /* 145 * Use incremental searching, initially in the reverse direction. 146 * isearch ignores any explicit arguments. 147 */ 148 /* ARGSUSED */ 149 int 150 backisearch(int f, int n) 151 { 152 if (macrodef || inmacro) 153 /* We can't isearch in macro. Use search instead */ 154 return (backsearch(f,n)); 155 else 156 return (isearch(SRCH_BACK)); 157 } 158 159 /* 160 * Incremental Search. 161 * dir is used as the initial direction to search. 162 * ^M exit from Isearch, set mark 163 * ^S switch direction to forward 164 * ^R switch direction to reverse 165 * ^Q quote next character (allows searching for ^N etc.) 166 * <ESC> exit from Isearch, set mark 167 * <DEL> undoes last character typed. (tricky job to do this correctly). 168 * other ^ exit search, don't set mark 169 * else accumulate into search string 170 */ 171 static int 172 isearch(int dir) 173 { 174 struct line *clp; /* Saved line pointer */ 175 int c; 176 int cbo; /* Saved offset */ 177 int success; 178 int pptr; 179 int firstc; 180 int xcase; 181 int i; 182 char opat[NPAT]; 183 int cdotline; /* Saved line number */ 184 185 if (macrodef) { 186 dobeep(); 187 ewprintf("Can't isearch in macro"); 188 return (FALSE); 189 } 190 for (cip = 0; cip < NSRCH; cip++) 191 cmds[cip].s_code = SRCH_NOPR; 192 193 (void)strlcpy(opat, pat, sizeof(opat)); 194 cip = 0; 195 pptr = -1; 196 clp = curwp->w_dotp; 197 cbo = curwp->w_doto; 198 cdotline = curwp->w_dotline; 199 is_lpush(); 200 is_cpush(SRCH_BEGIN); 201 success = TRUE; 202 is_prompt(dir, TRUE, success); 203 204 for (;;) { 205 update(CMODE); 206 207 switch (c = getkey(FALSE)) { 208 case CCHR('['): 209 /* 210 * If new characters come in the next 300 msec, 211 * we can assume that they belong to a longer 212 * escaped sequence so we should ungetkey the 213 * ESC to avoid writing out garbage. 214 */ 215 if (ttwait(300) == FALSE) 216 ungetkey(c); 217 /* FALLTHRU */ 218 case CCHR('M'): 219 srch_lastdir = dir; 220 curwp->w_markp = clp; 221 curwp->w_marko = cbo; 222 curwp->w_markline = cdotline; 223 ewprintf("Mark set"); 224 return (TRUE); 225 case CCHR('G'): 226 if (success != TRUE) { 227 while (is_peek() == SRCH_ACCM) 228 is_undo(&pptr, &dir); 229 success = TRUE; 230 is_prompt(dir, pptr < 0, success); 231 break; 232 } 233 curwp->w_dotp = clp; 234 curwp->w_doto = cbo; 235 curwp->w_dotline = cdotline; 236 curwp->w_rflag |= WFMOVE; 237 srch_lastdir = dir; 238 (void)ctrlg(FFRAND, 0); 239 (void)strlcpy(pat, opat, sizeof(pat)); 240 return (ABORT); 241 case CCHR('S'): 242 if (dir == SRCH_BACK) { 243 dir = SRCH_FORW; 244 is_lpush(); 245 is_cpush(SRCH_FORW); 246 success = TRUE; 247 } 248 if (success == FALSE && dir == SRCH_FORW) { 249 /* wrap the search to beginning */ 250 curwp->w_dotp = bfirstlp(curbp); 251 curwp->w_doto = 0; 252 curwp->w_dotline = 1; 253 if (is_find(dir) != FALSE) { 254 is_cpush(SRCH_MARK); 255 success = TRUE; 256 } 257 ewprintf("Overwrapped I-search: %s", pat); 258 break; 259 } 260 is_lpush(); 261 pptr = strlen(pat); 262 if (forwchar(FFRAND, 1) == FALSE) { 263 dobeep(); 264 success = FALSE; 265 ewprintf("Failed I-search: %s", pat); 266 } else { 267 if (is_find(SRCH_FORW) != FALSE) 268 is_cpush(SRCH_MARK); 269 else { 270 (void)backchar(FFRAND, 1); 271 dobeep(); 272 success = FALSE; 273 ewprintf("Failed I-search: %s", pat); 274 } 275 } 276 is_prompt(dir, pptr < 0, success); 277 break; 278 case CCHR('R'): 279 if (dir == SRCH_FORW) { 280 dir = SRCH_BACK; 281 is_lpush(); 282 is_cpush(SRCH_BACK); 283 success = TRUE; 284 } 285 if (success == FALSE && dir == SRCH_BACK) { 286 /* wrap the search to end */ 287 curwp->w_dotp = blastlp(curbp); 288 curwp->w_doto = llength(curwp->w_dotp); 289 curwp->w_dotline = curwp->w_bufp->b_lines; 290 if (is_find(dir) != FALSE) { 291 is_cpush(SRCH_MARK); 292 success = TRUE; 293 } 294 ewprintf("Overwrapped I-search: %s", pat); 295 break; 296 } 297 is_lpush(); 298 pptr = strlen(pat); 299 if (backchar(FFRAND, 1) == FALSE) { 300 dobeep(); 301 success = FALSE; 302 } else { 303 if (is_find(SRCH_BACK) != FALSE) 304 is_cpush(SRCH_MARK); 305 else { 306 (void)forwchar(FFRAND, 1); 307 dobeep(); 308 success = FALSE; 309 } 310 } 311 is_prompt(dir, pptr < 0, success); 312 break; 313 case CCHR('W'): 314 /* add the rest of the current word to the pattern */ 315 clp = curwp->w_dotp; 316 cbo = curwp->w_doto; 317 firstc = 1; 318 if (pptr == -1) 319 pptr = 0; 320 if (dir == SRCH_BACK) { 321 /* when isearching backwards, cbo is the start of the pattern */ 322 cbo += pptr; 323 } 324 325 /* if the search is case insensitive, add to pattern using lowercase */ 326 xcase = 0; 327 for (i = 0; pat[i]; i++) 328 if (ISUPPER(CHARMASK(pat[i]))) 329 xcase = 1; 330 331 while (cbo < llength(clp)) { 332 c = lgetc(clp, cbo++); 333 if ((!firstc && !isalnum(c))) 334 break; 335 336 if (pptr == NPAT - 1) { 337 dobeep(); 338 break; 339 } 340 firstc = 0; 341 if (!xcase && ISUPPER(c)) 342 c = TOLOWER(c); 343 344 pat[pptr++] = c; 345 pat[pptr] = '\0'; 346 /* cursor only moves when isearching forwards */ 347 if (dir == SRCH_FORW) { 348 curwp->w_doto = cbo; 349 curwp->w_rflag |= WFMOVE; 350 update(CMODE); 351 } 352 } 353 is_prompt(dir, pptr < 0, success); 354 break; 355 case CCHR('H'): 356 case CCHR('?'): 357 is_undo(&pptr, &dir); 358 if (is_peek() != SRCH_ACCM) 359 success = TRUE; 360 is_prompt(dir, pptr < 0, success); 361 break; 362 case CCHR('\\'): 363 case CCHR('Q'): 364 c = (char)getkey(FALSE); 365 goto addchar; 366 default: 367 if (ISCTRL(c)) { 368 ungetkey(c); 369 curwp->w_markp = clp; 370 curwp->w_marko = cbo; 371 curwp->w_markline = cdotline; 372 ewprintf("Mark set"); 373 curwp->w_rflag |= WFMOVE; 374 return (TRUE); 375 } 376 /* FALLTHRU */ 377 case CCHR('I'): 378 case CCHR('J'): 379 addchar: 380 if (pptr == -1) 381 pptr = 0; 382 if (pptr == 0) 383 success = TRUE; 384 if (pptr == NPAT - 1) 385 dobeep(); 386 else { 387 pat[pptr++] = c; 388 pat[pptr] = '\0'; 389 } 390 is_lpush(); 391 if (success != FALSE) { 392 if (is_find(dir) != FALSE) 393 is_cpush(c); 394 else { 395 success = FALSE; 396 dobeep(); 397 is_cpush(SRCH_ACCM); 398 } 399 } else 400 is_cpush(SRCH_ACCM); 401 is_prompt(dir, FALSE, success); 402 } 403 } 404 /* NOTREACHED */ 405 } 406 407 static void 408 is_cpush(int cmd) 409 { 410 if (++cip >= NSRCH) 411 cip = 0; 412 cmds[cip].s_code = cmd; 413 } 414 415 static void 416 is_lpush(void) 417 { 418 int ctp; 419 420 ctp = cip + 1; 421 if (ctp >= NSRCH) 422 ctp = 0; 423 cmds[ctp].s_code = SRCH_NOPR; 424 cmds[ctp].s_doto = curwp->w_doto; 425 cmds[ctp].s_dotp = curwp->w_dotp; 426 cmds[ctp].s_dotline = curwp->w_dotline; 427 } 428 429 static void 430 is_pop(void) 431 { 432 if (cmds[cip].s_code != SRCH_NOPR) { 433 curwp->w_doto = cmds[cip].s_doto; 434 curwp->w_dotp = cmds[cip].s_dotp; 435 curwp->w_dotline = cmds[cip].s_dotline; 436 curwp->w_rflag |= WFMOVE; 437 cmds[cip].s_code = SRCH_NOPR; 438 } 439 if (--cip <= 0) 440 cip = NSRCH - 1; 441 } 442 443 static int 444 is_peek(void) 445 { 446 return (cmds[cip].s_code); 447 } 448 449 /* this used to always return TRUE (the return value was checked) */ 450 static void 451 is_undo(int *pptr, int *dir) 452 { 453 int redo = FALSE; 454 455 switch (cmds[cip].s_code) { 456 case SRCH_BEGIN: 457 case SRCH_NOPR: 458 *pptr = -1; 459 break; 460 case SRCH_MARK: 461 break; 462 case SRCH_FORW: 463 *dir = SRCH_BACK; 464 redo = TRUE; 465 break; 466 case SRCH_BACK: 467 *dir = SRCH_FORW; 468 redo = TRUE; 469 break; 470 case SRCH_ACCM: 471 default: 472 *pptr -= 1; 473 if (*pptr < 0) 474 *pptr = 0; 475 pat[*pptr] = '\0'; 476 break; 477 } 478 is_pop(); 479 if (redo) 480 is_undo(pptr, dir); 481 } 482 483 static int 484 is_find(int dir) 485 { 486 int plen, odoto, odotline; 487 struct line *odotp; 488 489 odoto = curwp->w_doto; 490 odotp = curwp->w_dotp; 491 odotline = curwp->w_dotline; 492 plen = strlen(pat); 493 if (plen != 0) { 494 if (dir == SRCH_FORW) { 495 (void)backchar(FFARG | FFRAND, plen); 496 if (forwsrch() == FALSE) { 497 curwp->w_doto = odoto; 498 curwp->w_dotp = odotp; 499 curwp->w_dotline = odotline; 500 return (FALSE); 501 } 502 return (TRUE); 503 } 504 if (dir == SRCH_BACK) { 505 (void)forwchar(FFARG | FFRAND, plen); 506 if (backsrch() == FALSE) { 507 curwp->w_doto = odoto; 508 curwp->w_dotp = odotp; 509 curwp->w_dotline = odotline; 510 return (FALSE); 511 } 512 return (TRUE); 513 } 514 dobeep(); 515 ewprintf("bad call to is_find"); 516 return (FALSE); 517 } 518 return (FALSE); 519 } 520 521 /* 522 * If called with "dir" not one of SRCH_FORW or SRCH_BACK, this routine used 523 * to print an error message. It also used to return TRUE or FALSE, depending 524 * on if it liked the "dir". However, none of the callers looked at the 525 * status, so I just made the checking vanish. 526 */ 527 static void 528 is_prompt(int dir, int flag, int success) 529 { 530 if (dir == SRCH_FORW) { 531 if (success != FALSE) 532 is_dspl("I-search", flag); 533 else 534 is_dspl("Failing I-search", flag); 535 } else if (dir == SRCH_BACK) { 536 if (success != FALSE) 537 is_dspl("I-search backward", flag); 538 else 539 is_dspl("Failing I-search backward", flag); 540 } else 541 ewprintf("Broken call to is_prompt"); 542 } 543 544 /* 545 * Prompt writing routine for the incremental search. The "i_prompt" is just 546 * a string. The "flag" determines whether pat should be printed. 547 */ 548 static void 549 is_dspl(char *i_prompt, int flag) 550 { 551 if (flag != FALSE) 552 ewprintf("%s: ", i_prompt); 553 else 554 ewprintf("%s: %s", i_prompt, pat); 555 } 556 557 /* 558 * Query Replace. 559 * Replace strings selectively. Does a search and replace operation. 560 */ 561 /* ARGSUSED */ 562 int 563 queryrepl(int f, int n) 564 { 565 int s; 566 int rcnt = 0; /* replacements made so far */ 567 int plen; /* length of found string */ 568 char news[NPAT], *rep; /* replacement string */ 569 570 if (macrodef) { 571 dobeep(); 572 ewprintf("Can't query replace in macro"); 573 return (FALSE); 574 } 575 576 if ((s = readpattern("Query replace")) != TRUE) 577 return (s); 578 if ((rep = eread("Query replace %s with: ", news, NPAT, 579 EFNUL | EFNEW | EFCR, pat)) == NULL) 580 return (ABORT); 581 else if (rep[0] == '\0') 582 news[0] = '\0'; 583 ewprintf("Query replacing %s with %s:", pat, news); 584 plen = strlen(pat); 585 586 /* 587 * Search forward repeatedly, checking each time whether to insert 588 * or not. The "!" case makes the check always true, so it gets put 589 * into a tighter loop for efficiency. 590 */ 591 while (forwsrch() == TRUE) { 592 retry: 593 update(CMODE); 594 switch (getkey(FALSE)) { 595 case 'y': 596 case ' ': 597 if (lreplace((RSIZE)plen, news) == FALSE) 598 return (FALSE); 599 rcnt++; 600 break; 601 case '.': 602 if (lreplace((RSIZE)plen, news) == FALSE) 603 return (FALSE); 604 rcnt++; 605 goto stopsearch; 606 /* ^G, CR or ESC */ 607 case CCHR('G'): 608 (void)ctrlg(FFRAND, 0); 609 goto stopsearch; 610 case CCHR('['): 611 case CCHR('M'): 612 goto stopsearch; 613 case '!': 614 do { 615 if (lreplace((RSIZE)plen, news) == FALSE) 616 return (FALSE); 617 rcnt++; 618 } while (forwsrch() == TRUE); 619 goto stopsearch; 620 case 'n': 621 case CCHR('H'): 622 /* To not replace */ 623 case CCHR('?'): 624 break; 625 default: 626 ewprintf("y/n or <SP>/<DEL>: replace/don't, [.] repl-end, [!] repl-rest, <CR>/<ESC> quit"); 627 goto retry; 628 } 629 } 630 stopsearch: 631 curwp->w_rflag |= WFFULL; 632 update(CMODE); 633 if (rcnt == 1) 634 ewprintf("Replaced 1 occurrence"); 635 else 636 ewprintf("Replaced %d occurrences", rcnt); 637 return (TRUE); 638 } 639 640 /* 641 * Replace string globally without individual prompting. 642 */ 643 /* ARGSUSED */ 644 int 645 replstr(int f, int n) 646 { 647 char news[NPAT]; 648 int s, plen, rcnt = 0; 649 char *r; 650 651 if ((s = readpattern("Replace string")) != TRUE) 652 return s; 653 654 r = eread("Replace string %s with: ", news, NPAT, 655 EFNUL | EFNEW | EFCR, pat); 656 if (r == NULL) 657 return (ABORT); 658 659 plen = strlen(pat); 660 while (forwsrch() == TRUE) { 661 update(CMODE); 662 if (lreplace((RSIZE)plen, news) == FALSE) 663 return (FALSE); 664 665 rcnt++; 666 } 667 668 curwp->w_rflag |= WFFULL; 669 update(CMODE); 670 671 if (rcnt == 1) 672 ewprintf("Replaced 1 occurrence"); 673 else 674 ewprintf("Replaced %d occurrences", rcnt); 675 676 return (TRUE); 677 } 678 679 /* 680 * This routine does the real work of a forward search. The pattern is sitting 681 * in the external variable "pat". If found, dot is updated, the window system 682 * is notified of the change, and TRUE is returned. If the string isn't found, 683 * FALSE is returned. 684 */ 685 int 686 forwsrch(void) 687 { 688 struct line *clp, *tlp; 689 int cbo, tbo, c, i, xcase = 0; 690 char *pp; 691 int nline; 692 693 clp = curwp->w_dotp; 694 cbo = curwp->w_doto; 695 nline = curwp->w_dotline; 696 for (i = 0; pat[i]; i++) 697 if (ISUPPER(CHARMASK(pat[i]))) 698 xcase = 1; 699 for (;;) { 700 if (cbo == llength(clp)) { 701 if ((clp = lforw(clp)) == curbp->b_headp) 702 break; 703 nline++; 704 cbo = 0; 705 c = CCHR('J'); 706 } else 707 c = lgetc(clp, cbo++); 708 if (eq(c, pat[0], xcase) != FALSE) { 709 tlp = clp; 710 tbo = cbo; 711 pp = &pat[1]; 712 while (*pp != 0) { 713 if (tbo == llength(tlp)) { 714 tlp = lforw(tlp); 715 if (tlp == curbp->b_headp) 716 goto fail; 717 tbo = 0; 718 c = CCHR('J'); 719 if (eq(c, *pp++, xcase) == FALSE) 720 goto fail; 721 nline++; 722 } else { 723 c = lgetc(tlp, tbo++); 724 if (eq(c, *pp++, xcase) == FALSE) 725 goto fail; 726 } 727 } 728 curwp->w_dotp = tlp; 729 curwp->w_doto = tbo; 730 curwp->w_dotline = nline; 731 curwp->w_rflag |= WFMOVE; 732 return (TRUE); 733 } 734 fail: ; 735 } 736 return (FALSE); 737 } 738 739 /* 740 * This routine does the real work of a backward search. The pattern is 741 * sitting in the external variable "pat". If found, dot is updated, the 742 * window system is notified of the change, and TRUE is returned. If the 743 * string isn't found, FALSE is returned. 744 */ 745 int 746 backsrch(void) 747 { 748 struct line *clp, *tlp; 749 int cbo, tbo, c, i, xcase = 0; 750 char *epp, *pp; 751 int nline, pline; 752 753 for (epp = &pat[0]; epp[1] != 0; ++epp); 754 clp = curwp->w_dotp; 755 cbo = curwp->w_doto; 756 nline = curwp->w_dotline; 757 for (i = 0; pat[i]; i++) 758 if (ISUPPER(CHARMASK(pat[i]))) 759 xcase = 1; 760 for (;;) { 761 if (cbo == 0) { 762 clp = lback(clp); 763 if (clp == curbp->b_headp) 764 return (FALSE); 765 nline--; 766 cbo = llength(clp) + 1; 767 } 768 if (--cbo == llength(clp)) 769 c = CCHR('J'); 770 else 771 c = lgetc(clp, cbo); 772 if (eq(c, *epp, xcase) != FALSE) { 773 tlp = clp; 774 tbo = cbo; 775 pp = epp; 776 pline = nline; 777 while (pp != &pat[0]) { 778 if (tbo == 0) { 779 tlp = lback(tlp); 780 if (tlp == curbp->b_headp) 781 goto fail; 782 nline--; 783 tbo = llength(tlp) + 1; 784 } 785 if (--tbo == llength(tlp)) 786 c = CCHR('J'); 787 else 788 c = lgetc(tlp, tbo); 789 if (eq(c, *--pp, xcase) == FALSE) { 790 nline = pline; 791 goto fail; 792 } 793 } 794 curwp->w_dotp = tlp; 795 curwp->w_doto = tbo; 796 curwp->w_dotline = nline; 797 curwp->w_rflag |= WFMOVE; 798 return (TRUE); 799 } 800 fail: ; 801 } 802 /* NOTREACHED */ 803 } 804 805 /* 806 * Compare two characters. The "bc" comes from the buffer. It has its case 807 * folded out. The "pc" is from the pattern. 808 */ 809 static int 810 eq(int bc, int pc, int xcase) 811 { 812 bc = CHARMASK(bc); 813 pc = CHARMASK(pc); 814 if (bc == pc) 815 return (TRUE); 816 if (xcase) 817 return (FALSE); 818 if (ISUPPER(bc)) 819 return (TOLOWER(bc) == pc); 820 if (ISUPPER(pc)) 821 return (bc == TOLOWER(pc)); 822 return (FALSE); 823 } 824 825 /* 826 * Read a pattern. Stash it in the external variable "pat". The "pat" is not 827 * updated if the user types in an empty line. If the user typed an empty 828 * line, and there is no old pattern, it is an error. Display the old pattern, 829 * in the style of Jeff Lomicka. There is some do-it-yourself control 830 * expansion. 831 */ 832 int 833 readpattern(char *r_prompt) 834 { 835 char tpat[NPAT], *rep; 836 int retval; 837 838 if (pat[0] == '\0') 839 rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, r_prompt); 840 else 841 rep = eread("%s (default %s): ", tpat, NPAT, 842 EFNUL | EFNEW | EFCR, r_prompt, pat); 843 844 /* specified */ 845 if (rep == NULL) { 846 retval = ABORT; 847 } else if (rep[0] != '\0') { 848 (void)strlcpy(pat, tpat, sizeof(pat)); 849 retval = TRUE; 850 } else if (pat[0] != '\0') { 851 retval = TRUE; 852 } else 853 retval = FALSE; 854 return (retval); 855 } 856