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