1 /* $NetBSD: search.c,v 1.5 1997/07/06 18:25:34 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Christos Zoulas of Cornell University. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the University of 21 * California, Berkeley and its contributors. 22 * 4. Neither the name of the University nor the names of its contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 #if !defined(lint) && !defined(SCCSID) 41 #if 0 42 static char sccsid[] = "@(#)search.c 8.1 (Berkeley) 6/4/93"; 43 #else 44 __RCSID("$NetBSD: search.c,v 1.5 1997/07/06 18:25:34 christos 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 el->el_search.patbuf[0] = '.'; 473 el->el_search.patbuf[1] = '*'; 474 (void)strncpy(&el->el_search.patbuf[2], tmpbuf, 475 sizeof(el->el_search.patbuf) - 3); 476 el->el_search.patlen++; 477 el->el_search.patbuf[el->el_search.patlen++] = '.'; 478 el->el_search.patbuf[el->el_search.patlen++] = '*'; 479 el->el_search.patbuf[el->el_search.patlen] = '\0'; 480 } 481 #endif 482 } 483 else { 484 #ifdef ANCHOR 485 tmpbuf[tmplen++] = '.'; 486 tmpbuf[tmplen++] = '*'; 487 #endif 488 tmpbuf[tmplen] = '\0'; 489 (void)strncpy(el->el_search.patbuf, tmpbuf, 490 sizeof(el->el_search.patbuf) - 1); 491 el->el_search.patlen = tmplen; 492 } 493 el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */ 494 el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer; 495 if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) : 496 ed_search_next_history(el, 0)) == CC_ERROR) { 497 re_refresh(el); 498 return CC_ERROR; 499 } 500 else { 501 if (ch == 0033) { 502 re_refresh(el); 503 *el->el_line.lastchar++ = '\n'; 504 *el->el_line.lastchar = '\0'; 505 re_goto_bottom(el); 506 return CC_NEWLINE; 507 } 508 else 509 return CC_REFRESH; 510 } 511 } 512 513 514 /* ce_search_line(): 515 * Look for a pattern inside a line 516 */ 517 protected el_action_t 518 ce_search_line(el, pattern, dir) 519 EditLine *el; 520 char *pattern; 521 int dir; 522 { 523 char *cp; 524 525 if (dir == ED_SEARCH_PREV_HISTORY) { 526 for (cp = el->el_line.cursor; cp >= el->el_line.buffer; cp--) 527 if (el_match(cp, pattern)) { 528 el->el_line.cursor = cp; 529 return CC_NORM; 530 } 531 return CC_ERROR; 532 } else { 533 for (cp = el->el_line.cursor; *cp != '\0' && 534 cp < el->el_line.limit; cp++) 535 if (el_match(cp, pattern)) { 536 el->el_line.cursor = cp; 537 return CC_NORM; 538 } 539 return CC_ERROR; 540 } 541 } 542 543 544 /* cv_repeat_srch(): 545 * Vi repeat search 546 */ 547 protected el_action_t 548 cv_repeat_srch(el, c) 549 EditLine *el; 550 int c; 551 { 552 #ifdef SDEBUG 553 (void) fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n", 554 c, el->el_search.patlen, el->el_search.patbuf); 555 #endif 556 557 el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */ 558 el->el_line.lastchar = el->el_line.buffer; 559 560 switch (c) { 561 case ED_SEARCH_NEXT_HISTORY: 562 return ed_search_next_history(el, 0); 563 case ED_SEARCH_PREV_HISTORY: 564 return ed_search_prev_history(el, 0); 565 default: 566 return CC_ERROR; 567 } 568 } 569 570 571 /* cv_csearch_back(): 572 * Vi character search reverse 573 */ 574 protected el_action_t 575 cv_csearch_back(el, ch, count, tflag) 576 EditLine *el; 577 int ch, count, tflag; 578 { 579 char *cp; 580 581 cp = el->el_line.cursor; 582 while (count--) { 583 if (*cp == ch) 584 cp--; 585 while (cp > el->el_line.buffer && *cp != ch) 586 cp--; 587 } 588 589 if (cp < el->el_line.buffer || (cp == el->el_line.buffer && *cp != ch)) 590 return CC_ERROR; 591 592 if (*cp == ch && tflag) 593 cp++; 594 595 el->el_line.cursor = cp; 596 597 if (el->el_chared.c_vcmd.action & DELETE) { 598 el->el_line.cursor++; 599 cv_delfini(el); 600 return CC_REFRESH; 601 } 602 603 re_refresh_cursor(el); 604 return CC_NORM; 605 } 606 607 608 /* cv_csearch_fwd(): 609 * Vi character search forward 610 */ 611 protected el_action_t 612 cv_csearch_fwd(el, ch, count, tflag) 613 EditLine *el; 614 int ch, count, tflag; 615 { 616 char *cp; 617 618 cp = el->el_line.cursor; 619 while (count--) { 620 if(*cp == ch) 621 cp++; 622 while (cp < el->el_line.lastchar && *cp != ch) 623 cp++; 624 } 625 626 if (cp >= el->el_line.lastchar) 627 return CC_ERROR; 628 629 if (*cp == ch && tflag) 630 cp--; 631 632 el->el_line.cursor = cp; 633 634 if (el->el_chared.c_vcmd.action & DELETE) { 635 el->el_line.cursor++; 636 cv_delfini(el); 637 return CC_REFRESH; 638 } 639 re_refresh_cursor(el); 640 return CC_NORM; 641 } 642