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