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