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