1 /* $OpenBSD: search.c,v 1.44 2015/03/19 21:22:15 bcallah 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 * ^S switch direction to forward 163 * ^R switch direction to reverse 164 * ^Q quote next character (allows searching for ^N etc.) 165 * <ESC> exit from Isearch 166 * <DEL> undoes last character typed. (tricky job to do this correctly). 167 * other ^ exit search, don't set mark 168 * else accumulate into search string 169 */ 170 static int 171 isearch(int dir) 172 { 173 struct line *clp; /* Saved line pointer */ 174 int c; 175 int cbo; /* Saved offset */ 176 int success; 177 int pptr; 178 int firstc; 179 int xcase; 180 int i; 181 char opat[NPAT]; 182 int cdotline; /* Saved line number */ 183 184 if (macrodef) { 185 dobeep(); 186 ewprintf("Can't isearch in macro"); 187 return (FALSE); 188 } 189 for (cip = 0; cip < NSRCH; cip++) 190 cmds[cip].s_code = SRCH_NOPR; 191 192 (void)strlcpy(opat, pat, sizeof(opat)); 193 cip = 0; 194 pptr = -1; 195 clp = curwp->w_dotp; 196 cbo = curwp->w_doto; 197 cdotline = curwp->w_dotline; 198 is_lpush(); 199 is_cpush(SRCH_BEGIN); 200 success = TRUE; 201 is_prompt(dir, TRUE, success); 202 203 for (;;) { 204 update(CMODE); 205 206 switch (c = getkey(FALSE)) { 207 case CCHR('['): 208 /* 209 * If new characters come in the next 300 msec, 210 * we can assume that they belong to a longer 211 * escaped sequence so we should ungetkey the 212 * ESC to avoid writing out garbage. 213 */ 214 if (ttwait(300) == FALSE) 215 ungetkey(c); 216 srch_lastdir = dir; 217 curwp->w_markp = clp; 218 curwp->w_marko = cbo; 219 curwp->w_markline = cdotline; 220 ewprintf("Mark set"); 221 return (TRUE); 222 case CCHR('G'): 223 if (success != TRUE) { 224 while (is_peek() == SRCH_ACCM) 225 is_undo(&pptr, &dir); 226 success = TRUE; 227 is_prompt(dir, pptr < 0, success); 228 break; 229 } 230 curwp->w_dotp = clp; 231 curwp->w_doto = cbo; 232 curwp->w_dotline = cdotline; 233 curwp->w_rflag |= WFMOVE; 234 srch_lastdir = dir; 235 (void)ctrlg(FFRAND, 0); 236 (void)strlcpy(pat, opat, sizeof(pat)); 237 return (ABORT); 238 case CCHR('S'): 239 if (dir == SRCH_BACK) { 240 dir = SRCH_FORW; 241 is_lpush(); 242 is_cpush(SRCH_FORW); 243 success = TRUE; 244 } 245 if (success == FALSE && dir == SRCH_FORW) { 246 /* wrap the search to beginning */ 247 curwp->w_dotp = bfirstlp(curbp); 248 curwp->w_doto = 0; 249 curwp->w_dotline = 1; 250 if (is_find(dir) != FALSE) { 251 is_cpush(SRCH_MARK); 252 success = TRUE; 253 } 254 ewprintf("Overwrapped I-search: %s", pat); 255 break; 256 } 257 is_lpush(); 258 pptr = strlen(pat); 259 if (forwchar(FFRAND, 1) == FALSE) { 260 dobeep(); 261 success = FALSE; 262 ewprintf("Failed I-search: %s", pat); 263 } else { 264 if (is_find(SRCH_FORW) != FALSE) 265 is_cpush(SRCH_MARK); 266 else { 267 (void)backchar(FFRAND, 1); 268 dobeep(); 269 success = FALSE; 270 ewprintf("Failed I-search: %s", pat); 271 } 272 } 273 is_prompt(dir, pptr < 0, success); 274 break; 275 case CCHR('R'): 276 if (dir == SRCH_FORW) { 277 dir = SRCH_BACK; 278 is_lpush(); 279 is_cpush(SRCH_BACK); 280 success = TRUE; 281 } 282 if (success == FALSE && dir == SRCH_BACK) { 283 /* wrap the search to end */ 284 curwp->w_dotp = blastlp(curbp); 285 curwp->w_doto = llength(curwp->w_dotp); 286 curwp->w_dotline = curwp->w_bufp->b_lines; 287 if (is_find(dir) != FALSE) { 288 is_cpush(SRCH_MARK); 289 success = TRUE; 290 } 291 ewprintf("Overwrapped I-search: %s", pat); 292 break; 293 } 294 is_lpush(); 295 pptr = strlen(pat); 296 if (backchar(FFRAND, 1) == FALSE) { 297 dobeep(); 298 success = FALSE; 299 } else { 300 if (is_find(SRCH_BACK) != FALSE) 301 is_cpush(SRCH_MARK); 302 else { 303 (void)forwchar(FFRAND, 1); 304 dobeep(); 305 success = FALSE; 306 } 307 } 308 is_prompt(dir, pptr < 0, success); 309 break; 310 case CCHR('W'): 311 /* add the rest of the current word to the pattern */ 312 clp = curwp->w_dotp; 313 cbo = curwp->w_doto; 314 firstc = 1; 315 if (pptr == -1) 316 pptr = 0; 317 if (dir == SRCH_BACK) { 318 /* when isearching backwards, cbo is the start of the pattern */ 319 cbo += pptr; 320 } 321 322 /* if the search is case insensitive, add to pattern using lowercase */ 323 xcase = 0; 324 for (i = 0; pat[i]; i++) 325 if (ISUPPER(CHARMASK(pat[i]))) 326 xcase = 1; 327 328 while (cbo < llength(clp)) { 329 c = lgetc(clp, cbo++); 330 if ((!firstc && !isalnum(c))) 331 break; 332 333 if (pptr == NPAT - 1) { 334 dobeep(); 335 break; 336 } 337 firstc = 0; 338 if (!xcase && ISUPPER(c)) 339 c = TOLOWER(c); 340 341 pat[pptr++] = c; 342 pat[pptr] = '\0'; 343 /* cursor only moves when isearching forwards */ 344 if (dir == SRCH_FORW) { 345 curwp->w_doto = cbo; 346 curwp->w_rflag |= WFMOVE; 347 update(CMODE); 348 } 349 } 350 is_prompt(dir, pptr < 0, success); 351 break; 352 case CCHR('H'): 353 case CCHR('?'): 354 is_undo(&pptr, &dir); 355 if (is_peek() != SRCH_ACCM) 356 success = TRUE; 357 is_prompt(dir, pptr < 0, success); 358 break; 359 case CCHR('\\'): 360 case CCHR('Q'): 361 c = (char)getkey(FALSE); 362 goto addchar; 363 case CCHR('M'): 364 c = CCHR('J'); 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 "prompt" is just 546 * a string. The "flag" determines whether pat should be printed. 547 */ 548 static void 549 is_dspl(char *prompt, int flag) 550 { 551 if (flag != FALSE) 552 ewprintf("%s: ", prompt); 553 else 554 ewprintf("%s: %s", 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 *prompt) 834 { 835 char tpat[NPAT], *rep; 836 int retval; 837 838 if (pat[0] == '\0') 839 rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, prompt); 840 else 841 rep = eread("%s: (default %s) ", tpat, NPAT, 842 EFNUL | EFNEW | EFCR, 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