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