|
|
1.1 root 1:
2: /*
3: * The functions in this file implement the search commands (both plain
4: * and incremental searches are supported) and the query-replace command.
5: *
6: * The plain old search code is part of the original MicroEMACS "distribution".
7: * The incremental search code, and the query-replace code, is by Rich Ellison,
8: * with some stylistic modifications.
9: */
10: #include <stdio.h>
11: #include "ed.h"
12:
13: #define CCHR(x) ((x)-'@')
14:
15: #define SRCH_BEGIN (0) /* Search sub-codes. */
16: #define SRCH_FORW (-1)
17: #define SRCH_BACK (-2)
18: #define SRCH_PREV (-3)
19: #define SRCH_NEXT (-4)
20: #define SRCH_NOPR (-5)
21: #define SRCH_ACCM (-6)
22:
23: typedef struct {
24: int s_code;
25: LINE *s_dotp;
26: int s_doto;
27: } SRCHCOM;
28:
29: extern int ctrlg();
30:
31: static SRCHCOM cmds[NSRCH];
32: static int cip;
33:
34: int srch_lastdir = SRCH_NOPR; /* Last search flags. */
35:
36: /*
37: * Search forward.
38: * Get a search string from the user, and search for it, starting at ".".
39: * If found, "." gets moved to just after the matched characters, and display
40: * does all the hard stuff. If not found, it just prints a message.
41: * This is normally bound to "M-S".
42: */
43: forwsearch(f, n)
44: {
45: register int s;
46:
47: if ((s=readpattern("Search")) != TRUE)
48: return (s);
49: if (forwsrch() == FALSE) {
50: mlwrite("Not found");
51: return (FALSE);
52: }
53: srch_lastdir = SRCH_FORW;
54: return (TRUE);
55: }
56:
57: /*
58: * Reverse search.
59: * Get a search string from the user, and search, starting at "."
60: * and proceeding toward the front of the buffer. If found "." is left
61: * pointing at the first character of the pattern [the last character that
62: * was matched]. Bound to "M-R".
63: */
64: backsearch(f, n)
65: {
66: register int s;
67:
68: if ((s=readpattern("Reverse search")) != TRUE)
69: return (s);
70: if (backsrch() == FALSE) {
71: mlwrite("Not found");
72: return (FALSE);
73: }
74: srch_lastdir = SRCH_BACK;
75: return (TRUE);
76: }
77:
78: /*
79: * Search again, using the same search string and direction as the last search
80: * command. The direction has been saved in "srch_lastdir", so you know which
81: * way to go.
82: * Bound to "M-/".
83: */
84: searchagain(f, n)
85: {
86: if (srch_lastdir == SRCH_FORW) {
87: if (forwsrch() == FALSE) {
88: mlwrite("Not found");
89: return (FALSE);
90: }
91: return (TRUE);
92: }
93: if (srch_lastdir == SRCH_BACK) {
94: if (backsrch() == FALSE) {
95: mlwrite("Not found");
96: return (FALSE);
97: }
98: return (TRUE);
99: }
100: mlwrite("No last search");
101: return (FALSE);
102: }
103:
104: /*
105: * Use incremental searching, initially in the forward direction.
106: * isearch ignores any explicit arguments.
107: */
108: forwisearch(f, n)
109: {
110: return (isearch(SRCH_FORW));
111: }
112:
113: /*
114: * Use incremental searching, initially in the reverse direction.
115: * isearch ignores any explicit arguments.
116: */
117: backisearch(f, n)
118: {
119: return (isearch(SRCH_BACK));
120: }
121:
122: /*
123: * Incremental Search.
124: * dir is used as the initial direction to search.
125: * ^N find next occurance (if first thing typed reuse old string).
126: * ^P find prev occurance (if first thing typed reuse old string).
127: * ^S switch direction to forward, find next
128: * ^R switch direction to reverse, find prev
129: * ^Q quote next character (allows searching for ^N etc.)
130: * <ESC> exit from Isearch.
131: * <DEL>
132: * ^H undoes last character typed. (tricky job to do this correctly).
133: * else accumulate into search string
134: */
135: isearch(dir)
136: {
137: int c;
138: int pptr;
139: LINE *clp;
140: int cbo;
141: int success;
142:
143: for (cip=0; cip<NSRCH; cip++)
144: cmds[cip].s_code = SRCH_NOPR;
145: cip = 0;
146: pptr = -1;
147: clp = curwp->w_dotp;
148: cbo = curwp->w_doto;
149: isrch_lpush();
150: isrch_cpush(SRCH_BEGIN);
151: success = TRUE;
152: isrch_prompt(dir,TRUE,success);
153: for (;;) {
154: update();
155: c = ttgetc();
156: bracketoff();
157: switch (c) {
158:
159: case CCHR('M'):
160: case METACH:
161: #if LK201
162: case AGRAVE:
163: #endif
164: srch_lastdir = dir;
165: mlwrite("[Done]");
166: return (TRUE);
167:
168: case CCHR('G'):
169: curwp->w_dotp = clp;
170: curwp->w_doto = cbo;
171: curwp->w_flag |= WFMOVE;
172: srch_lastdir = dir;
173: mlwrite("Aborting");
174: return (FALSE);
175:
176: case CCHR('S'):
177: case CCHR('F'):
178: if (dir == SRCH_BACK) {
179: dir = SRCH_FORW;
180: isrch_lpush();
181: isrch_cpush(SRCH_FORW);
182: success = TRUE;
183: }
184: /* drop through to find next */
185:
186: case CCHR('N'):
187: if ((success == FALSE)&&(dir == SRCH_FORW))
188: break;
189: isrch_lpush();
190: forwchar(FALSE, 1);
191: if (isrch_find(SRCH_NEXT) != FALSE) {
192: isrch_cpush(SRCH_NEXT);
193: pptr = strlen(pat);
194: } else {
195: backchar(FALSE, 1);
196: tbeep();
197: success = FALSE;
198: }
199: isrch_prompt(dir,FALSE,success);
200: break;
201:
202: case CCHR('R'):
203: case CCHR('B'):
204: if (dir == SRCH_FORW) {
205: dir = SRCH_BACK;
206: isrch_lpush();
207: isrch_cpush(SRCH_BACK);
208: success = TRUE;
209: }
210: /* drop through to find prev */
211:
212: case CCHR('P'):
213: if ((success == FALSE)&&(dir == SRCH_BACK))
214: break;
215: isrch_lpush();
216: backchar(FALSE, 1);
217: if (isrch_find(SRCH_PREV) != FALSE) {
218: isrch_cpush(SRCH_PREV);
219: pptr = strlen(pat);
220: } else {
221: forwchar(FALSE, 1);
222: tbeep();
223: success = FALSE;
224: }
225: isrch_prompt(dir,FALSE,success);
226: break;
227:
228: case CCHR('H'):
229: case 0x7f:
230: if (isrch_undo(&pptr,&dir) != TRUE) {
231: return (ABORT);
232: }
233: if (isrch_peek() != SRCH_ACCM)
234: success = TRUE;
235: isrch_prompt(dir,FALSE,success);
236: break;
237:
238: case CCHR('^'):
239: case CCHR('Q'):
240: c = ttgetc();
241: case CCHR('U'):
242: case CCHR('X'):
243: case CCHR('I'):
244: case CCHR('J'):
245: goto addchar;
246: default:
247: if (c < ' ') { /* uninterpreted ctrl */
248: c += '@';
249: c |= CTRL;
250: success = execute(c, FALSE, 1);
251: curwp->w_flag |= WFMOVE;
252: return (success);
253: }
254: addchar:
255: if (pptr == -1)
256: pptr=0;
257: if (pptr == 0)
258: success = TRUE;
259: pat[pptr++] = c;
260: if (pptr==NPAT) {
261: mlwrite("Pattern too long");
262: ctrlg(FALSE, 0);
263: return (ABORT);
264: }
265: pat[pptr] = '\0';
266: isrch_lpush();
267: if (success != FALSE) {
268: if (isrch_find(dir) != FALSE) {
269: isrch_cpush(c);
270: } else {
271: success = FALSE;
272: tbeep();
273: isrch_cpush(SRCH_ACCM);
274: }
275: } else {
276: isrch_cpush(SRCH_ACCM);
277: }
278: isrch_prompt(dir, FALSE, success);
279: }
280: }
281: }
282:
283: isrch_cpush(cmd)
284: register int cmd;
285: {
286: if (++cip >= NSRCH)
287: cip = 0;
288: cmds[cip].s_code = cmd;
289: }
290:
291: isrch_lpush()
292: {
293: register int ctp;
294:
295: ctp = cip+1;
296: if (ctp >= NSRCH)
297: ctp = 0;
298: cmds[ctp].s_code = SRCH_NOPR;
299: cmds[ctp].s_doto = curwp->w_doto;
300: cmds[ctp].s_dotp = curwp->w_dotp;
301: }
302:
303: isrch_pop()
304: {
305: if (cmds[cip].s_code != SRCH_NOPR) {
306: curwp->w_doto = cmds[cip].s_doto;
307: curwp->w_dotp = cmds[cip].s_dotp;
308: curwp->w_flag |= WFMOVE; /* Was "=", wrong. */
309: cmds[cip].s_code = SRCH_NOPR;
310: }
311: if (--cip <= 0) /* Is "=" right? */
312: cip = NSRCH-1;
313: }
314:
315: int
316: isrch_peek()
317: {
318: if (cip == 0)
319: return (cmds[NSRCH-1].s_code);
320: else
321: return (cmds[cip-1].s_code);
322: }
323:
324: int
325: isrch_undo(pptr,dir)
326: register int *pptr;
327: register int *dir;
328: {
329: switch (cmds[cip].s_code) {
330: case SRCH_NOPR:
331: case SRCH_BEGIN:
332: case SRCH_NEXT:
333: case SRCH_PREV:
334: break;
335:
336: case SRCH_FORW:
337: if (*dir == SRCH_BACK) {
338: mlwrite("ISEARCH error, to back when already back");
339: return (FALSE);
340: }
341: *dir = SRCH_BACK;
342: break;
343:
344: case SRCH_BACK:
345: if (*dir == SRCH_FORW) {
346: mlwrite("ISEARCH error, to forw when already forw");
347: return (FALSE);
348: }
349: *dir = SRCH_FORW;
350: break;
351:
352: case SRCH_ACCM:
353: default:
354: if (*pptr == 0) {
355: mlwrite("ISEARCH error, delete without character");
356: return (FALSE);
357: }
358: *pptr -= 1;
359: if (*pptr < 0)
360: *pptr = 0;
361: pat[*pptr] = '\0';
362: break;
363: }
364: isrch_pop();
365: return (TRUE);
366: }
367:
368: isrch_find(dir)
369: register int dir;
370: {
371: register int plen;
372:
373: plen = strlen(pat);
374: if (plen != 0) {
375: if (dir==SRCH_FORW || dir==SRCH_NEXT) {
376: backchar(FALSE, plen);
377: if (forwsrch() == FALSE) {
378: forwchar(FALSE, plen);
379: return (FALSE);
380: }
381: return (TRUE);
382: }
383: if (dir==SRCH_BACK || dir==SRCH_PREV) {
384: forwchar(FALSE, plen);
385: if (backsrch() == FALSE) {
386: backchar(FALSE, plen);
387: return (FALSE);
388: }
389: return (TRUE);
390: }
391: mlwrite("bad call to isrch_find");
392: ctrlg(FALSE, 0);
393: return (FALSE);
394: }
395: return (FALSE);
396: }
397:
398: /*
399: * If called with "dir" not one of SRCH_FORW
400: * or SRCH_BACK, this routine used to print an error
401: * message. It also used to return TRUE or FALSE,
402: * depending on if it liked the "dir". However, none
403: * of the callers looked at the status, so I just
404: * made the checking vanish.
405: */
406: isrch_prompt(dir, flag, success)
407: {
408: if (dir == SRCH_FORW) {
409: if (success != FALSE)
410: isrch_dspl("i-search forward", flag);
411: else {
412: isrch_dspl("failing i-search forward", flag);
413: curwp->w_flag |= WFEDIT;
414: }
415:
416: } else if (dir == SRCH_BACK) {
417: if (success != FALSE)
418: isrch_dspl("i-search backward", flag);
419: else {
420: isrch_dspl("failing i-search backward", flag);
421: curwp->w_flag |= WFEDIT;
422: }
423: }
424: }
425:
426: /*
427: * Prompt writing routine for the incremental
428: * search. The "prompt" is just a string. The "flag" determines
429: * if a "[ ]" or ":" embelishment is used. The string is packed
430: * into a big buffer and zapped out with a single call to
431: * "mlwrite". The "26" in the "tpat" declaration is the length
432: * of the longest prompt string. This actually isn't long
433: * enough if the pattern is full of control characters
434: * and other things that need an "^".
435: */
436: isrch_dspl(prompt, flag)
437: uchar *prompt;
438: {
439: register uchar *cp1;
440: register uchar *cp2;
441: register unsigned c;
442: uchar tpat[NPAT+26+2+1+1];
443:
444: cp1 = &tpat[0];
445: cp2 = prompt;
446: while ((c = *cp2++) != '\0')
447: *cp1++ = c;
448: if (flag != FALSE) {
449: *cp1++ = ' ';
450: *cp1++ = '[';
451: } else {
452: *cp1++ = ':';
453: *cp1++ = ' ';
454: }
455: cp2 = &pat[0];
456: while ((c = *cp2++) != 0) {
457: if (cp1 < &tpat[NPAT+20-4]) { /* "??]\0" */
458: if (dblchr(c)) {
459: *cp1++ = '^';
460: c ^= 0x40;
461: } else if (c == '%') /* Map "%" to */
462: *cp1++ = c; /* "%%". */
463: *cp1++ = c;
464: }
465: }
466: if (flag != FALSE)
467: *cp1++ = ']';
468: *cp1= '\0';
469: mlwrite(tpat);
470: }
471:
472: /*
473: * Query Replace.
474: * Replace strings selectively. Does a search and replace operation.
475: * A space or a comma replaces the string, a period replaces and quits,
476: * an n doesn't replace, a C-G quits.
477: */
478: queryrepl(f, n)
479: {
480: register int s;
481: uchar news[NPAT];
482: LINE *clp;
483: int cbo;
484: register int flg;
485: register int flgc;
486: int rcnt;
487:
488: if ((s=readpattern("Old string")) != TRUE)
489: return (s);
490: if ((s=mlreply("New string: ",news, NPAT)) == ABORT)
491: return (s);
492: if (s == FALSE) /* Null string. */
493: news[0] = '\0';
494: mlwrite("Query Replace: [%s] -> [%s]", pat, news);
495: clp = curwp->w_dotp;
496: cbo = curwp->w_doto;
497: rcnt = 0;
498: flg = TRUE;
499: flgc = FALSE;
500: while (flg==TRUE && forwsrch()==TRUE) {
501: if (flgc != TRUE) {
502: retry:
503: update();
504: switch (ttgetc()) {
505: case ' ':
506: case ',':
507: replstring(news, f);
508: rcnt++;
509: break;
510:
511: case '.':
512: replstring(news, f);
513: rcnt++;
514: flg = FALSE;
515: break;
516:
517: case '\007':
518: flg = ABORT;
519: ctrlg(FALSE, 0);
520: break;
521:
522: case '!':
523: replstring(news, f);
524: rcnt++;
525: flgc = TRUE;
526: break;
527:
528: case 'n':
529: break;
530:
531: default:
532: mlwrite("<SP>[,] replace, [.] rep-end, [n] dont, [!] repl rest <C-G> quit");
533: goto retry;
534: }
535: } else {
536: replstring(news, f);
537: rcnt++;
538: }
539: }
540: curwp->w_dotp = clp;
541: curwp->w_doto = cbo;
542: curwp->w_flag |= WFHARD;
543: update();
544: if (rcnt == 0)
545: mlwrite("[No replacements done]");
546: else if (rcnt == 1)
547: mlwrite("[1 replacement done]");
548: else
549: mlwrite("[%d replacements done]", rcnt);
550: return (TRUE);
551: }
552:
553: #define FLOWER 0 /* Found lower case. */
554: #define FCAPTL 1 /* Found capital. */
555: #define FUPPER 2 /* Found upper case. */
556:
557: replstring(st, f)
558: uchar *st;
559: int f;
560: {
561: register uchar *tpt;
562: register int plen;
563: register int rtype;
564: register int c, d;
565:
566: plen = strlen(pat);
567: backchar(TRUE, plen);
568: c = lgetc(curwp->w_dotp, curwp->w_doto);
569: rtype = FLOWER;
570: if (ishi(c)) {
571: rtype = FCAPTL;
572: if (curwp->w_doto+1 != llength(curwp->w_dotp)) {
573: c = lgetc(curwp->w_dotp, curwp->w_doto+1);
574: if (ishi(c))
575: rtype = FUPPER;
576: }
577: }
578: ldelete(plen, FALSE);
579: tpt = &st[0];
580: if (f != FALSE) {
581: while (*tpt != '\0')
582: linsert(1, *tpt++);
583: } else {
584: switch (rtype) {
585: case FLOWER:
586: while (*tpt != '\0') {
587: linsert(1, *tpt++);
588: }
589: break;
590:
591: case FCAPTL:
592: if (*tpt != '\0') {
593: if (d = islow(*tpt))
594: linsert(1, d);
595: else
596: linsert(1, *tpt);
597: tpt++;
598: }
599: while (*tpt != '\0')
600: linsert(1, *tpt++);
601: break;
602:
603: case FUPPER:
604: while (*tpt != '\0') {
605: if (d = islow(*tpt))
606: linsert(1, d);
607: else
608: linsert(1, *tpt);
609: tpt++;
610: }
611: break;
612: }
613: }
614: curwp->w_flag |= WFHARD;
615: }
616:
617: /*
618: * This routine does the real work of a
619: * forward search. The pattern is sitting in the external
620: * variable "pat". If found, dot is updated, the window system
621: * is notified of the change, and TRUE is returned. If the
622: * string isn't found, FALSE is returned.
623: */
624: forwsrch()
625: {
626: register LINE *clp;
627: register int cbo;
628: register LINE *tlp;
629: register int tbo;
630: register uchar *pp;
631: register int c;
632:
633: clp = curwp->w_dotp;
634: cbo = curwp->w_doto;
635: while (clp != curbp->b_linep) {
636: if (cbo == llength(clp)) {
637: clp = lforw(clp);
638: cbo = 0;
639: c = '\n';
640: } else
641: c = lgetc(clp, cbo++);
642: if (eq(c, pat[0]) != FALSE) {
643: tlp = clp;
644: tbo = cbo;
645: pp = &pat[1];
646: while (*pp != 0) {
647: if (tlp == curbp->b_linep)
648: goto fail;
649: if (tbo == llength(tlp)) {
650: tlp = lforw(tlp);
651: tbo = 0;
652: c = '\n';
653: } else
654: c = lgetc(tlp, tbo++);
655: if (eq(c, *pp++) == FALSE)
656: goto fail;
657: }
658: curwp->w_dotp = tlp;
659: curwp->w_doto = tbo;
660: curwp->w_flag |= WFMOVE;
661: return (TRUE);
662: }
663: fail: ;
664: }
665: return (FALSE);
666: }
667:
668: /*
669: * This routine does the real work of a
670: * backward search. The pattern is sitting in the external
671: * variable "pat". If found, dot is updated, the window system
672: * is notified of the change, and TRUE is returned. If the
673: * string isn't found, FALSE is returned.
674: */
675: backsrch()
676: {
677: register LINE *clp;
678: register int cbo;
679: register LINE *tlp;
680: register int tbo;
681: register int c;
682: register uchar *epp;
683: register uchar *pp;
684:
685: for (epp = &pat[0]; epp[1] != 0; ++epp)
686: ;
687: clp = curwp->w_dotp;
688: cbo = curwp->w_doto;
689: for (;;) {
690: if (cbo == 0) {
691: clp = lback(clp);
692: if (clp == curbp->b_linep)
693: return (FALSE);
694: cbo = llength(clp)+1;
695: }
696: if (--(cbo) == llength(clp))
697: c = '\n';
698: else
699: c = lgetc(clp,cbo);
700: if (eq(c, *epp) != FALSE) {
701: tlp = clp;
702: tbo = cbo;
703: pp = epp;
704: while (pp != &pat[0]) {
705: if (tbo == 0) {
706: tlp = lback(tlp);
707: if (tlp == curbp->b_linep)
708: goto fail;
709: tbo = llength(tlp)+1;
710: }
711: if (--(tbo) == llength(tlp))
712: c = '\n';
713: else
714: c = lgetc(tlp,tbo);
715: if (eq(c, *--pp) == FALSE)
716: goto fail;
717: }
718: curwp->w_dotp = tlp;
719: curwp->w_doto = tbo;
720: curwp->w_flag |= WFMOVE;
721: return (TRUE);
722: }
723: fail: ;
724: }
725: }
726:
727: /*
728: * Compare two characters.
729: * The "bc" comes from the buffer.
730: * It has it's case folded out. The
731: * "pc" is from the pattern.
732: */
733: eq(bc, pc)
734: register int bc;
735: register int pc;
736: {
737: int c;
738:
739: if (bind.ffold) {
740: if (c = islow(bc))
741: bc = c;
742: if (c = islow(pc))
743: pc = c;
744: }
745: if (bc == pc)
746: return (TRUE);
747: return (FALSE);
748: }
749:
750: /*
751: * Read a pattern.
752: * Stash it in the external variable "pat". The "pat" is
753: * not updated if the user types in an empty line. If the user typed
754: * an empty line, and there is no old pattern, it is an error.
755: * Display the old pattern, in the style of Jeff Lomicka. There is
756: * some do-it-yourself control expansion.
757: */
758: readpattern(prompt)
759: uchar *prompt;
760: {
761: register uchar *cp1;
762: register uchar *cp2;
763: register unsigned c;
764: register int s;
765: uchar tpat[NPAT+20];
766:
767: cp1 = &tpat[0]; /* Copy prompt */
768: cp2 = prompt;
769: while ((c = *cp2++) != '\0')
770: *cp1++ = c;
771: if (pat[0] != '\0') { /* Old pattern */
772: *cp1++ = ' ';
773: *cp1++ = '[';
774: cp2 = &pat[0];
775: while ((c = *cp2++) != 0) {
776: if (cp1 < &tpat[NPAT+20-6]) { /* "??]: \0" */
777: if (dblchr(c)) {
778: *cp1++ = '^';
779: c ^= 0x40;
780: } else if (c == '%') /* Map "%" to */
781: *cp1++ = c; /* "%%". */
782: *cp1++ = c;
783: }
784: }
785: *cp1++ = ']';
786: }
787: *cp1++ = ':'; /* Finish prompt */
788: *cp1++ = ' ';
789: *cp1++ = '\0';
790: s = mlreply(tpat, tpat, NPAT); /* Read pattern */
791: if (s == TRUE) /* Specified */
792: strcpy(pat, tpat);
793: else if (s==FALSE && pat[0]!=0) /* CR, but old one */
794: s = TRUE;
795: return (s);
796: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.