Home | History | Annotate | Line # | Download | only in libedit
search.c revision 1.3
      1 /*	$NetBSD: search.c,v 1.3 1997/01/11 06:48:09 lukem 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 #if !defined(lint) && !defined(SCCSID)
     40 #if 0
     41 static char sccsid[] = "@(#)search.c	8.1 (Berkeley) 6/4/93";
     42 #else
     43 static char rcsid[] = "$NetBSD: search.c,v 1.3 1997/01/11 06:48:09 lukem Exp $";
     44 #endif
     45 #endif /* not lint && not SCCSID */
     46 
     47 /*
     48  * search.c: History and character search functions
     49  */
     50 #include "sys.h"
     51 #include <stdlib.h>
     52 #if defined(REGEX)
     53 #include <regex.h>
     54 #elif defined(REGEXP)
     55 #include <regexp.h>
     56 #endif
     57 #include "el.h"
     58 
     59 /*
     60  * Adjust cursor in vi mode to include the character under it
     61  */
     62 #define EL_CURSOR(el) \
     63     ((el)->el_line.cursor + (((el)->el_map.type == MAP_VI) && \
     64 			    ((el)->el_map.current == (el)->el_map.alt)))
     65 
     66 /* search_init():
     67  *	Initialize the search stuff
     68  */
     69 protected int
     70 search_init(el)
     71     EditLine *el;
     72 {
     73     el->el_search.patbuf = (char *) el_malloc(EL_BUFSIZ);
     74     el->el_search.patlen = 0;
     75     el->el_search.patdir = -1;
     76     el->el_search.chacha = '\0';
     77     el->el_search.chadir = -1;
     78     return 0;
     79 }
     80 
     81 
     82 /* search_end():
     83  *	Initialize the search stuff
     84  */
     85 protected void
     86 search_end(el)
     87     EditLine *el;
     88 {
     89     el_free((ptr_t) el->el_search.patbuf);
     90     el->el_search.patbuf = NULL;
     91 }
     92 
     93 #ifdef REGEXP
     94 /* regerror():
     95  *	Handle regular expression errors
     96  */
     97 public void
     98 /*ARGSUSED*/
     99 regerror(msg)
    100     const char *msg;
    101 {
    102 }
    103 #endif
    104 
    105 /* el_match():
    106  *	Return if string matches pattern
    107  */
    108 protected int
    109 el_match(str, pat)
    110     const char *str;
    111     const char *pat;
    112 {
    113 #if defined (REGEX)
    114     regex_t re;
    115     int rv;
    116 #elif defined (REGEXP)
    117     regexp *rp;
    118     int rv;
    119 #else
    120     extern char *re_comp __P((const char *));
    121     extern int re_exec __P((const char *));
    122 #endif
    123 
    124     if (strstr(str, pat) != NULL)
    125 	return 1;
    126 
    127 #if defined(REGEX)
    128     if (regcomp(&re, pat, 0) == 0) {
    129 	rv = regexec(&re, str, 0, NULL, 0) == 0;
    130 	regfree(&re);
    131     } else {
    132 	rv = 0;
    133     }
    134     return rv;
    135 #elif defined(REGEXP)
    136     if ((re = regcomp(pat)) != NULL) {
    137 	rv = regexec(re, str);
    138 	free((ptr_t) re);
    139     } else {
    140 	rv = 0;
    141     }
    142     return rv;
    143 #else
    144     if (re_comp(pat) != NULL)
    145 	return 0;
    146     else
    147     return re_exec(str) == 1;
    148 #endif
    149 }
    150 
    151 
    152 /* c_hmatch():
    153  *	 return True if the pattern matches the prefix
    154  */
    155 protected int
    156 c_hmatch(el, str)
    157     EditLine *el;
    158     const char *str;
    159 {
    160 #ifdef SDEBUG
    161     (void) fprintf(el->el_errfile, "match `%s' with `%s'\n",
    162 		   el->el_search.patbuf, str);
    163 #endif /* SDEBUG */
    164 
    165     return el_match(str, el->el_search.patbuf);
    166 }
    167 
    168 
    169 /* c_setpat():
    170  *	Set the history seatch pattern
    171  */
    172 protected void
    173 c_setpat(el)
    174     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 	}
    186 	else
    187 	    el->el_search.patlen = strlen(el->el_search.patbuf);
    188     }
    189 #ifdef SDEBUG
    190     (void) fprintf(el->el_errfile, "\neventno = %d\n", 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", el->el_search.patbuf);
    193     (void) fprintf(el->el_errfile, "cursor %d lastchar %d\n",
    194 		   EL_CURSOR(el) - el->el_line.buffer,
    195 		   el->el_line.lastchar - el->el_line.buffer);
    196 #endif
    197 }
    198 
    199 
    200 /* ce_inc_search():
    201  *	Emacs incremental search
    202  */
    203 protected el_action_t
    204 ce_inc_search(el, dir)
    205     EditLine *el;
    206     int dir;
    207 {
    208     static char STRfwd[] = { 'f', 'w', 'd', '\0' },
    209 		STRbck[] = { 'b', 'c', 'k', '\0' };
    210     static char pchar = ':';	/* ':' = normal, '?' = failed */
    211     static char endcmd[2] = { '\0', '\0' };
    212     char ch, *cp, *ocursor = el->el_line.cursor, oldpchar = pchar;
    213 
    214     el_action_t ret = CC_NORM;
    215 
    216     int ohisteventno = el->el_history.eventno,
    217 	oldpatlen = el->el_search.patlen,
    218 	newdir = dir,
    219         done, redo;
    220 
    221     if (el->el_line.lastchar + sizeof(STRfwd) / sizeof(char) + 2 +
    222 	el->el_search.patlen >= el->el_line.limit)
    223 	return CC_ERROR;
    224 
    225     for (;;) {
    226 
    227 	if (el->el_search.patlen == 0) {	/* first round */
    228 	    pchar = ':';
    229 #ifdef ANCHOR
    230 	    el->el_search.patbuf[el->el_search.patlen++] = '.';
    231 	    el->el_search.patbuf[el->el_search.patlen++] = '*';
    232 #endif
    233 	}
    234 	done = redo = 0;
    235 	*el->el_line.lastchar++ = '\n';
    236 	for (cp = newdir == ED_SEARCH_PREV_HISTORY ? STRbck : STRfwd;
    237 	     *cp; *el->el_line.lastchar++ = *cp++)
    238 	     continue;
    239 	*el->el_line.lastchar++ = pchar;
    240 	for (cp = &el->el_search.patbuf[1];
    241 	      cp < &el->el_search.patbuf[el->el_search.patlen];
    242 	      *el->el_line.lastchar++ = *cp++)
    243 	    continue;
    244 	*el->el_line.lastchar = '\0';
    245 	re_refresh(el);
    246 
    247 	if (el_getc(el, &ch) != 1)
    248 	    return ed_end_of_file(el, 0);
    249 
    250 	switch (el->el_map.current[(unsigned char) ch]) {
    251 	case ED_INSERT:
    252 	case ED_DIGIT:
    253 	    if (el->el_search.patlen > EL_BUFSIZ - 3)
    254 		term_beep(el);
    255 	    else {
    256 		el->el_search.patbuf[el->el_search.patlen++] = 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 ED_DELETE_PREV_CHAR:
    274 	    if (el->el_search.patlen > 1)
    275 		done++;
    276 	    else
    277 		term_beep(el);
    278 	    break;
    279 
    280 	default:
    281 	    switch (ch) {
    282 	    case 0007:		/* ^G: Abort */
    283 		ret = CC_ERROR;
    284 		done++;
    285 		break;
    286 
    287 	    case 0027:		/* ^W: Append word */
    288 		/* No can do if globbing characters in pattern */
    289 		for (cp = &el->el_search.patbuf[1]; ; cp++)
    290 		    if (cp >= &el->el_search.patbuf[el->el_search.patlen]) {
    291 			el->el_line.cursor += el->el_search.patlen - 1;
    292 			cp = c__next_word(el->el_line.cursor,
    293 					  el->el_line.lastchar, 1, ce__isword);
    294 			while (el->el_line.cursor < cp &&
    295 			       *el->el_line.cursor != '\n') {
    296 			    if (el->el_search.patlen > EL_BUFSIZ - 3) {
    297 				term_beep(el);
    298 				break;
    299 			    }
    300 			    el->el_search.patbuf[el->el_search.patlen++] =
    301 				*el->el_line.cursor;
    302 			    *el->el_line.lastchar++ = *el->el_line.cursor++;
    303 			}
    304 			el->el_line.cursor = ocursor;
    305 			*el->el_line.lastchar = '\0';
    306 			re_refresh(el);
    307 			break;
    308 		    } else if (isglob(*cp)) {
    309 			term_beep(el);
    310 			break;
    311 		    }
    312 		break;
    313 
    314 	    default:		/* Terminate and execute cmd */
    315 		endcmd[0] = ch;
    316 		el_push(el, endcmd);
    317 		/*FALLTHROUGH*/
    318 
    319 	    case 0033:		/* ESC: Terminate */
    320 		ret = CC_REFRESH;
    321 		done++;
    322 		break;
    323 	    }
    324 	    break;
    325 	}
    326 
    327 	while (el->el_line.lastchar > el->el_line.buffer &&
    328 	       *el->el_line.lastchar != '\n')
    329 	    *el->el_line.lastchar-- = '\0';
    330 	*el->el_line.lastchar = '\0';
    331 
    332 	if (!done) {
    333 
    334 	    /* Can't search if unmatched '[' */
    335 	    for (cp = &el->el_search.patbuf[el->el_search.patlen-1], ch = ']';
    336 		 cp > el->el_search.patbuf; cp--)
    337 		if (*cp == '[' || *cp == ']') {
    338 		    ch = *cp;
    339 		    break;
    340 		}
    341 
    342 	    if (el->el_search.patlen > 1 && ch != '[') {
    343 		if (redo && newdir == dir) {
    344 		    if (pchar == '?') {	/* wrap around */
    345 			el->el_history.eventno =
    346 			    newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff;
    347 			if (hist_get(el) == CC_ERROR)
    348 			    /* el->el_history.eventno was fixed by first call */
    349 			    (void) hist_get(el);
    350 			el->el_line.cursor = newdir == ED_SEARCH_PREV_HISTORY ?
    351 			    el->el_line.lastchar : el->el_line.buffer;
    352 		    } else
    353 			el->el_line.cursor +=
    354 				newdir == ED_SEARCH_PREV_HISTORY ? -1 : 1;
    355 		}
    356 #ifdef ANCHOR
    357 		el->el_search.patbuf[el->el_search.patlen++] = '.';
    358 		el->el_search.patbuf[el->el_search.patlen++] = '*';
    359 #endif
    360 		el->el_search.patbuf[el->el_search.patlen] = '\0';
    361 		if (el->el_line.cursor < el->el_line.buffer ||
    362 		    el->el_line.cursor > el->el_line.lastchar ||
    363 		    (ret = ce_search_line(el, &el->el_search.patbuf[1],
    364 					  newdir)) == CC_ERROR) {
    365 		    /* avoid c_setpat */
    366 		    el->el_state.lastcmd = (el_action_t) newdir;
    367 		    ret = newdir == ED_SEARCH_PREV_HISTORY ?
    368 			ed_search_prev_history(el, 0) :
    369 			ed_search_next_history(el, 0);
    370 		    if (ret != CC_ERROR) {
    371 			el->el_line.cursor = newdir == ED_SEARCH_PREV_HISTORY ?
    372 			    el->el_line.lastchar : el->el_line.buffer;
    373 			(void) ce_search_line(el, &el->el_search.patbuf[1],
    374 					      newdir);
    375 		    }
    376 		}
    377 		el->el_search.patbuf[--el->el_search.patlen] = '\0';
    378 		if (ret == CC_ERROR) {
    379 		    term_beep(el);
    380 		    if (el->el_history.eventno != ohisteventno) {
    381 			el->el_history.eventno = ohisteventno;
    382 			if (hist_get(el) == CC_ERROR)
    383 			    return CC_ERROR;
    384 		    }
    385 		    el->el_line.cursor = ocursor;
    386 		    pchar = '?';
    387 		} else {
    388 		    pchar = ':';
    389 		}
    390 	    }
    391 
    392 	    ret = ce_inc_search(el, newdir);
    393 
    394 	    if (ret == CC_ERROR && pchar == '?' && oldpchar == ':')
    395 		/* break abort of failed search at last non-failed */
    396 		ret = CC_NORM;
    397 
    398 	}
    399 
    400 	if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) {
    401 	    /* restore on normal return or error exit */
    402 	    pchar = oldpchar;
    403 	    el->el_search.patlen = oldpatlen;
    404 	    if (el->el_history.eventno != ohisteventno) {
    405 		el->el_history.eventno = ohisteventno;
    406 		if (hist_get(el) == CC_ERROR)
    407 		    return CC_ERROR;
    408 	    }
    409 	    el->el_line.cursor = ocursor;
    410 	    if (ret == CC_ERROR)
    411 		re_refresh(el);
    412 	}
    413 	if (done || ret != CC_NORM)
    414 	    return ret;
    415     }
    416 }
    417 
    418 
    419 /* cv_search():
    420  *	Vi search.
    421  */
    422 protected el_action_t
    423 cv_search(el, dir)
    424     EditLine *el;
    425     int dir;
    426 {
    427     char ch;
    428     char tmpbuf[EL_BUFSIZ];
    429     int tmplen;
    430 
    431     tmplen = 0;
    432 #ifdef ANCHOR
    433     tmpbuf[tmplen++] = '.';
    434     tmpbuf[tmplen++] = '*';
    435 #endif
    436 
    437     el->el_line.buffer[0] = '\0';
    438     el->el_line.lastchar = el->el_line.buffer;
    439     el->el_line.cursor = el->el_line.buffer;
    440     el->el_search.patdir = dir;
    441 
    442     c_insert(el, 2);	/* prompt + '\n' */
    443     *el->el_line.cursor++ = '\n';
    444     *el->el_line.cursor++ = dir == ED_SEARCH_PREV_HISTORY ? '/' : '?';
    445     re_refresh(el);
    446 
    447 #ifdef ANCHOR
    448 # define LEN 2
    449 #else
    450 # define LEN 0
    451 #endif
    452 
    453     tmplen = c_gets(el, &tmpbuf[LEN]) + LEN;
    454     ch = tmpbuf[tmplen];
    455     tmpbuf[tmplen] = '\0';
    456 
    457     if (tmplen == LEN) {
    458 	/*
    459 	 * Use the old pattern, but wild-card it.
    460 	 */
    461 	if (el->el_search.patlen == 0) {
    462 	    el->el_line.buffer[0] = '\0';
    463 	    el->el_line.lastchar = el->el_line.buffer;
    464 	    el->el_line.cursor = el->el_line.buffer;
    465 	    re_refresh(el);
    466 	    return CC_ERROR;
    467 	}
    468 #ifdef ANCHOR
    469 	if (el->el_search.patbuf[0] != '.' && el->el_search.patbuf[0] != '*') {
    470 	    (void) strcpy(tmpbuf, el->el_search.patbuf);
    471 	    el->el_search.patbuf[0] = '.';
    472 	    el->el_search.patbuf[1] = '*';
    473 	    (void) strcpy(&el->el_search.patbuf[2], tmpbuf);
    474 	    el->el_search.patlen++;
    475 	    el->el_search.patbuf[el->el_search.patlen++] = '.';
    476 	    el->el_search.patbuf[el->el_search.patlen++] = '*';
    477 	    el->el_search.patbuf[el->el_search.patlen] = '\0';
    478 	}
    479 #endif
    480     }
    481     else {
    482 #ifdef ANCHOR
    483 	tmpbuf[tmplen++] = '.';
    484 	tmpbuf[tmplen++] = '*';
    485 #endif
    486 	tmpbuf[tmplen] = '\0';
    487 	(void) strcpy(el->el_search.patbuf, tmpbuf);
    488 	el->el_search.patlen = tmplen;
    489     }
    490     el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */
    491     el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer;
    492     if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) :
    493 			        ed_search_next_history(el, 0)) == CC_ERROR) {
    494 	re_refresh(el);
    495 	return CC_ERROR;
    496     }
    497     else {
    498 	if (ch == 0033) {
    499 	    re_refresh(el);
    500 	    *el->el_line.lastchar++ = '\n';
    501 	    *el->el_line.lastchar = '\0';
    502 	    re_goto_bottom(el);
    503 	    return CC_NEWLINE;
    504 	}
    505 	else
    506 	    return CC_REFRESH;
    507     }
    508 }
    509 
    510 
    511 /* ce_search_line():
    512  *	Look for a pattern inside a line
    513  */
    514 protected el_action_t
    515 ce_search_line(el, pattern, dir)
    516     EditLine *el;
    517     char *pattern;
    518     int dir;
    519 {
    520     char *cp;
    521 
    522     if (dir == ED_SEARCH_PREV_HISTORY) {
    523 	for (cp = el->el_line.cursor; cp >= el->el_line.buffer; cp--)
    524 	    if (el_match(cp, pattern)) {
    525 		el->el_line.cursor = cp;
    526 		return CC_NORM;
    527 	    }
    528 	return CC_ERROR;
    529     } else {
    530 	for (cp = el->el_line.cursor; *cp != '\0' &&
    531 	     cp < el->el_line.limit; cp++)
    532 	    if (el_match(cp, pattern)) {
    533 		el->el_line.cursor = cp;
    534 		return CC_NORM;
    535 	    }
    536 	return CC_ERROR;
    537     }
    538 }
    539 
    540 
    541 /* cv_repeat_srch():
    542  *	Vi repeat search
    543  */
    544 protected el_action_t
    545 cv_repeat_srch(el, c)
    546     EditLine *el;
    547     int c;
    548 {
    549 #ifdef SDEBUG
    550     (void) fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n",
    551 		   c, el->el_search.patlen, el->el_search.patbuf);
    552 #endif
    553 
    554     el->el_state.lastcmd = (el_action_t) c;  /* Hack to stop c_setpat */
    555     el->el_line.lastchar = el->el_line.buffer;
    556 
    557     switch (c) {
    558     case ED_SEARCH_NEXT_HISTORY:
    559 	return ed_search_next_history(el, 0);
    560     case ED_SEARCH_PREV_HISTORY:
    561 	return ed_search_prev_history(el, 0);
    562     default:
    563 	return CC_ERROR;
    564     }
    565 }
    566 
    567 
    568 /* cv_csearch_back():
    569  *	Vi character search reverse
    570  */
    571 protected el_action_t
    572 cv_csearch_back(el, ch, count, tflag)
    573     EditLine *el;
    574     int ch, count, tflag;
    575 {
    576     char *cp;
    577 
    578     cp = el->el_line.cursor;
    579     while (count--) {
    580 	if (*cp == ch)
    581 	    cp--;
    582 	while (cp > el->el_line.buffer && *cp != ch)
    583 	    cp--;
    584     }
    585 
    586     if (cp < el->el_line.buffer || (cp == el->el_line.buffer && *cp != ch))
    587 	return CC_ERROR;
    588 
    589     if (*cp == ch && tflag)
    590 	cp++;
    591 
    592     el->el_line.cursor = cp;
    593 
    594     if (el->el_chared.c_vcmd.action & DELETE) {
    595 	el->el_line.cursor++;
    596 	cv_delfini(el);
    597 	return CC_REFRESH;
    598     }
    599 
    600     re_refresh_cursor(el);
    601     return CC_NORM;
    602 }
    603 
    604 
    605 /* cv_csearch_fwd():
    606  *	Vi character search forward
    607  */
    608 protected el_action_t
    609 cv_csearch_fwd(el, ch, count, tflag)
    610     EditLine *el;
    611     int ch, count, tflag;
    612 {
    613     char *cp;
    614 
    615     cp = el->el_line.cursor;
    616     while (count--) {
    617 	if(*cp == ch)
    618 	    cp++;
    619 	while (cp < el->el_line.lastchar && *cp != ch)
    620 	    cp++;
    621     }
    622 
    623     if (cp >= el->el_line.lastchar)
    624 	return CC_ERROR;
    625 
    626     if (*cp == ch && tflag)
    627 	cp--;
    628 
    629     el->el_line.cursor = cp;
    630 
    631     if (el->el_chared.c_vcmd.action & DELETE) {
    632 	el->el_line.cursor++;
    633 	cv_delfini(el);
    634 	return CC_REFRESH;
    635     }
    636     re_refresh_cursor(el);
    637     return CC_NORM;
    638 }
    639