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