search.c revision 27397
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, EL_BUFSIZ - 3);
468	    el->el_search.patlen++;
469	    el->el_search.patbuf[el->el_search.patlen++] = '.';
470	    el->el_search.patbuf[el->el_search.patlen++] = '*';
471	    el->el_search.patbuf[el->el_search.patlen] = '\0';
472	}
473#endif
474    }
475    else {
476#ifdef ANCHOR
477	tmpbuf[tmplen++] = '.';
478	tmpbuf[tmplen++] = '*';
479#endif
480	tmpbuf[tmplen] = '\0';
481	(void)strncpy(el->el_search.patbuf, tmpbuf, EL_BUFSIZ - 1);
482	el->el_search.patlen = tmplen;
483    }
484    el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */
485    el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer;
486    if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) :
487			        ed_search_next_history(el, 0)) == CC_ERROR) {
488	re_refresh(el);
489	return CC_ERROR;
490    }
491    else {
492	if (ch == 0033) {
493	    re_refresh(el);
494	    *el->el_line.lastchar++ = '\n';
495	    *el->el_line.lastchar = '\0';
496	    re_goto_bottom(el);
497	    return CC_NEWLINE;
498	}
499	else
500	    return CC_REFRESH;
501    }
502}
503
504
505/* ce_search_line():
506 *	Look for a pattern inside a line
507 */
508protected el_action_t
509ce_search_line(el, pattern, dir)
510    EditLine *el;
511    char *pattern;
512    int dir;
513{
514    char *cp;
515
516    if (dir == ED_SEARCH_PREV_HISTORY) {
517	for (cp = el->el_line.cursor; cp >= el->el_line.buffer; cp--)
518	    if (el_match(cp, pattern)) {
519		el->el_line.cursor = cp;
520		return CC_NORM;
521	    }
522	return CC_ERROR;
523    } else {
524	for (cp = el->el_line.cursor; *cp != '\0' &&
525	     cp < el->el_line.limit; cp++)
526	    if (el_match(cp, pattern)) {
527		el->el_line.cursor = cp;
528		return CC_NORM;
529	    }
530	return CC_ERROR;
531    }
532}
533
534
535/* cv_repeat_srch():
536 *	Vi repeat search
537 */
538protected el_action_t
539cv_repeat_srch(el, c)
540    EditLine *el;
541    int c;
542{
543#ifdef SDEBUG
544    (void) fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n",
545		   c, el->el_search.patlen, el->el_search.patbuf);
546#endif
547
548    el->el_state.lastcmd = (el_action_t) c;  /* Hack to stop c_setpat */
549    el->el_line.lastchar = el->el_line.buffer;
550
551    switch (c) {
552    case ED_SEARCH_NEXT_HISTORY:
553	return ed_search_next_history(el, 0);
554    case ED_SEARCH_PREV_HISTORY:
555	return ed_search_prev_history(el, 0);
556    default:
557	return CC_ERROR;
558    }
559}
560
561
562/* cv_csearch_back():
563 *	Vi character search reverse
564 */
565protected el_action_t
566cv_csearch_back(el, ch, count, tflag)
567    EditLine *el;
568    int ch, count, tflag;
569{
570    char *cp;
571
572    cp = el->el_line.cursor;
573    while (count--) {
574	if (*cp == ch)
575	    cp--;
576	while (cp > el->el_line.buffer && *cp != ch)
577	    cp--;
578    }
579
580    if (cp < el->el_line.buffer || (cp == el->el_line.buffer && *cp != ch))
581	return CC_ERROR;
582
583    if (*cp == ch && tflag)
584	cp++;
585
586    el->el_line.cursor = cp;
587
588    if (el->el_chared.c_vcmd.action & DELETE) {
589	el->el_line.cursor++;
590	cv_delfini(el);
591	return CC_REFRESH;
592    }
593
594    re_refresh_cursor(el);
595    return CC_NORM;
596}
597
598
599/* cv_csearch_fwd():
600 *	Vi character search forward
601 */
602protected el_action_t
603cv_csearch_fwd(el, ch, count, tflag)
604    EditLine *el;
605    int ch, count, tflag;
606{
607    char *cp;
608
609    cp = el->el_line.cursor;
610    while (count--) {
611	if(*cp == ch)
612	    cp++;
613	while (cp < el->el_line.lastchar && *cp != ch)
614	    cp++;
615    }
616
617    if (cp >= el->el_line.lastchar)
618	return CC_ERROR;
619
620    if (*cp == ch && tflag)
621	cp--;
622
623    el->el_line.cursor = cp;
624
625    if (el->el_chared.c_vcmd.action & DELETE) {
626	el->el_line.cursor++;
627	cv_delfini(el);
628	return CC_REFRESH;
629    }
630    re_refresh_cursor(el);
631    return CC_NORM;
632}
633