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