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