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