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