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