/* * The functions in this file * implement commands that search in the * forward and backward directions. There are * no special characters in the search strings. * Probably should have a regular expression * search, or something like that. */ #include #include #include "ed.h" #define CCHR(x) ((x)-'@') #define SRCH_BEGIN (0) /* Search sub-codes. */ #define SRCH_FORW (-1) #define SRCH_BACK (-2) #define SRCH_PREV (-3) #define SRCH_NEXT (-4) #define SRCH_NOPR (-5) #define SRCH_ACCM (-6) #define NSRCH 128 /* Undoable search commands. */ typedef struct { int s_code; LINE *s_dotp; int s_doto; } SRCHCOM; static SRCHCOM cmds[NSRCH]; static int cip; int srch_lastdir = SRCH_NOPR; /* Last search flags. */ /* * Search forward. * Get a search string from the user, and search for it, * starting at ".". If found, "." gets moved to just after the * matched characters, and display does all the hard stuff. * If not found, it just prints a message. */ forwsearch(f, n) register int f, n; { if ((f=readpattern("Search", &pat)) != TRUE) return (f); if (forwsrch() == FALSE) { mlwrite("Not found"); return (FALSE); } return (TRUE); } /* * Reverse search. * Get a search string from the user, and search, starting at "." * and proceeding toward the front of the buffer. If found "." is left * pointing at the first character of the pattern [the last character that * was matched]. */ backsearch(f, n) register int f, n; { if ((f=readpattern("Reverse search", &pat)) != TRUE) return (f); if (backsrch() == FALSE) { mlwrite("Not found"); return (FALSE); } return (TRUE); } /* * This routine does the real work of a * forward search. The pattern is sitting in the external * variable "pat". If found, dot is updated, the window system * is notified of the change, and TRUE is returned. If the * string isn't found, FALSE is returned. */ forwsrch() { register LINE *clp; register int cbo; register LINE *tlp; register int tbo; register char *pp; register int c; clp = curwp->w_dotp; cbo = curwp->w_doto; while (clp != curbp->b_linep) { if (cbo == llength(clp)) { clp = lforw(clp); cbo = 0; c = '\n'; } else c = lgetc(clp, cbo++); if (eq(c, pat[0]) != FALSE) { tlp = clp; tbo = cbo; pp = &pat[1]; while (*pp != 0) { if (tlp == curbp->b_linep) goto fail; if (tbo == llength(tlp)) { tlp = lforw(tlp); if (tlp == curbp->b_linep) goto fail; tbo = 0; c = '\n'; } else c = lgetc(tlp, tbo++); if (eq(c, *pp++) == FALSE) goto fail; } curwp->w_dotp = tlp; curwp->w_doto = tbo; curwp->w_flag |= WFMOVE; return (TRUE); } fail: ; } return (FALSE); } /* * This routine does the real work of a * backward search. The pattern is sitting in the external * variable "pat". If found, dot is updated, the window system * is notified of the change, and TRUE is returned. If the * string isn't found, FALSE is returned. */ backsrch() { register LINE *clp; register int cbo; register LINE *tlp; register int tbo; register int c; register char *epp; register char *pp; for (epp = &pat[0]; epp[1] != 0; ++epp) ; clp = curwp->w_dotp; cbo = curwp->w_doto; for (;;) { if (cbo == 0) { clp = lback(clp); if (clp == curbp->b_linep) return (FALSE); cbo = llength(clp)+1; } if (--cbo == llength(clp)) c = '\n'; else c = lgetc(clp,cbo); if (eq(c, *epp) != FALSE) { tlp = clp; tbo = cbo; pp = epp; while (pp != &pat[0]) { if (tbo == 0) { tlp = lback(tlp); if (tlp == curbp->b_linep) goto fail; tbo = llength(tlp)+1; } if (--tbo == llength(tlp)) c = '\n'; else c = lgetc(tlp,tbo); if (eq(c, *--pp) == FALSE) goto fail; } curwp->w_dotp = tlp; curwp->w_doto = tbo; curwp->w_flag |= WFMOVE; return (TRUE); } fail: ; } } /* * Compare two characters. * The "bc" comes from the buffer. * It has it's case folded out. The * "pc" is from the pattern. */ eq(bc, pc) register int bc; register int pc; { if (bc>='a' && bc<='z') bc -= 0x20; if (pc>='a' && pc<='z') pc -= 0x20; if (bc == pc) return (TRUE); return (FALSE); } /* * Read a pattern. * Stash it in an external * variable. The calling function * passes the address of inpat. The "pat" is * not updated if the user types in * an empty line. If the user typed * an empty line, and there is no * old pattern, it is an error. * Display the old pattern, in the * style of Jeff Lomicka. There is * some do-it-yourself control * expansion. */ readpattern(prompt, inpat) char *prompt; char *inpat; { register char *cp1; register char *cp2; register int c; register int s; char tpat[NPAT+20]; cp1 = &tpat[0]; /* Copy prompt */ cp2 = prompt; while ((c = *cp2++) != '\0') *cp1++ = c; if (inpat[0] != '\0') { /* Old pattern */ *cp1++ = ' '; *cp1++ = '['; cp2 = &inpat[0]; while ((c = *cp2++) != 0) { if (cp1 < &tpat[NPAT+20-6]) { /* "??]: \0" */ if (c<0x20 || c==0x7F) { *cp1++ = '^'; c ^= 0x40; } else if (c == '%') /* Map "%" to */ *cp1++ = c; /* "%%". */ *cp1++ = c; } } *cp1++ = ']'; } *cp1++ = ':'; /* Finish prompt */ *cp1++ = ' '; *cp1++ = '\0'; s = mlreply(tpat, tpat, NPAT); /* Read pattern */ if (s == TRUE) /* Specified */ strcpy(inpat, tpat); else if (s==FALSE && inpat[0]!=0) /* CR, but old one */ s = TRUE; return (s); } /* * Use incremental searching, initially in the forward direction. * isearch ignores any explicit arguments. */ forwisearch(f, n) register int f, n; { return (isearch(SRCH_FORW)); } /* * Use incremental searching, initially in the reverse direction. * isearch ignores any explicit arguments. */ backisearch(f, n) register int f, n; { return (isearch(SRCH_BACK)); } /* * Incremental Search. * dir is used as the initial direction to search. * ^N find next occurance (if first thing typed reuse old string). * ^P find prev occurance (if first thing typed reuse old string). * ^S switch direction to forward, find next * ^R switch direction to reverse, find prev * ^Q quote next character (allows searching for ^N etc.) * exit from Isearch. * undoes last character typed. (tricky job to do this correctly). * else accumulate into search string */ isearch(dir) int dir; { register int c; register LINE *clp; register int cbo; register int success; int pptr; for (cip=0; cipw_dotp; cbo = curwp->w_doto; is_lpush(); is_cpush(SRCH_BEGIN); success = TRUE; is_prompt(dir, TRUE, success); for (;;) { update(); switch (c = ttgetc()) { case CCHR('M'): case METACH: srch_lastdir = dir; mlwrite("[Done]"); return (TRUE); case CCHR('G'): curwp->w_dotp = clp; curwp->w_doto = cbo; curwp->w_flag |= WFMOVE; srch_lastdir = dir; ctrlg(FALSE, 0); return (FALSE); case CCHR('S'): case CCHR('F'): if (dir == SRCH_BACK) { dir = SRCH_FORW; is_lpush(); is_cpush(SRCH_FORW); success = TRUE; } /* Drop through to find next. */ case CCHR('N'): if (success==FALSE && dir==SRCH_FORW) break; is_lpush(); forwchar(FALSE, 1); if (is_find(SRCH_NEXT) != FALSE) { is_cpush(SRCH_NEXT); pptr = strlen(pat); } else { backchar(FALSE, 1); (*term.t_beep)(); success = FALSE; } is_prompt(dir, FALSE, success); break; case CCHR('R'): case CCHR('B'): if (dir == SRCH_FORW) { dir = SRCH_BACK; is_lpush(); is_cpush(SRCH_BACK); success = TRUE; } /* Drop through to find previous. */ case CCHR('P'): if (success==FALSE && dir==SRCH_BACK) break; is_lpush(); backchar(FALSE, 1); if (is_find(SRCH_PREV) != FALSE) { is_cpush(SRCH_PREV); pptr = strlen(pat); } else { forwchar(FALSE, 1); (*term.t_beep)(); success = FALSE; } is_prompt(dir,FALSE,success); break; case 0x7F: if (is_undo(&pptr, &dir) != TRUE) return (ABORT); if (is_peek() != SRCH_ACCM) success = TRUE; is_prompt(dir, FALSE, success); break; case CCHR('^'): case CCHR('Q'): c = ttgetc(); case CCHR('U'): case CCHR('X'): case CCHR('J'): goto addchar; default: if (iscntrl(c) != FALSE) { c += '@'; c |= CTRL; success = execute(c, FALSE, 1); curwp->w_flag |= WFMOVE; return (success); } addchar: if (pptr == -1) pptr = 0; if (pptr == 0) success = TRUE; pat[pptr++] = c; if (pptr == NPAT) { mlwrite("Pattern too long"); ctrlg(FALSE, 0); return (ABORT); } pat[pptr] = '\0'; is_lpush(); if (success != FALSE) { if (is_find(dir) != FALSE) is_cpush(c); else { success = FALSE; (*term.t_beep)(); is_cpush(SRCH_ACCM); } } else is_cpush(SRCH_ACCM); is_prompt(dir, FALSE, success); } } } is_cpush(cmd) register int cmd; { if (++cip >= NSRCH) cip = 0; cmds[cip].s_code = cmd; } is_lpush() { register int ctp; ctp = cip+1; if (ctp >= NSRCH) ctp = 0; cmds[ctp].s_code = SRCH_NOPR; cmds[ctp].s_doto = curwp->w_doto; cmds[ctp].s_dotp = curwp->w_dotp; } is_pop() { if (cmds[cip].s_code != SRCH_NOPR) { curwp->w_doto = cmds[cip].s_doto; curwp->w_dotp = cmds[cip].s_dotp; curwp->w_flag |= WFMOVE; cmds[cip].s_code = SRCH_NOPR; } if (--cip <= 0) cip = NSRCH-1; } is_peek() { if (cip == 0) return (cmds[NSRCH-1].s_code); else return (cmds[cip-1].s_code); } is_undo(pptr, dir) register int *pptr; register int *dir; { switch (cmds[cip].s_code) { case SRCH_NOPR: case SRCH_BEGIN: case SRCH_NEXT: case SRCH_PREV: break; case SRCH_FORW: *dir = SRCH_BACK; break; case SRCH_BACK: *dir = SRCH_FORW; break; case SRCH_ACCM: default: *pptr -= 1; if (*pptr < 0) *pptr = 0; pat[*pptr] = '\0'; break; } is_pop(); return (TRUE); } is_find(dir) register int dir; { register int plen; plen = strlen(pat); if (plen != 0) { if (dir==SRCH_FORW || dir==SRCH_NEXT) { backchar(FALSE, plen); if (forwsrch() == FALSE) { forwchar(FALSE, plen); return (FALSE); } return (TRUE); } if (dir==SRCH_BACK || dir==SRCH_PREV) { forwchar(FALSE, plen); if (backsrch() == FALSE) { backchar(FALSE, plen); return (FALSE); } return (TRUE); } mlwrite("bad call to is_find"); ctrlg(FALSE, 0); return (FALSE); } return (FALSE); } /* * If called with "dir" not one of SRCH_FORW * or SRCH_BACK, this routine used to print an error * message. It also used to return TRUE or FALSE, * depending on if it liked the "dir". However, none * of the callers looked at the status, so I just * made the checking vanish. */ is_prompt(dir, flag, success) { if (dir == SRCH_FORW) { if (success != FALSE) is_dspl("i-search forward", flag); else is_dspl("failing i-search forward", flag); } else if (dir == SRCH_BACK) { if (success != FALSE) is_dspl("i-search backward", flag); else is_dspl("failing i-search backward", flag); } } /* * Prompt writing routine for the incremental search. * The "prompt" is just a string. The "flag" determines * if a "[ ]" or ":" embelishment is used. */ is_dspl(prompt, flag) char *prompt; { if (flag != FALSE) mlwrite("%s [%s]", prompt, pat); else mlwrite("%s: %s", prompt, pat); } /* META Command Search and replace in forward direction only. Prompts * before replacement allows user to ABORT or continue. Calling with an * argument prevents case distinctions. Bound to M-R. */ replace(f, n) register int f, n; { register int s; register int kludge; /* Watch for saved line move */ register LINE *clp; /* saved line pointer */ char toprompt[81]; /* temporary string */ char prompt[81]; /* final prompt */ char *perptr, *index(); /* remap '%' to '%%' in prompt */ int cbo; /* offset into the saved line */ int rcnt = 0; /* Replacements made so far */ int plen; /* length of found string */ strcpy(prompt,"Query Replace "); if ((s=readpattern(prompt, &pat)) != TRUE) return (s); strcat(prompt,pat); strcat(prompt," with"); if ((perptr=index(prompt,'%'))!=NULL) { *perptr = '\0'; /* terminate prompt */ strcpy(toprompt,"%%"); /* setup mapping chars */ strcat(toprompt,++perptr); /* append end of prompt */ strcat(prompt,toprompt); /* append end to prompt */ } if ((s=readpattern(prompt, &rpat)) == ABORT) return (s); if (s == FALSE) rpat[0] = '\0'; plen = strlen(pat); /* * Search forward repeatedly, checking each time whether to insert * or not. The "!" case makes the check always true, so it gets put * into a tighter loop for efficiency. * * If we change the line that is the remembered value of dot, then * it is possible for the remembered value to move. This causes great * pain when trying to return to the non-existant line. * * possible fixes: * 1) put a single, relocated marker in the WINDOW structure, handled * like mark. The problem now becomes a what if two are needed... * 2) link markers into a list that gets updated (auto structures for * the nodes) * 3) Expand the mark into a stack of marks and add pushmark, popmark. */ clp = curwp->w_dotp; /* save the return location */ cbo = curwp->w_doto; while (forwsrch() == TRUE) { retry: update(); switch (ttgetc()) { case ' ': case ',': kludge = (curwp->w_dotp == clp); if (lreplace(plen, rpat, f) == FALSE) return (FALSE); rcnt++; if (kludge != FALSE) clp = curwp->w_dotp; break; case '.': kludge = (curwp->w_dotp == clp); if (lreplace(plen, rpat, f) == FALSE) return (FALSE); rcnt++; if (kludge != FALSE) clp = curwp->w_dotp; goto stopsearch; case 0x07: ctrlg(FALSE, 0); goto stopsearch; case '!': do { kludge = (curwp->w_dotp == clp); if (lreplace(plen, rpat, f) == FALSE) return (FALSE); rcnt++; if (kludge != FALSE) clp = curwp->w_dotp; } while (forwsrch() == TRUE); goto stopsearch; case 'n': break; default: mlwrite("[,] replace, [.] rep-end, [n] don't, [!] repl rest [C-G] quit"); goto retry; } } stopsearch: curwp->w_dotp = clp; curwp->w_doto = cbo; curwp->w_flag |= WFHARD; update(); if (rcnt == 0) mlwrite("[No replacements done]"); else if (rcnt == 1) mlwrite("[1 replacement done]"); else mlwrite("[%d replacements done]", rcnt); return (TRUE); } /* * Replace plen characters before dot with argument string. * Control-J characters in st are interpreted as newlines. * There is a casehack disable flag (normally it likes to match * case of replacement to what was there). */ lreplace(plen, st, f) register int plen; /* length to remove */ char *st; /* replacement string */ int f; /* case hack disable */ { register int rlen; /* replacement length */ register int rtype; /* capitalization */ register int c; /* used for random characters */ register int doto; /* offset into line */ /* * Find the capitalization of the word that was found. * f says use exact case of replacement string (same thing that * happens with lowercase found), so bypass check. */ backchar(TRUE, plen); rtype = __l; c = lgetc(curwp->w_dotp, curwp->w_doto); if (isupper(c)!=FALSE && f==FALSE) { rtype = __u|__l; if (curwp->w_doto+1 < llength(curwp->w_dotp)) { c = lgetc(curwp->w_dotp, curwp->w_doto+1); if (isupper(c) != FALSE) { rtype = __u; } } } /* * make the string lengths match (either pad the line * so that it will fit, or scrunch out the excess). * be careful with dot's offset. */ rlen = strlen(st); doto = curwp->w_doto; if (plen > rlen) ldelete(plen-rlen, FALSE); else if (plen < rlen) { if (linsert(rlen-plen, ' ') == FALSE) return (FALSE); } curwp->w_doto = doto; /* * do the replacement: If was capital, then place first * char as if upper, and subsequent chars as if lower. * If inserting upper, check replacement for case. */ while ((c = *st++&0xff) != '\0') { if ((rtype&__u)!=0 && islower(c)!=0) c = toupper(c); if (rtype == (__u|__l)) rtype = __l; if (c == '\n') { if (curwp->w_doto == llength(curwp->w_dotp)) forwchar(FALSE, 1); else { ldelete(1, FALSE); lnewline(); } } else if (curwp->w_dotp == curbp->b_linep) { linsert(1, c); } else if (curwp->w_doto == llength(curwp->w_dotp)) { ldelete(1, FALSE); linsert(1, c); } else lputc(curwp->w_dotp, curwp->w_doto++, c); } lchange(WFHARD); return (TRUE); }