xref: /openbsd-src/usr.bin/mg/search.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: search.c,v 1.7 2001/05/24 03:05:26 mickey Exp $	*/
2 
3 /*
4  *		Search commands.
5  * The functions in this file implement the search commands (both plain and
6  * incremental searches are supported) and the query-replace command.
7  *
8  * The plain old search code is part of the original MicroEMACS "distribution".
9  * The incremental search code and the query-replace code is by Rich Ellison.
10  */
11 
12 #include "def.h"
13 
14 #ifndef NO_MACRO
15 #include "macro.h"
16 #endif /* !NO_MACRO */
17 
18 #define SRCH_BEGIN	(0)	/* Search sub-codes.	 */
19 #define SRCH_FORW	(-1)
20 #define SRCH_BACK	(-2)
21 #define SRCH_NOPR	(-3)
22 #define SRCH_ACCM	(-4)
23 #define SRCH_MARK	(-5)
24 
25 typedef struct {
26 	int	 s_code;
27 	LINE	*s_dotp;
28 	int	 s_doto;
29 } SRCHCOM;
30 
31 static int	isearch		__P((int));
32 static void	is_cpush	__P((int));
33 static void	is_lpush	__P((void));
34 static void	is_pop		__P((void));
35 static int	is_peek		__P((void));
36 static void	is_undo		__P((int *, int *));
37 static int	is_find		__P((int));
38 static void	is_prompt	__P((int, int, int));
39 static void	is_dspl		__P((char *, int));
40 static int	eq		__P((int, int));
41 
42 static SRCHCOM	cmds[NSRCH];
43 static int	cip;
44 
45 int		srch_lastdir = SRCH_NOPR;	/* Last search flags.	 */
46 
47 /*
48  * Search forward.  Get a search string from the user, and search for it
49  * starting at ".".  If found, "." gets moved to just after the matched
50  * characters, and display does all the hard stuff.  If not found, it just
51  * prints a message.
52  */
53 /* ARGSUSED */
54 int
55 forwsearch(f, n)
56 	int f, n;
57 {
58 	int	s;
59 
60 	if ((s = readpattern("Search")) != TRUE)
61 		return s;
62 	if (forwsrch() == FALSE) {
63 		ewprintf("Search failed: \"%s\"", pat);
64 		return FALSE;
65 	}
66 	srch_lastdir = SRCH_FORW;
67 	return TRUE;
68 }
69 
70 /*
71  * Reverse search.  Get a search string from the user, and search, starting
72  * at "." and proceeding toward the front of the buffer.  If found "." is
73  * left pointing at the first character of the pattern [the last character
74  * that was matched].
75  */
76 /* ARGSUSED */
77 int
78 backsearch(f, n)
79 	int f, n;
80 {
81 	int	s;
82 
83 	if ((s = readpattern("Search backward")) != TRUE)
84 		return (s);
85 	if (backsrch() == FALSE) {
86 		ewprintf("Search failed: \"%s\"", pat);
87 		return FALSE;
88 	}
89 	srch_lastdir = SRCH_BACK;
90 	return TRUE;
91 }
92 
93 /*
94  * Search again, using the same search string and direction as the last
95  * search command. The direction has been saved in "srch_lastdir", so you
96  * know which way to go.
97  */
98 /* ARGSUSED */
99 int
100 searchagain(f, n)
101 	int f, 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(f, n)
128 	int f, n;
129 {
130 	return isearch(SRCH_FORW);
131 }
132 
133 /*
134  * Use incremental searching, initially in the reverse direction.
135  * isearch ignores any explicit arguments.
136  */
137 /* ARGSUSED */
138 int
139 backisearch(f, n)
140 	int f, n;
141 {
142 	return isearch(SRCH_BACK);
143 }
144 
145 /*
146  * Incremental Search.
147  *	dir is used as the initial direction to search.
148  *	^S	switch direction to forward
149  *	^R	switch direction to reverse
150  *	^Q	quote next character (allows searching for ^N etc.)
151  *	<ESC>	exit from Isearch
152  *	<DEL>	undoes last character typed. (tricky job to do this correctly).
153  *	other ^ exit search, don't set mark
154  *	else	accumulate into search string
155  */
156 static int
157 isearch(dir)
158 	int dir;
159 {
160 	LINE	*clp;
161 
162 	int	 c;
163 	int	 cbo;
164 	int	 success;
165 	int	 pptr;
166 
167 	char	 opat[NPAT];
168 
169 #ifndef NO_MACRO
170 	if (macrodef) {
171 		ewprintf("Can't isearch in macro");
172 		return FALSE;
173 	}
174 #endif /* !NO_MACRO */
175 	for (cip = 0; cip < NSRCH; cip++)
176 		cmds[cip].s_code = SRCH_NOPR;
177 
178 	(void)strcpy(opat, pat);
179 	cip = 0;
180 	pptr = -1;
181 	clp = curwp->w_dotp;
182 	cbo = curwp->w_doto;
183 	is_lpush();
184 	is_cpush(SRCH_BEGIN);
185 	success = TRUE;
186 	is_prompt(dir, TRUE, success);
187 
188 	for (;;) {
189 		update();
190 
191 		switch (c = getkey(FALSE)) {
192 		case CCHR('['):
193 			/*
194 			 * If new characters come in the next 300 msec,
195 			 * we can assume that they belong to a longer
196 			 * escaped sequence so we should ungetkey the
197 			 * ESC to avoid writing out garbage.
198 			 */
199 			if (ttwait(300) == FALSE)
200 				ungetkey(c);
201 			srch_lastdir = dir;
202 			curwp->w_markp = clp;
203 			curwp->w_marko = cbo;
204 			ewprintf("Mark set");
205 			return (TRUE);
206 		case CCHR('G'):
207 			if (success != TRUE) {
208 				while (is_peek() == SRCH_ACCM)
209 					is_undo(&pptr, &dir);
210 				success = TRUE;
211 				is_prompt(dir, pptr < 0, success);
212 				break;
213 			}
214 			curwp->w_dotp = clp;
215 			curwp->w_doto = cbo;
216 			curwp->w_flag |= WFMOVE;
217 			srch_lastdir = dir;
218 			(void)ctrlg(FFRAND, 0);
219 			(void)strcpy(pat, opat);
220 			return ABORT;
221 		case CCHR(']'):
222 		case CCHR('S'):
223 			if (dir == SRCH_BACK) {
224 				dir = SRCH_FORW;
225 				is_lpush();
226 				is_cpush(SRCH_FORW);
227 				success = TRUE;
228 			}
229 			if (success == FALSE && dir == SRCH_FORW)
230 				break;
231 			is_lpush();
232 			pptr = strlen(pat);
233 			(void)forwchar(FFRAND, 1);
234 			if (is_find(SRCH_FORW) != FALSE)
235 				is_cpush(SRCH_MARK);
236 			else {
237 				(void)backchar(FFRAND, 1);
238 				ttbeep();
239 				success = FALSE;
240 			}
241 			is_prompt(dir, pptr < 0, success);
242 			break;
243 		case CCHR('R'):
244 			if (dir == SRCH_FORW) {
245 				dir = SRCH_BACK;
246 				is_lpush();
247 				is_cpush(SRCH_BACK);
248 				success = TRUE;
249 			}
250 			if (success == FALSE && dir == SRCH_BACK)
251 				break;
252 			is_lpush();
253 			pptr = strlen(pat);
254 			(void)backchar(FFRAND, 1);
255 			if (is_find(SRCH_BACK) != FALSE)
256 				is_cpush(SRCH_MARK);
257 			else {
258 				(void)forwchar(FFRAND, 1);
259 				ttbeep();
260 				success = FALSE;
261 			}
262 			is_prompt(dir, pptr < 0, success);
263 			break;
264 		case CCHR('H'):
265 		case CCHR('?'):
266 			is_undo(&pptr, &dir);
267 			if (is_peek() != SRCH_ACCM)
268 				success = TRUE;
269 			is_prompt(dir, pptr < 0, success);
270 			break;
271 		case CCHR('\\'):
272 		case CCHR('Q'):
273 			c = (char)getkey(FALSE);
274 			goto addchar;
275 		case CCHR('M'):
276 			c = CCHR('J');
277 			goto addchar;
278 		default:
279 			if (ISCTRL(c)) {
280 				ungetkey(c);
281 				curwp->w_markp = clp;
282 				curwp->w_marko = cbo;
283 				ewprintf("Mark set");
284 				curwp->w_flag |= WFMOVE;
285 				return TRUE;
286 			}	/* and continue */
287 		case CCHR('I'):
288 		case CCHR('J'):
289 	addchar:
290 			if (pptr == -1)
291 				pptr = 0;
292 			if (pptr == 0)
293 				success = TRUE;
294 			pat[pptr++] = c;
295 			if (pptr == NPAT) {
296 				ewprintf("Pattern too long");
297 				return FALSE;
298 			}
299 			pat[pptr] = '\0';
300 			is_lpush();
301 			if (success != FALSE) {
302 				if (is_find(dir) != FALSE)
303 					is_cpush(c);
304 				else {
305 					success = FALSE;
306 					ttbeep();
307 					is_cpush(SRCH_ACCM);
308 				}
309 			} else
310 				is_cpush(SRCH_ACCM);
311 			is_prompt(dir, FALSE, success);
312 		}
313 	}
314 	/* NOTREACHED */
315 }
316 
317 static void
318 is_cpush(cmd)
319 	int cmd;
320 {
321 	if (++cip >= NSRCH)
322 		cip = 0;
323 	cmds[cip].s_code = cmd;
324 }
325 
326 static void
327 is_lpush()
328 {
329 	int	ctp;
330 
331 	ctp = cip + 1;
332 	if (ctp >= NSRCH)
333 		ctp = 0;
334 	cmds[ctp].s_code = SRCH_NOPR;
335 	cmds[ctp].s_doto = curwp->w_doto;
336 	cmds[ctp].s_dotp = curwp->w_dotp;
337 }
338 
339 static void
340 is_pop()
341 {
342 	if (cmds[cip].s_code != SRCH_NOPR) {
343 		curwp->w_doto = cmds[cip].s_doto;
344 		curwp->w_dotp = cmds[cip].s_dotp;
345 		curwp->w_flag |= WFMOVE;
346 		cmds[cip].s_code = SRCH_NOPR;
347 	}
348 	if (--cip <= 0)
349 		cip = NSRCH - 1;
350 }
351 
352 static int
353 is_peek()
354 {
355 	return cmds[cip].s_code;
356 }
357 
358 /* this used to always return TRUE (the return value was checked) */
359 static void
360 is_undo(pptr, dir)
361 	int *pptr, *dir;
362 {
363 	int	redo = FALSE;
364 
365 	switch (cmds[cip].s_code) {
366 	case SRCH_BEGIN:
367 	case SRCH_NOPR:
368 		*pptr = -1;
369 	case SRCH_MARK:
370 		break;
371 	case SRCH_FORW:
372 		*dir = SRCH_BACK;
373 		redo = TRUE;
374 		break;
375 	case SRCH_BACK:
376 		*dir = SRCH_FORW;
377 		redo = TRUE;
378 		break;
379 	case SRCH_ACCM:
380 	default:
381 		*pptr -= 1;
382 		if (*pptr < 0)
383 			*pptr = 0;
384 		pat[*pptr] = '\0';
385 		break;
386 	}
387 	is_pop();
388 	if (redo)
389 		is_undo(pptr, dir);
390 }
391 
392 static int
393 is_find(dir)
394 	int	dir;
395 {
396 	int	 plen, odoto;
397 	LINE	*odotp;
398 
399 	odoto = curwp->w_doto;
400 	odotp = curwp->w_dotp;
401 	plen = strlen(pat);
402 	if (plen != 0) {
403 		if (dir == SRCH_FORW) {
404 			(void)backchar(FFARG | FFRAND, plen);
405 			if (forwsrch() == FALSE) {
406 				curwp->w_doto = odoto;
407 				curwp->w_dotp = odotp;
408 				return FALSE;
409 			}
410 			return TRUE;
411 		}
412 		if (dir == SRCH_BACK) {
413 			(void)forwchar(FFARG | FFRAND, plen);
414 			if (backsrch() == FALSE) {
415 				curwp->w_doto = odoto;
416 				curwp->w_dotp = odotp;
417 				return FALSE;
418 			}
419 			return TRUE;
420 		}
421 		ewprintf("bad call to is_find");
422 		return FALSE;
423 	}
424 	return FALSE;
425 }
426 
427 /*
428  * If called with "dir" not one of SRCH_FORW or SRCH_BACK, this routine used
429  * to print an error message.  It also used to return TRUE or FALSE, depending
430  * on if it liked the "dir".  However, none of the callers looked at the
431  * status, so I just made the checking vanish.
432  */
433 static void
434 is_prompt(dir, flag, success)
435 	int dir, flag, success;
436 {
437 	if (dir == SRCH_FORW) {
438 		if (success != FALSE)
439 			is_dspl("I-search", flag);
440 		else
441 			is_dspl("Failing I-search", flag);
442 	} else if (dir == SRCH_BACK) {
443 		if (success != FALSE)
444 			is_dspl("I-search backward", flag);
445 		else
446 			is_dspl("Failing I-search backward", flag);
447 	} else
448 		ewprintf("Broken call to is_prompt");
449 }
450 
451 /*
452  * Prompt writing routine for the incremental search.  The "prompt" is just
453  * a string. The "flag" determines whether pat should be printed.
454  */
455 static void
456 is_dspl(prompt, flag)
457 	char *prompt;
458 	int flag;
459 {
460 	if (flag != FALSE)
461 		ewprintf("%s: ", prompt);
462 	else
463 		ewprintf("%s: %s", prompt, pat);
464 }
465 
466 /*
467  * Query Replace.
468  *	Replace strings selectively.  Does a search and replace operation.
469  */
470 /* ARGSUSED */
471 int
472 queryrepl(f, n)
473 	int f, n;
474 {
475 	int	s;
476 	int	rcnt = 0;		/* replacements made so far	*/
477 	int	plen;			/* length of found string	*/
478 	char	news[NPAT];		/* replacement string		*/
479 
480 #ifndef NO_MACRO
481 	if (macrodef) {
482 		ewprintf("Can't query replace in macro");
483 		return FALSE;
484 	}
485 #endif /* !NO_MACRO */
486 
487 	if ((s = readpattern("Query replace")) != TRUE)
488 		return (s);
489 	if ((s = ereply("Query replace %s with: ", news, NPAT, pat)) == ABORT)
490 		return (s);
491 	if (s == FALSE)
492 		news[0] = '\0';
493 	ewprintf("Query replacing %s with %s:", pat, news);
494 	plen = strlen(pat);
495 
496 	/*
497 	 * Search forward repeatedly, checking each time whether to insert
498 	 * or not.  The "!" case makes the check always true, so it gets put
499 	 * into a tighter loop for efficiency.
500 	 */
501 	while (forwsrch() == TRUE) {
502 retry:
503 		update();
504 		switch (getkey(FALSE)) {
505 		case ' ':
506 			if (lreplace((RSIZE)plen, news, f) == FALSE)
507 				return (FALSE);
508 			rcnt++;
509 			break;
510 		case '.':
511 			if (lreplace((RSIZE)plen, news, f) == FALSE)
512 				return (FALSE);
513 			rcnt++;
514 			goto stopsearch;
515 		/* ^G or ESC */
516 		case CCHR('G'):
517 			(void)ctrlg(FFRAND, 0);
518 		case CCHR('['):
519 			goto stopsearch;
520 		case '!':
521 			do {
522 				if (lreplace((RSIZE)plen, news, f) == FALSE)
523 					return (FALSE);
524 				rcnt++;
525 			} while (forwsrch() == TRUE);
526 			goto stopsearch;
527 		case CCHR('H'):
528 		/* To not replace */
529 		case CCHR('?'):
530 			break;
531 		default:
532 			ewprintf("<SP> replace, [.] rep-end, <DEL> don't, [!] repl rest <ESC> quit");
533 			goto retry;
534 		}
535 	}
536 stopsearch:
537 	curwp->w_flag |= WFHARD;
538 	update();
539 	if (rcnt == 0)
540 		ewprintf("(No replacements done)");
541 	else if (rcnt == 1)
542 		ewprintf("(1 replacement done)");
543 	else
544 		ewprintf("(%d replacements done)", rcnt);
545 	return TRUE;
546 }
547 
548 /*
549  * This routine does the real work of a forward search.  The pattern is sitting
550  * in the external variable "pat".  If found, dot is updated, the window system
551  * is notified of the change, and TRUE is returned.  If the string isn't found,
552  * FALSE is returned.
553  */
554 int
555 forwsrch()
556 {
557 	LINE	*clp, *tlp;
558 	int	 cbo, tbo, c;
559 	char	*pp;
560 
561 	clp = curwp->w_dotp;
562 	cbo = curwp->w_doto;
563 	for (;;) {
564 		if (cbo == llength(clp)) {
565 			if ((clp = lforw(clp)) == curbp->b_linep)
566 				break;
567 			cbo = 0;
568 			c = CCHR('J');
569 		} else
570 			c = lgetc(clp, cbo++);
571 		if (eq(c, pat[0]) != FALSE) {
572 			tlp = clp;
573 			tbo = cbo;
574 			pp = &pat[1];
575 			while (*pp != 0) {
576 				if (tbo == llength(tlp)) {
577 					tlp = lforw(tlp);
578 					if (tlp == curbp->b_linep)
579 						goto fail;
580 					tbo = 0;
581 					c = CCHR('J');
582 				} else
583 					c = lgetc(tlp, tbo++);
584 				if (eq(c, *pp++) == FALSE)
585 					goto fail;
586 			}
587 			curwp->w_dotp = tlp;
588 			curwp->w_doto = tbo;
589 			curwp->w_flag |= WFMOVE;
590 			return TRUE;
591 		}
592 fail:		;
593 	}
594 	return FALSE;
595 }
596 
597 /*
598  * This routine does the real work of a backward search.  The pattern is
599  * sitting in the external variable "pat".  If found, dot is updated, the
600  * window system is notified of the change, and TRUE is returned.  If the
601  * string isn't found, FALSE is returned.
602  */
603 int
604 backsrch()
605 {
606 	LINE	*clp, *tlp;
607 	int	 cbo, tbo, c;
608 	char	*epp, *pp;
609 
610 	for (epp = &pat[0]; epp[1] != 0; ++epp);
611 	clp = curwp->w_dotp;
612 	cbo = curwp->w_doto;
613 	for (;;) {
614 		if (cbo == 0) {
615 			clp = lback(clp);
616 			if (clp == curbp->b_linep)
617 				return FALSE;
618 			cbo = llength(clp) + 1;
619 		}
620 		if (--cbo == llength(clp))
621 			c = CCHR('J');
622 		else
623 			c = lgetc(clp, cbo);
624 		if (eq(c, *epp) != FALSE) {
625 			tlp = clp;
626 			tbo = cbo;
627 			pp = epp;
628 			while (pp != &pat[0]) {
629 				if (tbo == 0) {
630 					tlp = lback(tlp);
631 					if (tlp == curbp->b_linep)
632 						goto fail;
633 					tbo = llength(tlp) + 1;
634 				}
635 				if (--tbo == llength(tlp))
636 					c = CCHR('J');
637 				else
638 					c = lgetc(tlp, tbo);
639 				if (eq(c, *--pp) == FALSE)
640 					goto fail;
641 			}
642 			curwp->w_dotp = tlp;
643 			curwp->w_doto = tbo;
644 			curwp->w_flag |= WFMOVE;
645 			return TRUE;
646 		}
647 fail:		;
648 	}
649 	/* NOTREACHED */
650 }
651 
652 /*
653  * Compare two characters.  The "bc" comes from the buffer.  It has its case
654  * folded out. The "pc" is from the pattern.
655  */
656 static int
657 eq(bc, pc)
658 	int bc, pc;
659 {
660 	bc = CHARMASK(bc);
661 	pc = CHARMASK(pc);
662 	if (bc == pc)
663 		return TRUE;
664 	if (ISUPPER(bc))
665 		return TOLOWER(bc) == pc;
666 	if (ISUPPER(pc))
667 		return bc == TOLOWER(pc);
668 	return FALSE;
669 }
670 
671 /*
672  * Read a pattern.  Stash it in the external variable "pat".  The "pat" is not
673  * updated if the user types in an empty line.  If the user typed an empty
674  * line, and there is no old pattern, it is an error.  Display the old pattern,
675  * in the style of Jeff Lomicka.  There is some do-it-yourself control
676  * expansion.
677  */
678 int
679 readpattern(prompt)
680 	char *prompt;
681 {
682 	int	s;
683 	char	tpat[NPAT];
684 
685 	if (tpat[0] == '\0')
686 		s = ereply("%s: ", tpat, NPAT, prompt);
687 	else
688 		s = ereply("%s: (default %s) ", tpat, NPAT, prompt, pat);
689 
690 	/* specified */
691 	if (s == TRUE)
692 		(void) strcpy(pat, tpat);
693 	/* CR, but old one */
694 	else if (s == FALSE && pat[0] != 0)
695 		s = TRUE;
696 	return s;
697 }
698