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