1 /************************************************************************
2  * This program is Copyright (C) 1986-1996 by Jonathan Payne.  JOVE is  *
3  * provided to you without charge, and with no warranty.  You may give  *
4  * away copies of JOVE, including sources, provided that this notice is *
5  * included in all the files.                                           *
6  ************************************************************************/
7 
8 /* regular expression applications: search, replace, and tags */
9 
10 #include "jove.h"
11 #include "fp.h"
12 #include "re.h"
13 #include "jctype.h"
14 #include "chars.h"
15 #include "disp.h"
16 #include "ask.h"
17 #include "fmt.h"
18 #include "marks.h"
19 #include "reapp.h"
20 #include "wind.h"
21 
22 #ifdef MAC
23 # include "mac.h"
24 #else
25 # include <sys/stat.h>
26 #endif
27 
28 private void
29 	IncSearch proto((int));
30 
31 private int
32 	isearch proto((int, Bufpos *));
33 
34 private char
35 	searchstr[128];		/* global search string */
36 
37 bool
38 	UseRE = NO;		/* VAR: use regular expressions in search */
39 
40 private void
setsearch(str)41 setsearch(str)
42 char	*str;
43 {
44 	strcpy(searchstr, str);
45 }
46 
47 private char *
getsearch()48 getsearch()
49 {
50 	return searchstr;
51 }
52 
53 private void
search(dir,re,setdefault)54 search(dir, re, setdefault)
55 int	dir;
56 bool	re,
57 	setdefault;
58 {
59 	Bufpos	*newdot;
60 	char	*s;
61 
62 	s = ask(searchstr, ProcFmt);
63 	if (setdefault)
64 		setsearch(s);
65 	okay_wrap = YES;
66 	newdot = dosearch(s, dir, re);
67 	okay_wrap = NO;
68 	if (newdot == NULL) {
69 		if (WrapScan)
70 			complain("No \"%s\" in buffer.", s);
71 		else
72 			complain("No \"%s\" found to %s.", s,
73 				 (dir == FORWARD) ? "bottom" : "top");
74 	}
75 	PushPntp(newdot->p_line);
76 	SetDot(newdot);
77 }
78 
79 void
ForSearch()80 ForSearch()
81 {
82 	search(FORWARD, UseRE, YES);
83 }
84 
85 void
RevSearch()86 RevSearch()
87 {
88 	search(BACKWARD, UseRE, YES);
89 }
90 
91 void
FSrchND()92 FSrchND()
93 {
94 	search(FORWARD, UseRE, NO);
95 }
96 
97 void
RSrchND()98 RSrchND()
99 {
100 	search(BACKWARD, UseRE, NO);
101 }
102 
103 private int
substitute(re_blk,query,l1,char1,l2,char2)104 substitute(re_blk, query, l1, char1, l2, char2)
105 struct RE_block	*re_blk;
106 LinePtr	l1,
107 	l2;
108 bool	query;
109 int	char1,
110 	char2;
111 {
112 	LinePtr	lp;
113 	int	numdone = 0,
114 		UNDO_nd = 0,
115 		offset = char1;
116 	bool	stop = NO;
117 	daddr	UNDO_da = NULL_DADDR;
118 	LinePtr	UNDO_lp = NULL;
119 
120 	lsave();
121 
122 	for (lp = l1; lp != l2->l_next; lp = lp->l_next) {
123 		int	crater = -1;	/* end of last substitution on this line */
124 		bool	LineDone = NO;	/* already replaced last empty string on line? */
125 
126 		while (!LineDone
127 		&& re_lindex(lp, offset, FORWARD, re_blk, NO, crater)
128 		&& (lp != l2 || REeom <= char2))
129 		{
130 			DotTo(lp, REeom);
131 			offset = curchar;
132 			if (query) {
133 				ZXchar	c;
134 
135 				message("Replace (Type '?' for help)? ");
136 reswitch:
137 				redisplay();
138 				c = kbd_getch();
139 				if (c == AbortChar)
140 					return numdone;
141 
142 				switch (CharUpcase(c)) {
143 				case '.':
144 					stop = YES;
145 					/*FALLTHROUGH*/
146 				case ' ':
147 				case 'Y':
148 					break;
149 
150 				case BS:
151 				case DEL:
152 				case 'N':
153 					if (REbom == REeom) {
154 						offset += 1;
155 						if (linebuf[REeom] == '\0')
156 							LineDone = YES;
157 					}
158 					continue;
159 
160 				case CTL('W'):
161 					re_dosub(re_blk, linebuf, YES);
162 					if (lp == l2)
163 						char2 += REdelta;
164 					modify();
165 					numdone += 1;
166 					curchar = REbom;
167 					makedirty(curline);
168 					/*FALLTHROUGH*/
169 				case CTL('R'):
170 				case 'R':
171 					RErecur();
172 					UNDO_lp = NULL;	/* can't reliably undo this */
173 					offset = curchar;
174 					lp = curline;
175 					continue;
176 
177 				case CTL('U'):
178 				case 'U':
179 					if (UNDO_lp == NULL) {
180 						rbell();
181 						goto reswitch;
182 					}
183 					lp = UNDO_lp;
184 					lp->l_dline = UNDO_da;
185 					if (UNDO_lp == curline)
186 						getDOT();	/* downdate line cache */
187 					makedirty(lp);
188 					offset = 0;
189 					numdone = UNDO_nd;
190 					UNDO_lp = NULL;
191 					continue;
192 
193 				case 'P':
194 				case '!':
195 					query = NO;
196 					break;
197 
198 				case CR:
199 				case LF:
200 				case 'Q':
201 					return numdone;
202 
203 				case CTL('L'):
204 					RedrawDisplay();
205 					goto reswitch;
206 
207 				default:
208 					rbell();
209 message("Space or Y, Period, Delete or N, ^R or R, ^W, ^U or U, P or !, Return.");
210 					goto reswitch;
211 				}
212 			}
213 			if (UNDO_lp != curline) {
214 				UNDO_da = curline->l_dline;
215 				UNDO_lp = curline;
216 				UNDO_nd = numdone;
217 			}
218 			if (REbom == REeom && linebuf[REeom] == '\0')
219 				LineDone = YES;
220 			re_dosub(re_blk, linebuf, NO);
221 			if (lp == l2)
222 				char2 += REdelta;
223 			numdone += 1;
224 			modify();
225 			crater = offset = curchar = REeom;
226 			makedirty(curline);
227 			if (query) {
228 				message(mesgbuf);	/* no blinking */
229 				redisplay();		/* show the change */
230 			}
231 			if (stop)
232 				return numdone;
233 		}
234 		offset = 0;
235 	}
236 	return numdone;
237 }
238 
239 /* prompt for search and replacement strings and do the substitution */
240 private void
replace(query,inreg)241 replace(query, inreg)
242 bool	query,
243 	inreg;
244 {
245 	Mark	*m;
246 	char	*rep_ptr;
247 	LinePtr	l1 = curline,
248 		l2 = curbuf->b_last;
249 	int	char1 = curchar,
250 		char2 = length(curbuf->b_last),
251 		numdone;
252 	struct RE_block	re_blk;
253 
254 	if (inreg) {
255 		m = CurMark();
256 		l2 = m->m_line;
257 		char2 = m->m_char;
258 		(void) fixorder(&l1, &char1, &l2, &char2);
259 	}
260 
261 	/* get search string */
262 	strcpy(rep_search, ask(rep_search[0] ? rep_search : (char *)NULL, ProcFmt));
263 	REcompile(rep_search, UseRE, &re_blk);
264 	/* Now the replacement string.  Do_ask() so the user can play with
265 	   the default (previous) replacement string by typing ^R in ask(),
266 	   OR, he can just hit Return to replace with nothing. */
267 	rep_ptr = do_ask("\r\n", NULL_ASK_EXT, rep_str, ": %f %s with ",
268 		rep_search);
269 	if (rep_ptr == NULL)
270 		rep_ptr = NullStr;
271 	strcpy(rep_str, rep_ptr);
272 
273 	if ((numdone = substitute(&re_blk, query, l1, char1, l2, char2)) != 0
274 	&& !inreg)
275 	{
276 		do_set_mark(l1, char1);
277 		add_mess(" ");		/* just making things pretty */
278 	} else {
279 		message(NullStr);
280 	}
281 	add_mess("(%d substitution%n)", numdone, numdone);
282 }
283 
284 void
RegReplace()285 RegReplace()
286 {
287 	replace(NO, YES);
288 }
289 
290 void
QRepSearch()291 QRepSearch()
292 {
293 	replace(YES, NO);
294 }
295 
296 void
RepSearch()297 RepSearch()
298 {
299 	replace(NO, NO);
300 }
301 
302 /* Lookup a tag in tag file FILE.  FILE is assumed to be sorted
303    alphabetically.  The FASTTAGS code, which is implemented with
304    a binary search, depends on this assumption.  If it's not true
305    it is possible to comment out the fast tag code (which is clearly
306    labeled), delete the marked test in the sequential loop, and
307    everything else will just work. */
308 
309 private bool
lookup_tag(searchbuf,sbsize,filebuf,tag,file)310 lookup_tag(searchbuf, sbsize, filebuf, tag, file)
311 char	*searchbuf;
312 size_t	sbsize;
313 char	*filebuf,
314 	*tag,
315 	*file;
316 {
317 	register size_t	taglen = strlen(tag);
318 	char	line[JBUFSIZ],
319 		pattern[128];
320 	register File	*fp;
321 	struct stat	stbuf;
322 	bool	success = NO;
323 
324 	fp = open_file(file, iobuff, F_READ, NO);
325 	if (fp == NULL) {
326 		message(IOerr("open", file));
327 		return NO;
328 	}
329 	swritef(pattern, sizeof(pattern), "^%s[^\t]*\t*\\([^\t]*\\)\t*\\([?/]\\)\\(.*\\)\\2$", tag);
330 
331 	/* ********BEGIN FAST TAG CODE******** */
332 
333 	/* Mac doesn't have fstat */
334 #ifdef MAC
335 	if (stat(file, &stbuf) >= 0)
336 #else
337 	if (fstat(fp->f_fd, &stbuf) >= 0)
338 #endif
339 	{
340 		/* Invariant: if there is a line matching the tag, it
341 		 * begins somewhere after position lower, and begins
342 		 * at or before upper.  There is one possible
343 		 * exception: if lower is 0, the line with the tag
344 		 * might be the very first line.
345 		 *
346 		 * When this loop is done, we seek to lower, advance
347 		 * past the next newline (unless lower is 0), and fall
348 		 * into the sequential search.
349 		 */
350 		register off_t	lower = 0;
351 		register off_t	upper = stbuf.st_size;
352 
353 		for (;;) {
354 			off_t	mid;
355 			int	chars_eq;
356 
357 			if (upper - lower < JBUFSIZ)
358 				break;	/* small range: search sequentially */
359 			mid = (lower + upper) / 2;
360 			f_seek(fp, mid);	/* mid will not be 0 */
361 			f_toNL(fp);
362 			if (f_gets(fp, line, sizeof line))
363 				break;		/* unexpected: bail out */
364 			chars_eq = numcomp(line, tag);
365 			if (chars_eq == taglen && jiswhite(line[chars_eq])) {
366 				/* we hit the exact line: get out */
367 				lower = mid;
368 				break;
369 			}
370 			if (line[chars_eq] < tag[chars_eq])
371 				lower = mid;	/* line is BEFORE tag */
372 			else
373 				upper = mid;	/* line is AFTER tag */
374 		}
375 		/* sequentially search from lower */
376 		f_seek(fp, lower);
377 		if (lower > 0)
378 			f_toNL(fp);
379 	}
380 
381 	/* END FAST TAG CODE */
382 
383 	while (!f_gets(fp, line, sizeof line)) {
384 		int	cmp = line[0] - *tag;
385 
386 		if (cmp == 0) {
387 			cmp = strncmp(line, tag, taglen);
388 			if (cmp == 0) {
389 				/* we've found the match */
390 				if (!LookingAt(pattern, line, 0)) {
391 					complain("I thought I saw it!");
392 				} else {
393 					putmatch(1, filebuf, (size_t)FILESIZE);
394 					putmatch(3, searchbuf, sbsize-1);
395 					success = YES;
396 				}
397 				break;
398 			}
399 		}
400 		if (cmp > 0)
401 			break;	/* failure: gone too far.  PRESUMES ALPHABETIC ORDER */
402 	}
403 	close_file(fp);
404 
405 	if (!success)
406 		s_mess("Can't find tag \"%s\".", tag);
407 	return success;
408 }
409 
410 char	TagFile[FILESIZE] = "tags";	/* VAR: default tag file */
411 
412 void
find_tag(tag,localp)413 find_tag(tag, localp)
414 char	*tag;
415 bool	localp;
416 {
417 	char	filebuf[FILESIZE],
418 		sstr[100],
419 		tfbuf[FILESIZE];
420 	register Bufpos	*bp;
421 	register Buffer	*b;
422 
423 	if (lookup_tag(sstr, sizeof(sstr), filebuf, tag,
424 	  localp? TagFile : ask_file("With tag file ", TagFile, tfbuf)))
425 	{
426 		set_mark();
427 		b = do_find(curwind, filebuf, YES, NO);
428 		if (curbuf != b)
429 			SetABuf(curbuf);
430 		SetBuf(b);
431 		if ((bp = dosearch(sstr, BACKWARD, NO)) == NULL
432 		&& (bp = dosearch(sstr, FORWARD, NO)) == NULL)
433 			message("Well, I found the file, but the tag is missing.");
434 		else
435 			SetDot(bp);
436 	}
437 }
438 
439 void
FindTag()440 FindTag()
441 {
442 	bool	localp = !is_an_arg();
443 	char	tag[128];
444 
445 	strcpy(tag, ask((char *)NULL, ProcFmt));
446 	find_tag(tag, localp);
447 }
448 
449 /* Find Tag at Dot. */
450 
451 void
FDotTag()452 FDotTag()
453 {
454 	int	c1 = curchar,
455 		c2 = c1;
456 	char	tagname[50];
457 
458 	if (!jisident(linebuf[curchar]))
459 		complain("Not a tag!");
460 	while (c1 > 0 && jisident(linebuf[c1 - 1]))
461 		c1 -= 1;
462 	while (jisident(linebuf[c2]))
463 		c2 += 1;
464 	if ((c2 - c1) >= (int)sizeof(tagname))
465 		complain("tag too long");
466 	null_ncpy(tagname, linebuf + c1, (size_t) (c2 - c1));
467 	find_tag(tagname, !is_an_arg());
468 }
469 
470 /* I-search returns a code saying what to do:
471    I_STOP:	We found the match, so unwind the stack and leave
472 		where it is.
473    I_DELETE:	Rubout the last command.
474    I_BACKUP:	Back up to where the isearch was last NOT failing.
475    I_TOSTART:	Abort the search, going back where isearch started
476 
477    When a character is typed it is appended to the search string, and
478    then, isearch is called recursively.  When ^S or ^R is typed, isearch
479    is again called recursively. */
480 
481 #define I_STOP	1
482 #define I_DELETE	2
483 #define I_BACKUP	3
484 #define I_TOSTART	4
485 
486 private char
487 	ISbuf[128],
488 	*incp = NULL;
489 
490 ZXchar	SExitChar = CR;	/* VAR: type this to stop i-search */
491 
492 private Bufpos *
doisearch(dir,c,failing)493 doisearch(dir, c, failing)
494 register ZXchar	c;
495 register int	dir;
496 bool		failing;
497 {
498 	static Bufpos	buf;
499 	Bufpos	*bp;
500 
501 	if (c != CTL('S') && c != CTL('R')) {
502 		if (failing)
503 			return NULL;
504 		DOTsave(&buf);
505 		if (dir == FORWARD) {
506 			if (ZXC(linebuf[curchar]) == c
507 			|| (CaseIgnore && cind_eq(linebuf[curchar], c))) {
508 				buf.p_char = curchar + 1;
509 				return &buf;
510 			}
511 		} else {
512 			if (look_at(ISbuf))
513 				return &buf;
514 		}
515 	}
516 	okay_wrap = YES;
517 	if ((bp = dosearch(ISbuf, dir, NO)) == NULL)
518 		rbell();	/* ring the first time there's no match */
519 	okay_wrap = NO;
520 	return bp;
521 }
522 
523 void
IncFSearch()524 IncFSearch()
525 {
526 	IncSearch(FORWARD);
527 }
528 
529 void
IncRSearch()530 IncRSearch()
531 {
532 	IncSearch(BACKWARD);
533 }
534 
535 private void
IncSearch(dir)536 IncSearch(dir)
537 int	dir;
538 {
539 	Bufpos	save_env;
540 
541 	DOTsave(&save_env);
542 	ISbuf[0] = '\0';
543 	incp = ISbuf;
544 	if (isearch(dir, &save_env) == I_TOSTART)
545 		SetDot(&save_env);
546 	else {
547 		if (LineDist(curline, save_env.p_line) >= MarkThresh)
548 			do_set_mark(save_env.p_line, save_env.p_char);
549 	}
550 	setsearch(ISbuf);
551 }
552 
553 /* Nicely recursive. */
554 
555 private int
isearch(dir,bp)556 isearch(dir, bp)
557 int	dir;
558 Bufpos	*bp;
559 {
560 	Bufpos	pushbp;
561 	ZXchar	c;
562 	int	ndir;
563 	bool	failing;
564 	char	*orig_incp;
565 
566 	if (bp != NULL) {		/* Move to the new position. */
567 		pushbp.p_line = bp->p_line;
568 		pushbp.p_char = bp->p_char;
569 		SetDot(bp);
570 		failing = NO;
571 	} else {
572 		DOTsave(&pushbp);
573 		failing = YES;
574 	}
575 	orig_incp = incp;
576 	ndir = dir;		/* Same direction as when we got here, unless
577 				   we change it with ^S or ^R. */
578 	for (;;) {
579 		SetDot(&pushbp);
580 		message(NullStr);
581 		if (failing)
582 			add_mess("Failing ");
583 		if (dir == BACKWARD)
584 			add_mess("reverse-");
585 		add_mess("I-search: %s", ISbuf);
586 		DrawMesg(NO);
587 		add_mess(NullStr);	/* tell me this is disgusting ... */
588 		c = getch();
589 		if (c == SExitChar)
590 			return I_STOP;
591 		if (c == AbortChar) {
592 			/* If we're failing, we backup until we're no longer
593 			   failing or we've reached the beginning; else, we
594 			   just abort the search and go back to the start. */
595 			if (failing)
596 				return I_BACKUP;
597 			return I_TOSTART;
598 		}
599 		switch (c) {
600 		case DEL:
601 		case BS:
602 			return I_DELETE;
603 
604 		case CTL('\\'):
605 			c = CTL('S');
606 			/*FALLTHROUGH*/
607 		case CTL('S'):
608 		case CTL('R'):
609 			/* If this is the first time through and we have a
610 			   search string left over from last time, and Inputp
611 			   is not in use [kludge!], use that one now. */
612 			if (Inputp == NULL && incp == ISbuf) {
613 				Inputp = getsearch();
614 				continue;
615 			}
616 			ndir = (c == CTL('S')) ? FORWARD : BACKWARD;
617 			/* If we're failing and we're not changing our
618 			   direction, don't recur since there's no way
619 			   the search can work. */
620 			if (failing && ndir == dir) {
621 				rbell();
622 				continue;
623 			}
624 			break;
625 
626 		case '\\':
627 			if (incp > &ISbuf[(sizeof ISbuf) - 1]) {
628 				rbell();
629 				continue;
630 			}
631 			*incp++ = '\\';
632 			add_mess("\\");
633 			/*FALLTHROUGH*/
634 		case CTL('Q'):
635 		case CTL('^'):
636 			add_mess(NullStr);
637 			c = getch();
638 			goto literal;
639 
640 		default:
641 			/* check for "funny" characters */
642 			if (!jisprint(c) || IsPrefixChar(c)) {
643 				Ungetc(c);
644 				return I_STOP;
645 			}
646 			/*FALLTHROUGH*/
647 		case '\t':
648 		literal:
649 			if (incp > &ISbuf[(sizeof ISbuf) - 1]) {
650 				rbell();
651 				continue;
652 			}
653 			*incp++ = c;
654 			*incp = '\0';
655 			break;
656 		}
657 		add_mess("%s", orig_incp);
658 		add_mess(" ...");	/* so we know what's going on */
659 		DrawMesg(NO);		/* do it now */
660 		switch (isearch(ndir, doisearch(ndir, c, failing))) {
661 		case I_TOSTART:
662 			return I_TOSTART;
663 
664 		case I_STOP:
665 			return I_STOP;
666 
667 		case I_BACKUP:
668 			/* If we're not failing, we just continue to to the
669 			   for loop; otherwise we keep returning to the
670 			   previous levels until we find one that isn't
671 			   failing OR we reach the beginning. */
672 			if (failing)
673 				return I_BACKUP;
674 			/*FALLTHROUGH*/
675 		case I_DELETE:
676 			incp = orig_incp;
677 			*incp = '\0';
678 			continue;
679 		}
680 	}
681 }
682