xref: /netbsd-src/lib/libedit/search.c (revision d0fed6c87ddc40a8bffa6f99e7433ddfc864dd83)
1 /*	$NetBSD: search.c,v 1.4 1997/01/23 14:02:47 mrg 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.4 1997/01/23 14:02:47 mrg 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)strncpy(tmpbuf, el->el_search.patbuf, sizeof(tmpbuf) - 1);
471 	    el->el_search.patbuf[0] = '.';
472 	    el->el_search.patbuf[1] = '*';
473 	    (void)strncpy(&el->el_search.patbuf[2], tmpbuf,
474 		sizeof(el->el_search.patbuf) - 3);
475 	    el->el_search.patlen++;
476 	    el->el_search.patbuf[el->el_search.patlen++] = '.';
477 	    el->el_search.patbuf[el->el_search.patlen++] = '*';
478 	    el->el_search.patbuf[el->el_search.patlen] = '\0';
479 	}
480 #endif
481     }
482     else {
483 #ifdef ANCHOR
484 	tmpbuf[tmplen++] = '.';
485 	tmpbuf[tmplen++] = '*';
486 #endif
487 	tmpbuf[tmplen] = '\0';
488 	(void)strncpy(el->el_search.patbuf, tmpbuf,
489 	    sizeof(el->el_search.patbuf) - 1);
490 	el->el_search.patlen = tmplen;
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