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