1 /* $OpenBSD: search.c,v 1.4 1997/06/29 23:40:51 millert Exp $ */ 2 /* $NetBSD: search.c,v 1.4 1997/01/23 14:02:47 mrg Exp $ */ 3 4 /*- 5 * Copyright (c) 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Christos Zoulas of Cornell University. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the University of 22 * California, Berkeley and its contributors. 23 * 4. Neither the name of the University nor the names of its contributors 24 * may be used to endorse or promote products derived from this software 25 * without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 * SUCH DAMAGE. 38 */ 39 40 #if !defined(lint) && !defined(SCCSID) 41 #if 0 42 static char sccsid[] = "@(#)search.c 8.1 (Berkeley) 6/4/93"; 43 #else 44 static char rcsid[] = "$OpenBSD: search.c,v 1.4 1997/06/29 23:40:51 millert Exp $"; 45 #endif 46 #endif /* not lint && not SCCSID */ 47 48 /* 49 * search.c: History and character search functions 50 */ 51 #include "sys.h" 52 #include <stdlib.h> 53 #if defined(REGEX) 54 #include <regex.h> 55 #elif defined(REGEXP) 56 #include <regexp.h> 57 #endif 58 #include "el.h" 59 60 /* 61 * Adjust cursor in vi mode to include the character under it 62 */ 63 #define EL_CURSOR(el) \ 64 ((el)->el_line.cursor + (((el)->el_map.type == MAP_VI) && \ 65 ((el)->el_map.current == (el)->el_map.alt))) 66 67 /* search_init(): 68 * Initialize the search stuff 69 */ 70 protected int 71 search_init(el) 72 EditLine *el; 73 { 74 el->el_search.patbuf = (char *) el_malloc(EL_BUFSIZ); 75 el->el_search.patlen = 0; 76 el->el_search.patdir = -1; 77 el->el_search.chacha = '\0'; 78 el->el_search.chadir = -1; 79 return 0; 80 } 81 82 83 /* search_end(): 84 * Initialize the search stuff 85 */ 86 protected void 87 search_end(el) 88 EditLine *el; 89 { 90 el_free((ptr_t) el->el_search.patbuf); 91 el->el_search.patbuf = NULL; 92 } 93 94 #ifdef REGEXP 95 /* regerror(): 96 * Handle regular expression errors 97 */ 98 public void 99 /*ARGSUSED*/ 100 regerror(msg) 101 const char *msg; 102 { 103 } 104 #endif 105 106 /* el_match(): 107 * Return if string matches pattern 108 */ 109 protected int 110 el_match(str, pat) 111 const char *str; 112 const char *pat; 113 { 114 #if defined (REGEX) 115 regex_t re; 116 int rv; 117 #elif defined (REGEXP) 118 regexp *rp; 119 int rv; 120 #else 121 extern char *re_comp __P((const char *)); 122 extern int re_exec __P((const char *)); 123 #endif 124 125 if (strstr(str, pat) != NULL) 126 return 1; 127 128 #if defined(REGEX) 129 if (regcomp(&re, pat, 0) == 0) { 130 rv = regexec(&re, str, 0, NULL, 0) == 0; 131 regfree(&re); 132 } else { 133 rv = 0; 134 } 135 return rv; 136 #elif defined(REGEXP) 137 if ((re = regcomp(pat)) != NULL) { 138 rv = regexec(re, str); 139 free((ptr_t) re); 140 } else { 141 rv = 0; 142 } 143 return rv; 144 #else 145 if (re_comp(pat) != NULL) 146 return 0; 147 else 148 return re_exec(str) == 1; 149 #endif 150 } 151 152 153 /* c_hmatch(): 154 * return True if the pattern matches the prefix 155 */ 156 protected int 157 c_hmatch(el, str) 158 EditLine *el; 159 const char *str; 160 { 161 #ifdef SDEBUG 162 (void)fprintf(el->el_errfile, "match `%s' with `%s'\n", 163 el->el_search.patbuf, str); 164 #endif /* SDEBUG */ 165 166 return el_match(str, el->el_search.patbuf); 167 } 168 169 170 /* c_setpat(): 171 * Set the history seatch pattern 172 */ 173 protected void 174 c_setpat(el) 175 EditLine *el; 176 { 177 if (el->el_state.lastcmd != ED_SEARCH_PREV_HISTORY && 178 el->el_state.lastcmd != ED_SEARCH_NEXT_HISTORY) { 179 el->el_search.patlen = EL_CURSOR(el) - el->el_line.buffer; 180 if (el->el_search.patlen >= EL_BUFSIZ) 181 el->el_search.patlen = EL_BUFSIZ -1; 182 if (el->el_search.patlen >= 0) { 183 (void)strncpy(el->el_search.patbuf, el->el_line.buffer, 184 el->el_search.patlen); 185 el->el_search.patbuf[el->el_search.patlen] = '\0'; 186 } 187 else 188 el->el_search.patlen = strlen(el->el_search.patbuf); 189 } 190 #ifdef SDEBUG 191 (void)fprintf(el->el_errfile, "\neventno = %d\n", el->el_history.eventno); 192 (void)fprintf(el->el_errfile, "patlen = %d\n", el->el_search.patlen); 193 (void)fprintf(el->el_errfile, "patbuf = \"%s\"\n", el->el_search.patbuf); 194 (void)fprintf(el->el_errfile, "cursor %d lastchar %d\n", 195 EL_CURSOR(el) - el->el_line.buffer, 196 el->el_line.lastchar - el->el_line.buffer); 197 #endif 198 } 199 200 201 /* ce_inc_search(): 202 * Emacs incremental search 203 */ 204 protected el_action_t 205 ce_inc_search(el, dir) 206 EditLine *el; 207 int dir; 208 { 209 static char STRfwd[] = { 'f', 'w', 'd', '\0' }, 210 STRbck[] = { 'b', 'c', 'k', '\0' }; 211 static char pchar = ':'; /* ':' = normal, '?' = failed */ 212 static char endcmd[2] = { '\0', '\0' }; 213 char ch, *cp, *ocursor = el->el_line.cursor, oldpchar = pchar; 214 215 el_action_t ret = CC_NORM; 216 217 int ohisteventno = el->el_history.eventno, 218 oldpatlen = el->el_search.patlen, 219 newdir = dir, 220 done, redo; 221 222 if (el->el_line.lastchar + sizeof(STRfwd) / sizeof(char) + 2 + 223 el->el_search.patlen >= el->el_line.limit) 224 return CC_ERROR; 225 226 for (;;) { 227 228 if (el->el_search.patlen == 0) { /* first round */ 229 pchar = ':'; 230 #ifdef ANCHOR 231 el->el_search.patbuf[el->el_search.patlen++] = '.'; 232 el->el_search.patbuf[el->el_search.patlen++] = '*'; 233 #endif 234 } 235 done = redo = 0; 236 *el->el_line.lastchar++ = '\n'; 237 for (cp = newdir == ED_SEARCH_PREV_HISTORY ? STRbck : STRfwd; 238 *cp; *el->el_line.lastchar++ = *cp++) 239 continue; 240 *el->el_line.lastchar++ = pchar; 241 for (cp = &el->el_search.patbuf[1]; 242 cp < &el->el_search.patbuf[el->el_search.patlen]; 243 *el->el_line.lastchar++ = *cp++) 244 continue; 245 *el->el_line.lastchar = '\0'; 246 re_refresh(el); 247 248 if (el_getc(el, &ch) != 1) 249 return ed_end_of_file(el, 0); 250 251 switch (el->el_map.current[(unsigned char) ch]) { 252 case ED_INSERT: 253 case ED_DIGIT: 254 if (el->el_search.patlen > EL_BUFSIZ - 3) 255 term_beep(el); 256 else { 257 el->el_search.patbuf[el->el_search.patlen++] = ch; 258 *el->el_line.lastchar++ = ch; 259 *el->el_line.lastchar = '\0'; 260 re_refresh(el); 261 } 262 break; 263 264 case EM_INC_SEARCH_NEXT: 265 newdir = ED_SEARCH_NEXT_HISTORY; 266 redo++; 267 break; 268 269 case EM_INC_SEARCH_PREV: 270 newdir = ED_SEARCH_PREV_HISTORY; 271 redo++; 272 break; 273 274 case ED_DELETE_PREV_CHAR: 275 if (el->el_search.patlen > 1) 276 done++; 277 else 278 term_beep(el); 279 break; 280 281 default: 282 switch (ch) { 283 case 0007: /* ^G: Abort */ 284 ret = CC_ERROR; 285 done++; 286 break; 287 288 case 0027: /* ^W: Append word */ 289 /* No can do if globbing characters in pattern */ 290 for (cp = &el->el_search.patbuf[1]; ; cp++) 291 if (cp >= &el->el_search.patbuf[el->el_search.patlen]) { 292 el->el_line.cursor += el->el_search.patlen - 1; 293 cp = c__next_word(el->el_line.cursor, 294 el->el_line.lastchar, 1, ce__isword); 295 while (el->el_line.cursor < cp && 296 *el->el_line.cursor != '\n') { 297 if (el->el_search.patlen > EL_BUFSIZ - 3) { 298 term_beep(el); 299 break; 300 } 301 el->el_search.patbuf[el->el_search.patlen++] = 302 *el->el_line.cursor; 303 *el->el_line.lastchar++ = *el->el_line.cursor++; 304 } 305 el->el_line.cursor = ocursor; 306 *el->el_line.lastchar = '\0'; 307 re_refresh(el); 308 break; 309 } else if (isglob(*cp)) { 310 term_beep(el); 311 break; 312 } 313 break; 314 315 default: /* Terminate and execute cmd */ 316 endcmd[0] = ch; 317 el_push(el, endcmd); 318 /*FALLTHROUGH*/ 319 320 case 0033: /* ESC: Terminate */ 321 ret = CC_REFRESH; 322 done++; 323 break; 324 } 325 break; 326 } 327 328 while (el->el_line.lastchar > el->el_line.buffer && 329 *el->el_line.lastchar != '\n') 330 *el->el_line.lastchar-- = '\0'; 331 *el->el_line.lastchar = '\0'; 332 333 if (!done) { 334 335 /* Can't search if unmatched '[' */ 336 for (cp = &el->el_search.patbuf[el->el_search.patlen-1], ch = ']'; 337 cp > el->el_search.patbuf; cp--) 338 if (*cp == '[' || *cp == ']') { 339 ch = *cp; 340 break; 341 } 342 343 if (el->el_search.patlen > 1 && ch != '[') { 344 if (redo && newdir == dir) { 345 if (pchar == '?') { /* wrap around */ 346 el->el_history.eventno = 347 newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff; 348 if (hist_get(el) == CC_ERROR) 349 /* el->el_history.eventno was fixed by first call */ 350 (void)hist_get(el); 351 el->el_line.cursor = newdir == ED_SEARCH_PREV_HISTORY ? 352 el->el_line.lastchar : el->el_line.buffer; 353 } else 354 el->el_line.cursor += 355 newdir == ED_SEARCH_PREV_HISTORY ? -1 : 1; 356 } 357 #ifdef ANCHOR 358 el->el_search.patbuf[el->el_search.patlen++] = '.'; 359 el->el_search.patbuf[el->el_search.patlen++] = '*'; 360 #endif 361 el->el_search.patbuf[el->el_search.patlen] = '\0'; 362 if (el->el_line.cursor < el->el_line.buffer || 363 el->el_line.cursor > el->el_line.lastchar || 364 (ret = ce_search_line(el, &el->el_search.patbuf[1], 365 newdir)) == CC_ERROR) { 366 /* avoid c_setpat */ 367 el->el_state.lastcmd = (el_action_t) newdir; 368 ret = newdir == ED_SEARCH_PREV_HISTORY ? 369 ed_search_prev_history(el, 0) : 370 ed_search_next_history(el, 0); 371 if (ret != CC_ERROR) { 372 el->el_line.cursor = newdir == ED_SEARCH_PREV_HISTORY ? 373 el->el_line.lastchar : el->el_line.buffer; 374 (void)ce_search_line(el, &el->el_search.patbuf[1], 375 newdir); 376 } 377 } 378 el->el_search.patbuf[--el->el_search.patlen] = '\0'; 379 if (ret == CC_ERROR) { 380 term_beep(el); 381 if (el->el_history.eventno != ohisteventno) { 382 el->el_history.eventno = ohisteventno; 383 if (hist_get(el) == CC_ERROR) 384 return CC_ERROR; 385 } 386 el->el_line.cursor = ocursor; 387 pchar = '?'; 388 } else { 389 pchar = ':'; 390 } 391 } 392 393 ret = ce_inc_search(el, newdir); 394 395 if (ret == CC_ERROR && pchar == '?' && oldpchar == ':') 396 /* break abort of failed search at last non-failed */ 397 ret = CC_NORM; 398 399 } 400 401 if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) { 402 /* restore on normal return or error exit */ 403 pchar = oldpchar; 404 el->el_search.patlen = oldpatlen; 405 if (el->el_history.eventno != ohisteventno) { 406 el->el_history.eventno = ohisteventno; 407 if (hist_get(el) == CC_ERROR) 408 return CC_ERROR; 409 } 410 el->el_line.cursor = ocursor; 411 if (ret == CC_ERROR) 412 re_refresh(el); 413 } 414 if (done || ret != CC_NORM) 415 return ret; 416 } 417 } 418 419 420 /* cv_search(): 421 * Vi search. 422 */ 423 protected el_action_t 424 cv_search(el, dir) 425 EditLine *el; 426 int dir; 427 { 428 char ch; 429 char tmpbuf[EL_BUFSIZ]; 430 int tmplen; 431 432 tmplen = 0; 433 #ifdef ANCHOR 434 tmpbuf[tmplen++] = '.'; 435 tmpbuf[tmplen++] = '*'; 436 #endif 437 438 el->el_line.buffer[0] = '\0'; 439 el->el_line.lastchar = el->el_line.buffer; 440 el->el_line.cursor = el->el_line.buffer; 441 el->el_search.patdir = dir; 442 443 c_insert(el, 2); /* prompt + '\n' */ 444 *el->el_line.cursor++ = '\n'; 445 *el->el_line.cursor++ = dir == ED_SEARCH_PREV_HISTORY ? '/' : '?'; 446 re_refresh(el); 447 448 #ifdef ANCHOR 449 # define LEN 2 450 #else 451 # define LEN 0 452 #endif 453 454 tmplen = c_gets(el, &tmpbuf[LEN]) + LEN; 455 ch = tmpbuf[tmplen]; 456 tmpbuf[tmplen] = '\0'; 457 458 if (tmplen == LEN) { 459 /* 460 * Use the old pattern, but wild-card it. 461 */ 462 if (el->el_search.patlen == 0) { 463 el->el_line.buffer[0] = '\0'; 464 el->el_line.lastchar = el->el_line.buffer; 465 el->el_line.cursor = el->el_line.buffer; 466 re_refresh(el); 467 return CC_ERROR; 468 } 469 #ifdef ANCHOR 470 if (el->el_search.patbuf[0] != '.' && el->el_search.patbuf[0] != '*') { 471 (void)strncpy(tmpbuf, el->el_search.patbuf, sizeof(tmpbuf) - 1); 472 tmpbuf[sizeof(tmpbuf) - 1] = '\0'; 473 el->el_search.patbuf[0] = '.'; 474 el->el_search.patbuf[1] = '*'; 475 (void)strncpy(&el->el_search.patbuf[2], tmpbuf, 476 sizeof(el->el_search.patbuf) - 3); 477 el->el_search.patbuf[sizeof(el->el_search.patbuf) - 1] = '\0'; 478 el->el_search.patlen++; 479 el->el_search.patbuf[el->el_search.patlen++] = '.'; 480 el->el_search.patbuf[el->el_search.patlen++] = '*'; 481 el->el_search.patbuf[el->el_search.patlen] = '\0'; 482 } 483 #endif 484 } 485 else { 486 #ifdef ANCHOR 487 tmpbuf[tmplen++] = '.'; 488 tmpbuf[tmplen++] = '*'; 489 #endif 490 tmpbuf[tmplen] = '\0'; 491 (void)strncpy(el->el_search.patbuf, tmpbuf, 492 sizeof(el->el_search.patbuf) - 1); 493 el->el_search.patbuf[sizeof(el->el_search.patbuf) - 1] = '\0'; 494 el->el_search.patlen = strlen(el->el_search.patbuf); 495 } 496 el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */ 497 el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer; 498 if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) : 499 ed_search_next_history(el, 0)) == CC_ERROR) { 500 re_refresh(el); 501 return CC_ERROR; 502 } 503 else { 504 if (ch == 0033) { 505 re_refresh(el); 506 *el->el_line.lastchar++ = '\n'; 507 *el->el_line.lastchar = '\0'; 508 re_goto_bottom(el); 509 return CC_NEWLINE; 510 } 511 else 512 return CC_REFRESH; 513 } 514 } 515 516 517 /* ce_search_line(): 518 * Look for a pattern inside a line 519 */ 520 protected el_action_t 521 ce_search_line(el, pattern, dir) 522 EditLine *el; 523 char *pattern; 524 int dir; 525 { 526 char *cp; 527 528 if (dir == ED_SEARCH_PREV_HISTORY) { 529 for (cp = el->el_line.cursor; cp >= el->el_line.buffer; cp--) 530 if (el_match(cp, pattern)) { 531 el->el_line.cursor = cp; 532 return CC_NORM; 533 } 534 return CC_ERROR; 535 } else { 536 for (cp = el->el_line.cursor; *cp != '\0' && 537 cp < el->el_line.limit; cp++) 538 if (el_match(cp, pattern)) { 539 el->el_line.cursor = cp; 540 return CC_NORM; 541 } 542 return CC_ERROR; 543 } 544 } 545 546 547 /* cv_repeat_srch(): 548 * Vi repeat search 549 */ 550 protected el_action_t 551 cv_repeat_srch(el, c) 552 EditLine *el; 553 int c; 554 { 555 #ifdef SDEBUG 556 (void)fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n", 557 c, el->el_search.patlen, el->el_search.patbuf); 558 #endif 559 560 el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */ 561 el->el_line.lastchar = el->el_line.buffer; 562 563 switch (c) { 564 case ED_SEARCH_NEXT_HISTORY: 565 return ed_search_next_history(el, 0); 566 case ED_SEARCH_PREV_HISTORY: 567 return ed_search_prev_history(el, 0); 568 default: 569 return CC_ERROR; 570 } 571 } 572 573 574 /* cv_csearch_back(): 575 * Vi character search reverse 576 */ 577 protected el_action_t 578 cv_csearch_back(el, ch, count, tflag) 579 EditLine *el; 580 int ch, count, tflag; 581 { 582 char *cp; 583 584 cp = el->el_line.cursor; 585 while (count--) { 586 if (*cp == ch) 587 cp--; 588 while (cp > el->el_line.buffer && *cp != ch) 589 cp--; 590 } 591 592 if (cp < el->el_line.buffer || (cp == el->el_line.buffer && *cp != ch)) 593 return CC_ERROR; 594 595 if (*cp == ch && tflag) 596 cp++; 597 598 el->el_line.cursor = cp; 599 600 if (el->el_chared.c_vcmd.action & DELETE) { 601 el->el_line.cursor++; 602 cv_delfini(el); 603 return CC_REFRESH; 604 } 605 606 re_refresh_cursor(el); 607 return CC_NORM; 608 } 609 610 611 /* cv_csearch_fwd(): 612 * Vi character search forward 613 */ 614 protected el_action_t 615 cv_csearch_fwd(el, ch, count, tflag) 616 EditLine *el; 617 int ch, count, tflag; 618 { 619 char *cp; 620 621 cp = el->el_line.cursor; 622 while (count--) { 623 if(*cp == ch) 624 cp++; 625 while (cp < el->el_line.lastchar && *cp != ch) 626 cp++; 627 } 628 629 if (cp >= el->el_line.lastchar) 630 return CC_ERROR; 631 632 if (*cp == ch && tflag) 633 cp--; 634 635 el->el_line.cursor = cp; 636 637 if (el->el_chared.c_vcmd.action & DELETE) { 638 el->el_line.cursor++; 639 cv_delfini(el); 640 return CC_REFRESH; 641 } 642 re_refresh_cursor(el); 643 return CC_NORM; 644 } 645