xref: /openbsd/usr.bin/mg/search.c (revision 5b133f3f)
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 
30 struct srchcom {
31 	int		 s_code;
32 	struct line	*s_dotp;
33 	int		 s_doto;
34 	int		 s_dotline;
35 };
36 
37 static int	isearch(int);
38 static void	is_cpush(int);
39 static void	is_lpush(void);
40 static void	is_pop(void);
41 static int	is_peek(void);
42 static void	is_undo(int *, int *);
43 static int	is_find(int);
44 static void	is_prompt(int, int, int);
45 static void	is_dspl(char *, int);
46 static int	eq(int, int, int);
47 
48 static struct srchcom	cmds[NSRCH];
49 static int	cip;
50 
51 int		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  */
59 int
forwsearch(int f,int n)60 forwsearch(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  */
81 int
backsearch(int f,int n)82 backsearch(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  */
102 int
searchagain(int f,int n)103 searchagain(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  */
130 int
forwisearch(int f,int n)131 forwisearch(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  */
144 int
backisearch(int f,int n)145 backisearch(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  */
166 static int
isearch(int dir)167 isearch(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 
402 static void
is_cpush(int cmd)403 is_cpush(int cmd)
404 {
405 	if (++cip >= NSRCH)
406 		cip = 0;
407 	cmds[cip].s_code = cmd;
408 }
409 
410 static void
is_lpush(void)411 is_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 
424 static void
is_pop(void)425 is_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 
438 static int
is_peek(void)439 is_peek(void)
440 {
441 	return (cmds[cip].s_code);
442 }
443 
444 /* this used to always return TRUE (the return value was checked) */
445 static void
is_undo(int * pptr,int * dir)446 is_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 
478 static int
is_find(int dir)479 is_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  */
522 static void
is_prompt(int dir,int flag,int success)523 is_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  */
543 static void
is_dspl(char * i_prompt,int flag)544 is_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  */
556 int
queryrepl(int f,int n)557 queryrepl(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) {
586 retry:
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 	}
624 stopsearch:
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  */
637 int
replstr(int f,int n)638 replstr(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  */
678 int
forwsrch(void)679 forwsrch(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 		}
727 fail:		;
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  */
738 int
backsrch(void)739 backsrch(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 		}
793 fail:		;
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  */
802 static int
eq(int bc,int pc,int xcase)803 eq(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  */
825 int
readpattern(char * r_prompt)826 readpattern(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  */
854 int
zaptochar(int f,int n)855 zaptochar(int f, int n)
856 {
857 	return (zap(TRUE, n));
858 }
859 
860 /* Like zaptochar but stops before the character. */
861 int
zapuptochar(int f,int n)862 zapuptochar(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  */
872 int
zap(int including,int n)873 zap(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