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