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