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