1/*	$OpenBSD: search.c,v 1.50 2023/03/08 04:43:11 guenther Exp $	*/
2
3/* This file is in the public domain. */
4
5/*
6 *		Search commands.
7 * The functions in this file implement the search commands (both plain and
8 * incremental searches are supported) and the query-replace command.
9 *
10 * The plain old search code is part of the original MicroEMACS "distribution".
11 * The incremental search code and the query-replace code is by Rich Ellison.
12 */
13
14#include <sys/queue.h>
15#include <ctype.h>
16#include <signal.h>
17#include <stdio.h>
18#include <string.h>
19
20#include "def.h"
21#include "macro.h"
22
23#define SRCH_BEGIN	(0)	/* Search sub-codes.	 */
24#define SRCH_FORW	(-1)
25#define SRCH_BACK	(-2)
26#define SRCH_NOPR	(-3)
27#define SRCH_ACCM	(-4)
28#define SRCH_MARK	(-5)
29
30struct srchcom {
31	int		 s_code;
32	struct line	*s_dotp;
33	int		 s_doto;
34	int		 s_dotline;
35};
36
37static int	isearch(int);
38static void	is_cpush(int);
39static void	is_lpush(void);
40static void	is_pop(void);
41static int	is_peek(void);
42static void	is_undo(int *, int *);
43static int	is_find(int);
44static void	is_prompt(int, int, int);
45static void	is_dspl(char *, int);
46static int	eq(int, int, int);
47
48static struct srchcom	cmds[NSRCH];
49static int	cip;
50
51int		srch_lastdir = SRCH_NOPR;	/* Last search flags.	 */
52
53/*
54 * Search forward.  Get a search string from the user, and search for it
55 * starting at ".".  If found, "." gets moved to just after the matched
56 * characters, and display does all the hard stuff.  If not found, it just
57 * prints a message.
58 */
59int
60forwsearch(int f, int n)
61{
62	int	s;
63
64	if ((s = readpattern("Search")) != TRUE)
65		return (s);
66	if (forwsrch() == FALSE) {
67		dobeep();
68		ewprintf("Search failed: \"%s\"", pat);
69		return (FALSE);
70	}
71	srch_lastdir = SRCH_FORW;
72	return (TRUE);
73}
74
75/*
76 * Reverse search.  Get a search string from the user, and search, starting
77 * at "." and proceeding toward the front of the buffer.  If found "." is
78 * left pointing at the first character of the pattern [the last character
79 * that was matched].
80 */
81int
82backsearch(int f, int n)
83{
84	int	s;
85
86	if ((s = readpattern("Search backward")) != TRUE)
87		return (s);
88	if (backsrch() == FALSE) {
89		dobeep();
90		ewprintf("Search failed: \"%s\"", pat);
91		return (FALSE);
92	}
93	srch_lastdir = SRCH_BACK;
94	return (TRUE);
95}
96
97/*
98 * Search again, using the same search string and direction as the last
99 * search command. The direction has been saved in "srch_lastdir", so you
100 * know which way to go.
101 */
102int
103searchagain(int f, int n)
104{
105	if (srch_lastdir == SRCH_FORW) {
106		if (forwsrch() == FALSE) {
107			dobeep();
108			ewprintf("Search failed: \"%s\"", pat);
109			return (FALSE);
110		}
111		return (TRUE);
112	}
113	if (srch_lastdir == SRCH_BACK) {
114		if (backsrch() == FALSE) {
115			dobeep();
116			ewprintf("Search failed: \"%s\"", pat);
117			return (FALSE);
118		}
119		return (TRUE);
120	}
121	dobeep();
122	ewprintf("No last search");
123	return (FALSE);
124}
125
126/*
127 * Use incremental searching, initially in the forward direction.
128 * isearch ignores any explicit arguments.
129 */
130int
131forwisearch(int f, int n)
132{
133	if (macrodef || inmacro)
134		/* We can't isearch in macro. Use search instead */
135		return (forwsearch(f,n));
136	else
137		return (isearch(SRCH_FORW));
138}
139
140/*
141 * Use incremental searching, initially in the reverse direction.
142 * isearch ignores any explicit arguments.
143 */
144int
145backisearch(int f, int n)
146{
147	if (macrodef || inmacro)
148		/* We can't isearch in macro. Use search instead */
149		return (backsearch(f,n));
150	else
151		return (isearch(SRCH_BACK));
152}
153
154/*
155 * Incremental Search.
156 *	dir is used as the initial direction to search.
157 *	^M	exit from Isearch, set mark
158 *	^S	switch direction to forward
159 *	^R	switch direction to reverse
160 *	^Q	quote next character (allows searching for ^N etc.)
161 *	<ESC>	exit from Isearch, set mark
162 *	<DEL>	undoes last character typed. (tricky job to do this correctly).
163 *	other ^ exit search, don't set mark
164 *	else	accumulate into search string
165 */
166static int
167isearch(int dir)
168{
169	struct line	*clp;		/* Saved line pointer */
170	int		 c;
171	int		 cbo;		/* Saved offset */
172	int		 success;
173	int		 pptr;
174	int		 firstc;
175	int		 xcase;
176	int		 i;
177	char		 opat[NPAT];
178	int		 cdotline;	/* Saved line number */
179
180	if (macrodef) {
181		dobeep();
182		ewprintf("Can't isearch in macro");
183		return (FALSE);
184	}
185	for (cip = 0; cip < NSRCH; cip++)
186		cmds[cip].s_code = SRCH_NOPR;
187
188	(void)strlcpy(opat, pat, sizeof(opat));
189	cip = 0;
190	pptr = -1;
191	clp = curwp->w_dotp;
192	cbo = curwp->w_doto;
193	cdotline = curwp->w_dotline;
194	is_lpush();
195	is_cpush(SRCH_BEGIN);
196	success = TRUE;
197	is_prompt(dir, TRUE, success);
198
199	for (;;) {
200		update(CMODE);
201
202		switch (c = getkey(FALSE)) {
203		case CCHR('['):
204			/*
205			 * If new characters come in the next 300 msec,
206			 * we can assume that they belong to a longer
207			 * escaped sequence so we should ungetkey the
208			 * ESC to avoid writing out garbage.
209			 */
210			if (ttwait(300) == FALSE)
211				ungetkey(c);
212			/* FALLTHRU */
213		case CCHR('M'):
214			srch_lastdir = dir;
215			curwp->w_markp = clp;
216			curwp->w_marko = cbo;
217			curwp->w_markline = cdotline;
218			ewprintf("Mark set");
219			return (TRUE);
220		case CCHR('G'):
221			if (success != TRUE) {
222				while (is_peek() == SRCH_ACCM)
223					is_undo(&pptr, &dir);
224				success = TRUE;
225				is_prompt(dir, pptr < 0, success);
226				break;
227			}
228			curwp->w_dotp = clp;
229			curwp->w_doto = cbo;
230			curwp->w_dotline = cdotline;
231			curwp->w_rflag |= WFMOVE;
232			srch_lastdir = dir;
233			(void)ctrlg(FFRAND, 0);
234			(void)strlcpy(pat, opat, sizeof(pat));
235			return (ABORT);
236		case CCHR('S'):
237			if (dir == SRCH_BACK) {
238				dir = SRCH_FORW;
239				is_lpush();
240				is_cpush(SRCH_FORW);
241				success = TRUE;
242			}
243			if (success == FALSE && dir == SRCH_FORW) {
244				/* wrap the search to beginning */
245				curwp->w_dotp = bfirstlp(curbp);
246				curwp->w_doto = 0;
247				curwp->w_dotline = 1;
248				if (is_find(dir) != FALSE) {
249					is_cpush(SRCH_MARK);
250					success = TRUE;
251				}
252				ewprintf("Overwrapped I-search: %s", pat);
253				break;
254			}
255			is_lpush();
256			pptr = strlen(pat);
257			if (forwchar(FFRAND, 1) == FALSE) {
258                                dobeep();
259                                success = FALSE;
260                                ewprintf("Failed I-search: %s", pat);
261			} else {
262				if (is_find(SRCH_FORW) != FALSE)
263					is_cpush(SRCH_MARK);
264				else {
265					(void)backchar(FFRAND, 1);
266					dobeep();
267					success = FALSE;
268					ewprintf("Failed I-search: %s", pat);
269				}
270			}
271			is_prompt(dir, pptr < 0, success);
272			break;
273		case CCHR('R'):
274			if (dir == SRCH_FORW) {
275				dir = SRCH_BACK;
276				is_lpush();
277				is_cpush(SRCH_BACK);
278				success = TRUE;
279			}
280			if (success == FALSE && dir == SRCH_BACK) {
281				/* wrap the search to end */
282				curwp->w_dotp = blastlp(curbp);
283				curwp->w_doto = llength(curwp->w_dotp);
284				curwp->w_dotline = curwp->w_bufp->b_lines;
285				if (is_find(dir) != FALSE) {
286					is_cpush(SRCH_MARK);
287					success = TRUE;
288				}
289				ewprintf("Overwrapped I-search: %s", pat);
290				break;
291			}
292			is_lpush();
293			pptr = strlen(pat);
294                        if (backchar(FFRAND, 1) == FALSE) {
295                                dobeep();
296                                success = FALSE;
297                        } else {
298				if (is_find(SRCH_BACK) != FALSE)
299					is_cpush(SRCH_MARK);
300				else {
301					(void)forwchar(FFRAND, 1);
302					dobeep();
303					success = FALSE;
304				}
305			}
306			is_prompt(dir, pptr < 0, success);
307			break;
308		case CCHR('W'):
309			/* add the rest of the current word to the pattern */
310			clp = curwp->w_dotp;
311			cbo = curwp->w_doto;
312			firstc = 1;
313			if (pptr == -1)
314				pptr = 0;
315			if (dir == SRCH_BACK) {
316				/* when isearching backwards, cbo is the start of the pattern */
317				cbo += pptr;
318			}
319
320			/* if the search is case insensitive, add to pattern using lowercase */
321			xcase = 0;
322			for (i = 0; pat[i]; i++)
323				if (ISUPPER(CHARMASK(pat[i])))
324					xcase = 1;
325
326			while (cbo < llength(clp)) {
327				c = lgetc(clp, cbo++);
328				if ((!firstc && !isalnum(c)))
329					break;
330
331				if (pptr == NPAT - 1) {
332					dobeep();
333					break;
334				}
335				firstc = 0;
336				if (!xcase && ISUPPER(c))
337					c = TOLOWER(c);
338
339				pat[pptr++] = c;
340				pat[pptr] = '\0';
341				/* cursor only moves when isearching forwards */
342				if (dir == SRCH_FORW) {
343					curwp->w_doto = cbo;
344					curwp->w_rflag |= WFMOVE;
345					update(CMODE);
346				}
347			}
348			is_prompt(dir, pptr < 0, success);
349			break;
350		case CCHR('H'):
351		case CCHR('?'):
352			is_undo(&pptr, &dir);
353			if (is_peek() != SRCH_ACCM)
354				success = TRUE;
355			is_prompt(dir, pptr < 0, success);
356			break;
357		case CCHR('\\'):
358		case CCHR('Q'):
359			c = (char)getkey(FALSE);
360			goto addchar;
361		default:
362			if (ISCTRL(c)) {
363				ungetkey(c);
364				curwp->w_markp = clp;
365				curwp->w_marko = cbo;
366				curwp->w_markline = cdotline;
367				ewprintf("Mark set");
368				curwp->w_rflag |= WFMOVE;
369				return (TRUE);
370			}
371			/* FALLTHRU */
372		case CCHR('I'):
373		case CCHR('J'):
374	addchar:
375			if (pptr == -1)
376				pptr = 0;
377			if (pptr == 0)
378				success = TRUE;
379			if (pptr == NPAT - 1)
380				dobeep();
381			else {
382				pat[pptr++] = c;
383				pat[pptr] = '\0';
384			}
385			is_lpush();
386			if (success != FALSE) {
387				if (is_find(dir) != FALSE)
388					is_cpush(c);
389				else {
390					success = FALSE;
391					dobeep();
392					is_cpush(SRCH_ACCM);
393				}
394			} else
395				is_cpush(SRCH_ACCM);
396			is_prompt(dir, FALSE, success);
397		}
398	}
399	/* NOTREACHED */
400}
401
402static void
403is_cpush(int cmd)
404{
405	if (++cip >= NSRCH)
406		cip = 0;
407	cmds[cip].s_code = cmd;
408}
409
410static void
411is_lpush(void)
412{
413	int	ctp;
414
415	ctp = cip + 1;
416	if (ctp >= NSRCH)
417		ctp = 0;
418	cmds[ctp].s_code = SRCH_NOPR;
419	cmds[ctp].s_doto = curwp->w_doto;
420	cmds[ctp].s_dotp = curwp->w_dotp;
421	cmds[ctp].s_dotline = curwp->w_dotline;
422}
423
424static void
425is_pop(void)
426{
427	if (cmds[cip].s_code != SRCH_NOPR) {
428		curwp->w_doto = cmds[cip].s_doto;
429		curwp->w_dotp = cmds[cip].s_dotp;
430		curwp->w_dotline = cmds[cip].s_dotline;
431		curwp->w_rflag |= WFMOVE;
432		cmds[cip].s_code = SRCH_NOPR;
433	}
434	if (--cip <= 0)
435		cip = NSRCH - 1;
436}
437
438static int
439is_peek(void)
440{
441	return (cmds[cip].s_code);
442}
443
444/* this used to always return TRUE (the return value was checked) */
445static void
446is_undo(int *pptr, int *dir)
447{
448	int	redo = FALSE;
449
450	switch (cmds[cip].s_code) {
451	case SRCH_BEGIN:
452	case SRCH_NOPR:
453		*pptr = -1;
454		break;
455	case SRCH_MARK:
456		break;
457	case SRCH_FORW:
458		*dir = SRCH_BACK;
459		redo = TRUE;
460		break;
461	case SRCH_BACK:
462		*dir = SRCH_FORW;
463		redo = TRUE;
464		break;
465	case SRCH_ACCM:
466	default:
467		*pptr -= 1;
468		if (*pptr < 0)
469			*pptr = 0;
470		pat[*pptr] = '\0';
471		break;
472	}
473	is_pop();
474	if (redo)
475		is_undo(pptr, dir);
476}
477
478static int
479is_find(int dir)
480{
481	int	 plen, odoto, odotline;
482	struct line	*odotp;
483
484	odoto = curwp->w_doto;
485	odotp = curwp->w_dotp;
486	odotline = curwp->w_dotline;
487	plen = strlen(pat);
488	if (plen != 0) {
489		if (dir == SRCH_FORW) {
490			(void)backchar(FFARG | FFRAND, plen);
491			if (forwsrch() == FALSE) {
492				curwp->w_doto = odoto;
493				curwp->w_dotp = odotp;
494				curwp->w_dotline = odotline;
495				return (FALSE);
496			}
497			return (TRUE);
498		}
499		if (dir == SRCH_BACK) {
500			(void)forwchar(FFARG | FFRAND, plen);
501			if (backsrch() == FALSE) {
502				curwp->w_doto = odoto;
503				curwp->w_dotp = odotp;
504				curwp->w_dotline = odotline;
505				return (FALSE);
506			}
507			return (TRUE);
508		}
509		dobeep();
510		ewprintf("bad call to is_find");
511		return (FALSE);
512	}
513	return (FALSE);
514}
515
516/*
517 * If called with "dir" not one of SRCH_FORW or SRCH_BACK, this routine used
518 * to print an error message.  It also used to return TRUE or FALSE, depending
519 * on if it liked the "dir".  However, none of the callers looked at the
520 * status, so I just made the checking vanish.
521 */
522static void
523is_prompt(int dir, int flag, int success)
524{
525	if (dir == SRCH_FORW) {
526		if (success != FALSE)
527			is_dspl("I-search", flag);
528		else
529			is_dspl("Failing I-search", flag);
530	} else if (dir == SRCH_BACK) {
531		if (success != FALSE)
532			is_dspl("I-search backward", flag);
533		else
534			is_dspl("Failing I-search backward", flag);
535	} else
536		ewprintf("Broken call to is_prompt");
537}
538
539/*
540 * Prompt writing routine for the incremental search.  The "i_prompt" is just
541 * a string. The "flag" determines whether pat should be printed.
542 */
543static void
544is_dspl(char *i_prompt, int flag)
545{
546	if (flag != FALSE)
547		ewprintf("%s: ", i_prompt);
548	else
549		ewprintf("%s: %s", i_prompt, pat);
550}
551
552/*
553 * Query Replace.
554 *	Replace strings selectively.  Does a search and replace operation.
555 */
556int
557queryrepl(int f, int n)
558{
559	int	s;
560	int	rcnt = 0;		/* replacements made so far	*/
561	int	plen;			/* length of found string	*/
562	char	news[NPAT], *rep;	/* replacement string		*/
563
564	if (macrodef) {
565		dobeep();
566		ewprintf("Can't query replace in macro");
567		return (FALSE);
568	}
569
570	if ((s = readpattern("Query replace")) != TRUE)
571		return (s);
572	if ((rep = eread("Query replace %s with: ", news, NPAT,
573	    EFNUL | EFNEW | EFCR, pat)) == NULL)
574		return (ABORT);
575	else if (rep[0] == '\0')
576		news[0] = '\0';
577	ewprintf("Query replacing %s with %s:", pat, news);
578	plen = strlen(pat);
579
580	/*
581	 * Search forward repeatedly, checking each time whether to insert
582	 * or not.  The "!" case makes the check always true, so it gets put
583	 * into a tighter loop for efficiency.
584	 */
585	while (forwsrch() == TRUE) {
586retry:
587		update(CMODE);
588		switch (getkey(FALSE)) {
589		case 'y':
590		case ' ':
591			if (lreplace((RSIZE)plen, news) == FALSE)
592				return (FALSE);
593			rcnt++;
594			break;
595		case '.':
596			if (lreplace((RSIZE)plen, news) == FALSE)
597				return (FALSE);
598			rcnt++;
599			goto stopsearch;
600		/* ^G, CR or ESC */
601		case CCHR('G'):
602			(void)ctrlg(FFRAND, 0);
603			goto stopsearch;
604		case CCHR('['):
605		case CCHR('M'):
606			goto stopsearch;
607		case '!':
608			do {
609				if (lreplace((RSIZE)plen, news) == FALSE)
610					return (FALSE);
611				rcnt++;
612			} while (forwsrch() == TRUE);
613			goto stopsearch;
614		case 'n':
615		case CCHR('H'):
616		/* To not replace */
617		case CCHR('?'):
618			break;
619		default:
620			ewprintf("y/n or <SP>/<DEL>: replace/don't, [.] repl-end, [!] repl-rest, <CR>/<ESC> quit");
621			goto retry;
622		}
623	}
624stopsearch:
625	curwp->w_rflag |= WFFULL;
626	update(CMODE);
627	if (rcnt == 1)
628		ewprintf("Replaced 1 occurrence");
629	else
630		ewprintf("Replaced %d occurrences", rcnt);
631	return (TRUE);
632}
633
634/*
635 * Replace string globally without individual prompting.
636 */
637int
638replstr(int f, int n)
639{
640	char	news[NPAT];
641	int	s, plen, rcnt = 0;
642	char	*r;
643
644	if ((s = readpattern("Replace string")) != TRUE)
645		return s;
646
647	r = eread("Replace string %s with: ", news, NPAT,
648	    EFNUL | EFNEW | EFCR,  pat);
649	if (r == NULL)
650		 return (ABORT);
651
652	plen = strlen(pat);
653	while (forwsrch() == TRUE) {
654		update(CMODE);
655		if (lreplace((RSIZE)plen, news) == FALSE)
656			return (FALSE);
657
658		rcnt++;
659	}
660
661	curwp->w_rflag |= WFFULL;
662	update(CMODE);
663
664	if (rcnt == 1)
665		ewprintf("Replaced 1 occurrence");
666	else
667		ewprintf("Replaced %d occurrences", rcnt);
668
669	return (TRUE);
670}
671
672/*
673 * This routine does the real work of a forward search.  The pattern is sitting
674 * in the external variable "pat".  If found, dot is updated, the window system
675 * is notified of the change, and TRUE is returned.  If the string isn't found,
676 * FALSE is returned.
677 */
678int
679forwsrch(void)
680{
681	struct line	*clp, *tlp;
682	int	 cbo, tbo, c, i, xcase = 0;
683	char	*pp;
684	int	 nline;
685
686	clp = curwp->w_dotp;
687	cbo = curwp->w_doto;
688	nline = curwp->w_dotline;
689	for (i = 0; pat[i]; i++)
690		if (ISUPPER(CHARMASK(pat[i])))
691			xcase = 1;
692	for (;;) {
693		if (cbo == llength(clp)) {
694			if ((clp = lforw(clp)) == curbp->b_headp)
695				break;
696			nline++;
697			cbo = 0;
698			c = CCHR('J');
699		} else
700			c = lgetc(clp, cbo++);
701		if (eq(c, pat[0], xcase) != FALSE) {
702			tlp = clp;
703			tbo = cbo;
704			pp = &pat[1];
705			while (*pp != 0) {
706				if (tbo == llength(tlp)) {
707					tlp = lforw(tlp);
708					if (tlp == curbp->b_headp)
709						goto fail;
710					tbo = 0;
711					c = CCHR('J');
712					if (eq(c, *pp++, xcase) == FALSE)
713						goto fail;
714					nline++;
715				} else {
716					c = lgetc(tlp, tbo++);
717					if (eq(c, *pp++, xcase) == FALSE)
718						goto fail;
719				}
720			}
721			curwp->w_dotp = tlp;
722			curwp->w_doto = tbo;
723			curwp->w_dotline = nline;
724			curwp->w_rflag |= WFMOVE;
725			return (TRUE);
726		}
727fail:		;
728	}
729	return (FALSE);
730}
731
732/*
733 * This routine does the real work of a backward search.  The pattern is
734 * sitting in the external variable "pat".  If found, dot is updated, the
735 * window system is notified of the change, and TRUE is returned.  If the
736 * string isn't found, FALSE is returned.
737 */
738int
739backsrch(void)
740{
741	struct line	*clp, *tlp;
742	int	 cbo, tbo, c, i, xcase = 0;
743	char	*epp, *pp;
744	int	 nline, pline;
745
746	for (epp = &pat[0]; epp[1] != 0; ++epp);
747	clp = curwp->w_dotp;
748	cbo = curwp->w_doto;
749	nline = curwp->w_dotline;
750	for (i = 0; pat[i]; i++)
751		if (ISUPPER(CHARMASK(pat[i])))
752			xcase = 1;
753	for (;;) {
754		if (cbo == 0) {
755			clp = lback(clp);
756			if (clp == curbp->b_headp)
757				return (FALSE);
758			nline--;
759			cbo = llength(clp) + 1;
760		}
761		if (--cbo == llength(clp))
762			c = CCHR('J');
763		else
764			c = lgetc(clp, cbo);
765		if (eq(c, *epp, xcase) != FALSE) {
766			tlp = clp;
767			tbo = cbo;
768			pp = epp;
769			pline = nline;
770			while (pp != &pat[0]) {
771				if (tbo == 0) {
772					tlp = lback(tlp);
773					if (tlp == curbp->b_headp)
774						goto fail;
775					nline--;
776					tbo = llength(tlp) + 1;
777				}
778				if (--tbo == llength(tlp))
779					c = CCHR('J');
780				else
781					c = lgetc(tlp, tbo);
782				if (eq(c, *--pp, xcase) == FALSE) {
783					nline = pline;
784					goto fail;
785				}
786			}
787			curwp->w_dotp = tlp;
788			curwp->w_doto = tbo;
789			curwp->w_dotline = nline;
790			curwp->w_rflag |= WFMOVE;
791			return (TRUE);
792		}
793fail:		;
794	}
795	/* NOTREACHED */
796}
797
798/*
799 * Compare two characters.  The "bc" comes from the buffer.  It has its case
800 * folded out. The "pc" is from the pattern.
801 */
802static int
803eq(int bc, int pc, int xcase)
804{
805	bc = CHARMASK(bc);
806	pc = CHARMASK(pc);
807	if (bc == pc)
808		return (TRUE);
809	if (xcase)
810		return (FALSE);
811	if (ISUPPER(bc))
812		return (TOLOWER(bc) == pc);
813	if (ISUPPER(pc))
814		return (bc == TOLOWER(pc));
815	return (FALSE);
816}
817
818/*
819 * Read a pattern.  Stash it in the external variable "pat".  The "pat" is not
820 * updated if the user types in an empty line.  If the user typed an empty
821 * line, and there is no old pattern, it is an error.  Display the old pattern,
822 * in the style of Jeff Lomicka.  There is some do-it-yourself control
823 * expansion.
824 */
825int
826readpattern(char *r_prompt)
827{
828	char	tpat[NPAT], *rep;
829	int	retval;
830
831	if (pat[0] == '\0')
832		rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, r_prompt);
833	else
834		rep = eread("%s (default %s): ", tpat, NPAT,
835		    EFNUL | EFNEW | EFCR, r_prompt, pat);
836
837	/* specified */
838	if (rep == NULL) {
839		retval = ABORT;
840	} else if (rep[0] != '\0') {
841		(void)strlcpy(pat, tpat, sizeof(pat));
842		retval = TRUE;
843	} else if (pat[0] != '\0') {
844		retval = TRUE;
845	} else
846		retval = FALSE;
847	return (retval);
848}
849
850/*
851 * Prompt for a character and kill until its next occurrence,
852 * including it.  Mark is cleared afterwards.
853 */
854int
855zaptochar(int f, int n)
856{
857	return (zap(TRUE, n));
858}
859
860/* Like zaptochar but stops before the character. */
861int
862zapuptochar(int f, int n)
863{
864	return (zap(FALSE, n));
865}
866
867/*
868 * Prompt for a character and deletes from the point up to, optionally
869 * including, the first instance of that character.  Marks is cleared
870 * afterwards.
871 */
872int
873zap(int including, int n)
874{
875	int	s, backward;
876
877	backward = n < 0;
878	if (backward)
879		n = -n;
880
881	if (including)
882		ewprintf("Zap to char: ");
883	else
884		ewprintf("Zap up to char: ");
885
886	s = getkey(FALSE);
887	eerase();
888	if (s == ABORT || s == CCHR('G'))
889		return (FALSE);
890
891	if (n == 0)
892		return (TRUE);
893
894	pat[0] = (char)s;
895	pat[1] = '\0';
896
897	isetmark();
898	while (n--) {
899		s = backward ? backsrch() : forwsrch();
900		if (s != TRUE) {
901			dobeep();
902			ewprintf("Search failed: \"%s\"", pat);
903			swapmark(FFARG, 0);
904			clearmark(FFARG, 0);
905			return (s);
906		}
907	}
908
909	if (!including) {
910		if (backward)
911			forwchar(FFARG, 1);
912		else
913			backchar(FFARG, 1);
914	}
915
916	killregion(FFARG, 0);
917	clearmark(FFARG, 0);
918	return (TRUE);
919}
920