1: /*************************************************************************** 2: * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne. JOVE * 3: * is 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: #include "jove.h" 9: #include "io.h" 10: #include "re.h" 11: #include "ctype.h" 12: 13: #ifdef MAC 14: # include "mac.h" 15: #else 16: # include <sys/stat.h> 17: #endif 18: 19: #ifdef MAC 20: # undef private 21: # define private 22: #endif 23: 24: #ifdef LINT_ARGS 25: private Bufpos * doisearch(int, int, int); 26: 27: private void 28: IncSearch(int), 29: replace(int, int); 30: private int 31: isearch(int, Bufpos *), 32: lookup(char *, char *, char *, char *), 33: substitute(int, Line *, int, Line *, int); 34: #else 35: private Bufpos * doisearch(); 36: 37: private void 38: IncSearch(), 39: replace(); 40: private int 41: isearch(), 42: lookup(), 43: substitute(); 44: #endif /* LINT_ARGS */ 45: 46: #ifdef MAC 47: # undef private 48: # define private static 49: #endif 50: 51: private int 52: substitute(query, l1, char1, l2, char2) 53: Line *l1, 54: *l2; 55: { 56: Line *lp; 57: int numdone = 0, 58: offset = curchar, 59: stop = NO; 60: disk_line UNDO_da = 0; 61: Line *UNDO_lp = 0; 62: 63: lsave(); 64: REdirection = FORWARD; 65: 66: lp = l1; 67: for (lp = l1; (lp != l2->l_next) && !stop; lp = lp->l_next) { 68: offset = (lp == l1) ? char1 : 0; 69: while (!stop && re_lindex(lp, offset, compbuf, alternates, 0)) { 70: if (lp == l2 && REeom > char2) /* nope, leave this alone */ 71: break; 72: DotTo(lp, REeom); 73: offset = curchar; 74: if (query) { 75: message("Replace (Type '?' for help)? "); 76: reswitch: redisplay(); 77: switch (CharUpcase(getchar())) { 78: case '.': 79: stop = YES; 80: /* Fall into ... */ 81: 82: case ' ': 83: case 'Y': 84: break; 85: 86: case BS: 87: case RUBOUT: 88: case 'N': 89: if (linebuf[offset++] == '\0') 90: goto nxtline; 91: continue; 92: 93: case CTL('W'): 94: re_dosub(linebuf, YES); 95: numdone += 1; 96: offset = curchar = REbom; 97: makedirty(curline); 98: /* Fall into ... */ 99: 100: case CTL('R'): 101: case 'R': 102: RErecur(); 103: offset = curchar; 104: lp = curline; 105: continue; 106: 107: case CTL('U'): 108: case 'U': 109: if (UNDO_lp == 0) 110: continue; 111: lp = UNDO_lp; 112: lp->l_dline = UNDO_da | DIRTY; 113: offset = 0; 114: numdone -= 1; 115: continue; 116: 117: case 'P': 118: case '!': 119: query = 0; 120: break; 121: 122: case CR: 123: case LF: 124: case 'Q': 125: goto done; 126: 127: case CTL('L'): 128: RedrawDisplay(); 129: goto reswitch; 130: 131: default: 132: rbell(); 133: message("Space or Y, Period, Rubout or N, C-R or R, C-W, C-U or U, P or !, Return."); 134: goto reswitch; 135: } 136: } 137: re_dosub(linebuf, NO); 138: numdone += 1; 139: modify(); 140: offset = curchar = REeom; 141: makedirty(curline); 142: if (query) { 143: message(mesgbuf); /* no blinking */ 144: redisplay(); /* show the change */ 145: } 146: UNDO_da = curline->l_dline; 147: UNDO_lp = curline; 148: if (linebuf[offset] == 0) 149: nxtline: break; 150: } 151: } 152: done: return numdone; 153: } 154: 155: /* prompt for search and replacement strings and do the substitution */ 156: private void 157: replace(query, inreg) 158: { 159: Mark *m; 160: char *rep_ptr; 161: Line *l1 = curline, 162: *l2 = curbuf->b_last; 163: int char1 = curchar, 164: char2 = length(curbuf->b_last), 165: numdone; 166: 167: if (inreg) { 168: m = CurMark(); 169: l2 = m->m_line; 170: char2 = m->m_char; 171: (void) fixorder(&l1, &char1, &l2, &char2); 172: } 173: 174: /* get search string */ 175: strcpy(rep_search, ask(rep_search[0] ? rep_search : (char *) 0, ProcFmt)); 176: REcompile(rep_search, UseRE, compbuf, alternates); 177: /* Now the replacement string. Do_ask() so the user can play with 178: the default (previous) replacement string by typing C-R in ask(), 179: OR, he can just hit Return to replace with nothing. */ 180: rep_ptr = do_ask("\r\n", (int (*)()) 0, rep_str, ": %f %s with ", rep_search); 181: if (rep_ptr == 0) 182: rep_ptr = NullStr; 183: strcpy(rep_str, rep_ptr); 184: 185: if (((numdone = substitute(query, l1, char1, l2, char2)) != 0) && 186: (inreg == NO)) { 187: do_set_mark(l1, char1); 188: add_mess(" "); /* just making things pretty */ 189: } else 190: message(""); 191: add_mess("(%d substitution%n)", numdone, numdone); 192: } 193: 194: void 195: RegReplace() 196: { 197: replace(0, YES); 198: } 199: 200: void 201: QRepSearch() 202: { 203: replace(1, NO); 204: } 205: 206: void 207: RepSearch() 208: { 209: replace(0, NO); 210: } 211: 212: /* Lookup a tag in tag file FILE. FILE is assumed to be sorted 213: alphabetically. The FASTTAGS code, which is implemented with 214: a binary search, depends on this assumption. If it's not true 215: it is possible to comment out the fast tag code (which is clearly 216: labeled) and everything else will just work. */ 217: 218: private int 219: lookup(searchbuf, filebuf, tag, file) 220: char *searchbuf, 221: *filebuf, 222: *tag, 223: *file; 224: { 225: register int taglen = strlen(tag); 226: char line[BUFSIZ], 227: pattern[128]; 228: register File *fp; 229: struct stat stbuf; 230: int fast = YES, 231: success = NO; 232: register off_t lower, upper; 233: 234: sprintf(pattern, "^%s[^\t]*\t*\\([^\t]*\\)\t*[?/]\\([^?/]*\\)[?/]", tag); 235: fp = open_file(file, iobuff, F_READ, !COMPLAIN, QUIET); 236: if (fp == NIL) 237: return 0; 238: 239: /* ********BEGIN FAST TAG CODE******** */ 240: 241: if (stat(file, &stbuf) < 0) 242: fast = NO; 243: else { 244: lower = 0; 245: upper = stbuf.st_size; 246: if (upper - lower < BUFSIZ) 247: fast = NO; 248: } 249: if (fast == YES) for (;;) { 250: off_t mid; 251: int whichway, 252: chars_eq; 253: 254: if (upper - lower < BUFSIZ) { 255: f_seek(fp, lower); 256: break; /* stop this nonsense */ 257: } 258: mid = (lower + upper) / 2; 259: f_seek(fp, mid); 260: f_toNL(fp); 261: if (f_gets(fp, line, sizeof line) == EOF) 262: break; 263: chars_eq = numcomp(line, tag); 264: if (chars_eq == taglen && iswhite(line[chars_eq])) 265: goto found; 266: whichway = line[chars_eq] - tag[chars_eq]; 267: if (whichway < 0) { /* line is BEFORE tag */ 268: lower = mid; 269: continue; 270: } else if (whichway > 0) { /* line is AFTER tag */ 271: upper = mid; 272: continue; 273: } 274: } 275: f_toNL(fp); 276: /* END FAST TAG CODE */ 277: 278: while (f_gets(fp, line, sizeof line) != EOF) { 279: int cmp; 280: 281: if (line[0] > *tag) 282: break; 283: else if ((cmp = strncmp(line, tag, taglen)) > 0) 284: break; 285: else if (cmp < 0) 286: continue; 287: /* if we get here, we've found the match */ 288: found: if (!LookingAt(pattern, line, 0)) { 289: complain("I thought I saw it!"); 290: break; 291: } else { 292: putmatch(1, filebuf, FILESIZE); 293: putmatch(2, searchbuf, 100); 294: success = YES; 295: break; 296: } 297: } 298: close_file(fp); 299: 300: if (success == NO) 301: s_mess("Can't find tag \"%s\".", tag); 302: return success; 303: } 304: 305: #ifndef MSDOS 306: char TagFile[FILESIZE] = "./tags"; 307: #else /* MSDOS */ 308: char TagFile[FILESIZE] = "tags"; 309: #endif /* MSDOS */ 310: 311: void 312: find_tag(tag, localp) 313: char *tag; 314: { 315: char filebuf[FILESIZE], 316: sstr[100], 317: tfbuf[FILESIZE]; 318: register Bufpos *bp; 319: register Buffer *b; 320: char *tagfname; 321: 322: if (!localp) 323: tagfname = ask_file("With tag file: ", TagFile, tfbuf); 324: else 325: tagfname = TagFile; 326: if (lookup(sstr, filebuf, tag, tagfname) == 0) 327: return; 328: set_mark(); 329: b = do_find(curwind, filebuf, 0); 330: if (curbuf != b) 331: SetABuf(curbuf); 332: SetBuf(b); 333: if ((bp = dosearch(sstr, BACKWARD, 0)) == 0 && 334: ((bp = dosearch(sstr, FORWARD, 0)) == 0)) 335: message("Well, I found the file, but the tag is missing."); 336: else 337: SetDot(bp); 338: } 339: 340: void 341: FindTag() 342: { 343: int localp = !is_an_arg(); 344: char tag[128]; 345: 346: strcpy(tag, ask((char *) 0, ProcFmt)); 347: find_tag(tag, localp); 348: } 349: 350: /* Find Tag at Dot. */ 351: 352: void 353: FDotTag() 354: { 355: int c1 = curchar, 356: c2 = c1; 357: char tagname[50]; 358: 359: if (!ismword(linebuf[curchar])) 360: complain("Not a tag!"); 361: while (c1 > 0 && ismword(linebuf[c1 - 1])) 362: c1 -= 1; 363: while (ismword(linebuf[c2])) 364: c2 += 1; 365: 366: null_ncpy(tagname, linebuf + c1, c2 - c1); 367: find_tag(tagname, !is_an_arg()); 368: } 369: 370: /* I-search returns a code saying what to do: 371: STOP: We found the match, so unwind the stack and leave 372: where it is. 373: DELETE: Rubout the last command. 374: BACKUP: Back up to where the isearch was last NOT failing. 375: 376: When a character is typed it is appended to the search string, and 377: then, isearch is called recursively. When C-S or C-R is typed, isearch 378: is again called recursively. */ 379: 380: #define STOP 1 381: #define DELETE 2 382: #define BACKUP 3 383: #define TOSTART 4 384: 385: static char ISbuf[128], 386: *incp = 0; 387: int SExitChar = CR; 388: 389: #define cmp_char(a, b) ((a) == (b) || (CaseIgnore && (CharUpcase(a) == CharUpcase(b)))) 390: 391: static Bufpos * 392: doisearch(dir, c, failing) 393: register int c, 394: dir, 395: failing; 396: { 397: static Bufpos buf; 398: Bufpos *bp; 399: extern int okay_wrap; 400: 401: if (c == CTL('S') || c == CTL('R')) 402: goto dosrch; 403: 404: if (failing) 405: return 0; 406: DOTsave(&buf); 407: if (dir == FORWARD) { 408: if (cmp_char(linebuf[curchar], c)) { 409: buf.p_char = curchar + 1; 410: return &buf; 411: } 412: } else { 413: if (look_at(ISbuf)) 414: return &buf; 415: } 416: dosrch: okay_wrap = YES; 417: if ((bp = dosearch(ISbuf, dir, 0)) == 0) 418: rbell(); /* ring the first time there's no match */ 419: okay_wrap = NO; 420: return bp; 421: } 422: 423: void 424: IncFSearch() 425: { 426: IncSearch(FORWARD); 427: } 428: 429: void 430: IncRSearch() 431: { 432: IncSearch(BACKWARD); 433: } 434: 435: private void 436: IncSearch(dir) 437: { 438: Bufpos save_env; 439: 440: DOTsave(&save_env); 441: ISbuf[0] = 0; 442: incp = ISbuf; 443: if (isearch(dir, &save_env) == TOSTART) 444: SetDot(&save_env); 445: else { 446: if (LineDist(curline, save_env.p_line) >= MarkThresh) 447: do_set_mark(save_env.p_line, save_env.p_char); 448: } 449: setsearch(ISbuf); 450: } 451: 452: /* Nicely recursive. */ 453: 454: private int 455: isearch(dir, bp) 456: Bufpos *bp; 457: { 458: Bufpos pushbp; 459: int c, 460: ndir, 461: failing; 462: char *orig_incp; 463: 464: if (bp != 0) { /* Move to the new position. */ 465: pushbp.p_line = bp->p_line; 466: pushbp.p_char = bp->p_char; 467: SetDot(bp); 468: failing = 0; 469: } else { 470: DOTsave(&pushbp); 471: failing = 1; 472: } 473: orig_incp = incp; 474: ndir = dir; /* Same direction as when we got here, unless 475: we change it with C-S or C-R. */ 476: for (;;) { 477: SetDot(&pushbp); 478: message(NullStr); 479: if (failing) 480: add_mess("Failing "); 481: if (dir == BACKWARD) 482: add_mess("reverse-"); 483: add_mess("I-search: %s", ISbuf); 484: DrawMesg(NO); 485: add_mess(NullStr); /* tell me this is disgusting ... */ 486: c = getch(); 487: if (c == SExitChar) 488: return STOP; 489: if (c == AbortChar) { 490: /* If we're failing, we backup until we're no longer 491: failing or we've reached the beginning; else, we 492: just about the search and go back to the start. */ 493: if (failing) 494: return BACKUP; 495: return TOSTART; 496: } 497: switch (c) { 498: case RUBOUT: 499: case BS: 500: return DELETE; 501: 502: case CTL('\\'): 503: c = CTL('S'); 504: 505: case CTL('S'): 506: case CTL('R'): 507: /* If this is the first time through and we have a 508: search string left over from last time, use that 509: one now. */ 510: if (incp == ISbuf) { 511: strcpy(ISbuf, getsearch()); 512: incp = &ISbuf[strlen(ISbuf)]; 513: } 514: ndir = (c == CTL('S')) ? FORWARD : BACKWARD; 515: /* If we're failing and we're not changing our 516: direction, don't recur since there's no way 517: the search can work. */ 518: if (failing && ndir == dir) { 519: rbell(); 520: continue; 521: } 522: break; 523: 524: case '\\': 525: if (incp > &ISbuf[(sizeof ISbuf) - 1]) { 526: rbell(); 527: continue; 528: } 529: *incp++ = '\\'; 530: add_mess("\\"); 531: /* Fall into ... */ 532: 533: case CTL('Q'): 534: case CTL('^'): 535: add_mess(""); 536: c = getch() | 0400; 537: /* Fall into ... */ 538: 539: default: 540: if (c & 0400) 541: c &= CHARMASK; 542: else { 543: #ifdef IBMPC 544: if (c == RUBOUT || c == 0xff || (c < ' ' && c != '\t')) { 545: #else 546: if (c > RUBOUT || (c < ' ' && c != '\t')) { 547: #endif 548: Ungetc(c); 549: return STOP; 550: } 551: } 552: if (incp > &ISbuf[(sizeof ISbuf) - 1]) { 553: rbell(); 554: continue; 555: } 556: *incp++ = c; 557: *incp = 0; 558: break; 559: } 560: add_mess("%s", orig_incp); 561: add_mess(" ..."); /* so we know what's going on */ 562: DrawMesg(NO); /* do it now */ 563: switch (isearch(ndir, doisearch(ndir, c, failing))) { 564: case TOSTART: 565: return TOSTART; 566: 567: case STOP: 568: return STOP; 569: 570: case BACKUP: 571: /* If we're not failing, we just continue to to the 572: for loop; otherwise we keep returning to the 573: previous levels until we find one that isn't 574: failing OR we reach the beginning. */ 575: if (failing) 576: return BACKUP; 577: /* Fall into ... */ 578: 579: case DELETE: 580: incp = orig_incp; 581: *incp = 0; 582: continue; 583: } 584: } 585: }