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