/* * This code contains changes by * Gunnar Ritter, Freiburg i. Br., Germany, 2002. All rights reserved. * * Conditions 1, 2, and 4 and the no-warranty notice below apply * to these changes. * * * Copyright (c) 1980, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * Copyright(C) Caldera International Inc. 2001-2002. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * Redistributions of source code and documentation must retain the * above copyright notice, this list of conditions and the following * disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed or owned by Caldera * International, Inc. * Neither the name of Caldera International, Inc. nor the names of * other contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE * LIABLE FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef lint #ifdef DOSCCS static char sccsid[] = "@(#)ex_cmdsub.c 1.29 (gritter) 2/17/05"; #endif #endif /* from ex_cmdsub.c 7.7 (Berkeley) 6/7/85 */ #include "ex.h" #include "ex_argv.h" #include "ex_temp.h" #include "ex_tty.h" #include "ex_vis.h" /* * Command mode subroutines implementing * append, args, copy, delete, join, move, put, * shift, tag, yank, z and undo */ bool endline = 1; line *tad1; static int jnoop(void); /* * Append after line a lines returned by function f. * Be careful about intermediate states to avoid scramble * if an interrupt comes in. */ int append(int (*f)(void), line *a) { register line *a1, *a2, *rdot; int nline; nline = 0; dot = a; if(FIXUNDO && !inopen && f!=getsub) { undap1 = undap2 = dot + 1; undkind = UNDCHANGE; } while ((*f)() == 0) { if (truedol >= endcore) { if (morelines() < 0) { if (FIXUNDO && f == getsub) { undap1 = addr1; undap2 = addr2 + 1; } error(catgets(catd, 1, 39, "Out of memory@- too many lines in file")); } } nline++; a1 = truedol + 1; a2 = a1 + 1; dot++; undap2++; dol++; unddol++; truedol++; for (rdot = dot; a1 > rdot;) *--a2 = *--a1; *rdot = 0; putmark(rdot); if (f == gettty) { dirtcnt++; TSYNC(); } } return (nline); } void appendnone(void) { if(FIXUNDO) { undkind = UNDCHANGE; undap1 = undap2 = addr1; } } /* * Print out the argument list, with []'s around the current name. */ void pargs(void) { register char **av = argv0, *as = args0; register int ac; for (ac = 0; ac < argc0; ac++) { if (ac != 0) putchar(' ' | QUOTE); if (ac + argc == argc0 - 1) printf("["); lprintf("%s", as); if (ac + argc == argc0 - 1) printf("]"); as = av ? *++av : strend(as) + 1; } noonl(); } /* * Delete lines; two cases are if we are really deleting, * more commonly we are just moving lines to the undo save area. */ void delete(int hush) { register line *a1, *a2; nonzero(); if(FIXUNDO) { register shand dsavint; #ifdef TRACE if (trace) vudump("before delete"); #endif change(); dsavint = signal(SIGINT, SIG_IGN); undkind = UNDCHANGE; a1 = addr1; squish(); a2 = addr2; if (a2++ != dol) { reverse(a1, a2); reverse(a2, dol + 1); reverse(a1, dol + 1); } dol -= a2 - a1; unddel = a1 - 1; if (a1 > dol) a1 = dol; dot = a1; pkill[0] = pkill[1] = 0; signal(SIGINT, dsavint); #ifdef TRACE if (trace) vudump("after delete"); #endif } else { register line *a3; register int i; change(); a1 = addr1; a2 = addr2 + 1; a3 = truedol; i = a2 - a1; unddol -= i; undap2 -= i; dol -= i; truedol -= i; do *a1++ = *a2++; while (a2 <= a3); a1 = addr1; if (a1 > dol) a1 = dol; dot = a1; } if (!hush) killed(); } void deletenone(void) { if(FIXUNDO) { undkind = UNDCHANGE; squish(); unddel = addr1; } } /* * Crush out the undo save area, moving the open/visual * save area down in its place. */ void squish(void) { register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1; if(FIXUNDO) { if (inopen == -1) return; if (a1 < a2 && a2 < a3) do *a1++ = *a2++; while (a2 < a3); truedol -= unddol - dol; unddol = dol; } } static int jcount; /* * Join lines. Special hacks put in spaces, two spaces if * preceding line ends with '.', or no spaces if next line starts with ). */ void join(int c) { register line *a1; register char *cp, *cp1; cp = genbuf; *cp = 0; for (a1 = addr1; a1 <= addr2; a1++) { getline(*a1); cp1 = linebuf; if (a1 != addr1 && c == 0) { while (*cp1 == ' ' || *cp1 == '\t') cp1++; if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') { if (*cp1 != ')') { *cp++ = ' '; if (cp[-2] == '.') *cp++ = ' '; } } } while (*cp++ = *cp1++) if (cp > &genbuf[LBSIZE-2]) error(catgets(catd, 1, 40, "Line overflow|Result line of join would be too long")); cp--; } strcLIN(genbuf); delete(0); jcount = 1; if (FIXUNDO) undap1 = undap2 = addr1; ignore(append(jnoop, --addr1)); if (FIXUNDO) vundkind = VMANY; } static int jnoop(void) { return(--jcount); } /* * Move and copy lines. Hard work is done by move1 which * is also called by undo. */ void move1(int cflag, line *addrt) { register line *adt, *ad1, *ad2; int lines; adt = addrt; lines = (addr2 - addr1) + 1; if (cflag) { tad1 = addr1; ad1 = dol; ignore(append(getcopy, ad1++)); ad2 = dol; } else { ad2 = addr2; for (ad1 = addr1; ad1 <= ad2;) *ad1++ &= ~01; ad1 = addr1; } ad2++; if (adt < ad1) { if (adt + 1 == ad1 && !cflag && !inglobal) error(catgets(catd, 1, 41, "That move would do nothing!")); dot = adt + (ad2 - ad1); if (++adt != ad1) { reverse(adt, ad1); reverse(ad1, ad2); reverse(adt, ad2); } } else if (adt >= ad2) { dot = adt++; reverse(ad1, ad2); reverse(ad2, adt); reverse(ad1, adt); } else error(catgets(catd, 1, 42, "Move to a moved line")); change(); if (!inglobal) if(FIXUNDO) { if (cflag) { undap1 = addrt + 1; undap2 = undap1 + lines; deletenone(); } else { undkind = UNDMOVE; undap1 = addr1; undap2 = addr2; unddel = addrt; squish(); } } } void move(void) { register line *adt; bool iscopy = 0; if (Command[0] == 'm') { setdot1(); markpr(addr2 == dot ? addr1 - 1 : addr2 + 1); } else { iscopy++; setdot(); } nonzero(); adt = address((char*)0); if (adt == 0) serror(catgets(catd, 1, 43, "%s where?|%s requires a trailing address"), Command); newline(); move1(iscopy, adt); killed(); } int getcopy(void) { if (tad1 > addr2) return (EOF); getline(*tad1++); return (0); } /* * Put lines in the buffer from the undo save area. */ int getput(void) { if (tad1 > unddol) return (EOF); getline(*tad1++); tad1++; return (0); } /*ARGSUSED*/ void put(int unused) { register int cnt; if (!FIXUNDO) error(catgets(catd, 1, 44, "Cannot put inside global/macro")); cnt = unddol - dol; if (cnt && inopen && pkill[0] && pkill[1]) { pragged(1); return; } tad1 = dol + 1; ignore(append(getput, addr2)); undkind = UNDPUT; notecnt = cnt; netchange(cnt); } /* * A tricky put, of a group of lines in the middle * of an existing line. Only from open/visual. * Argument says pkills have meaning, e.g. called from * put; it is 0 on calls from putreg. */ void pragged(int kill) { extern char *cursor; register char *gp = &genbuf[cursor - linebuf]; /* * This kind of stuff is TECO's forte. * We just grunge along, since it cuts * across our line-oriented model of the world * almost scrambling our addled brain. */ if (!kill) getDOT(); strcpy(genbuf, linebuf); getline(*unddol); if (kill) *pkill[1] = 0; strcat(linebuf, gp); putmark(unddol); getline(dol[1]); if (kill) strcLIN(pkill[0]); safecp(gp, linebuf, sizeof genbuf - (gp - genbuf), "Line too long"); strcLIN(genbuf); putmark(dol+1); undkind = UNDCHANGE; undap1 = dot; undap2 = dot + 1; unddel = dot - 1; undo(1); } /* * Shift lines, based on c. * If c is neither < nor >, then this is a lisp aligning =. */ void shift(int c, int cnt) { register line *addr; register char *cp = NULL; char *dp; register int i; if(FIXUNDO) save12(), undkind = UNDCHANGE; cnt *= value(SHIFTWIDTH); for (addr = addr1; addr <= addr2; addr++) { dot = addr; #ifdef LISPCODE if (c == '=' && addr == addr1 && addr != addr2) continue; #endif getDOT(); i = whitecnt(linebuf); switch (c) { case '>': if (linebuf[0] == 0) continue; cp = genindent(i + cnt); break; case '<': if (i == 0) continue; i -= cnt; cp = i > 0 ? genindent(i) : genbuf; break; #ifdef LISPCODE default: i = lindent(addr); getDOT(); cp = genindent(i); break; #endif } if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2]) error(catgets(catd, 1, 45, "Line too long|Result line after shift would be too long")); CP(cp, dp); strcLIN(genbuf); putmark(addr); } killed(); } /* * Find a tag in the tags file. * Most work here is in parsing the tags file itself. */ void tagfind(bool quick) { char cmdbuf[BUFSIZ]; char filebuf[FNSIZE]; char tagfbuf[128]; register int c, d; bool samef = 1; int tfcount = 0; int omagic; int owrapscan; char *fn, *fne; struct stat sbuf; char *savefirstpat = NULL; int ofailed; #ifdef FASTTAG int ft_iof; char ft_iofbuf[MAXBSIZE]; off_t mid; /* assumed byte offset */ off_t top, bot; /* length of tag file */ #endif omagic = value(MAGIC); owrapscan = value(WRAPSCAN); ofailed = failed; failed = 1; if (!skipend()) { register char *lp = lasttag; while (!is_white(peekchar()) && !endcmd(peekchar())) if (lp < &lasttag[sizeof lasttag - 2]) *lp++ = getchar(); else ignchar(); *lp++ = 0; if (!endcmd(peekchar())) badtag: error(catgets(catd, 1, 46, "Bad tag|Give one tag per line")); } else if (lasttag[0] == 0) error(catgets(catd, 1, 47, "No previous tag")); c = getchar(); if (!endcmd(c)) goto badtag; if (c == EOF) ungetchar(c); clrstats(); /* * Loop once for each file in tags "path". */ safecp(tagfbuf, svalue(TAGS), sizeof tagfbuf, "Tag too long"); fne = tagfbuf - 1; while (fne) { fn = ++fne; while (*fne && *fne != ' ') fne++; if (*fne == 0) fne = 0; /* done, quit after this time */ else *fne = 0; /* null terminate filename */ #ifdef FASTTAG ft_iof = topen(fn, ft_iofbuf); if (ft_iof == -1) continue; tfcount++; fstat(ft_iof, &sbuf); top = sbuf.st_size; if (top == (off_t) 0 ) top = (off_t) -1; bot = (off_t) 0; while (top >= bot) { #else /* * Avoid stdio and scan tag file linearly. */ io = open(fn, O_RDONLY); if (io<0) continue; tfcount++; if (fstat(io, &sbuf) < 0 || sbuf.st_blksize > LBSIZE) bsize = LBSIZE; else { bsize = sbuf.st_blksize; if (bsize <= 0) bsize = LBSIZE; } while (getfile() == 0) { #endif /* loop for each tags file entry */ register char *cp = linebuf; register char *lp = lasttag; char *oglobp; #ifdef FASTTAG mid = (top + bot) / 2; tseek(ft_iof, mid); if (mid > 0) /* to get first tag in file to work */ /* scan to next \n */ if(tgets(linebuf, sizeof linebuf, ft_iof)==0) goto goleft; /* get the line itself */ if(tgets(linebuf, sizeof linebuf, ft_iof)==0) goto goleft; #ifdef TDEBUG printf("tag: %o %o %o %s\n", bot, mid, top, linebuf); #endif #endif while (*cp && *lp == *cp) cp++, lp++; if ((*lp || !is_white(*cp)) && (value(TAGLENGTH)==0 || lp-lasttag < value(TAGLENGTH))) { #ifdef FASTTAG if (*lp > *cp) bot = mid + 1; else goleft: top = mid - 1; #endif /* Not this tag. Try the next */ continue; } /* * We found the tag. Decode the line in the file. */ #ifdef FASTTAG tclose(ft_iof); #else close(io); #endif /* Rest of tag if abbreviated */ while (!is_white(*cp)) cp++; /* name of file */ while (*cp && is_white(*cp)) cp++; if (!*cp) badtags: serror(catgets(catd, 1, 48, "%s: Bad tags file entry"), lasttag); lp = filebuf; while (*cp && *cp != ' ' && *cp != '\t') { if (lp < &filebuf[sizeof filebuf - 2]) *lp++ = *cp; cp++; } *lp++ = 0; if (*cp == 0) goto badtags; if (dol != zero) { /* * Save current position in 't for ^^ in visual. */ names['t'-'a'] = *dot &~ 01; if (inopen) { extern char *ncols['z'-'a'+2]; extern char *cursor; ncols['t'-'a'] = cursor; } } safecp(cmdbuf, cp, sizeof cmdbuf, "command too long"); if (strcmp(filebuf, savedfile) || !edited) { char cmdbuf2[sizeof filebuf + 10]; savefirstpat = firstpat; firstpat = NULL; /* Different file. Do autowrite & get it. */ if (!quick) { ckaw(); if (chng && dol > zero) error(catgets(catd, 1, 49, "No write@since last change (:tag! overrides)")); } oglobp = globp; strcpy(cmdbuf2, "e! "); strcat(cmdbuf2, filebuf); globp = cmdbuf2; d = peekc; ungetchar(0); commands(1, 1); peekc = d; globp = oglobp; value(MAGIC) = omagic; if (tflag > 0) value(WRAPSCAN) = owrapscan; samef = 0; firstpat = savefirstpat; } /* * Look for pattern in the current file. */ oglobp = globp; globp = cmdbuf; d = peekc; ungetchar(0); if (samef) markpr(dot); /* * BUG: if it isn't found (user edited header * line) we get left in nomagic mode. */ value(MAGIC) = 0; if (tflag > 0) value(WRAPSCAN) = 1; commands(1, 1); failed = ofailed; peekc = d; globp = oglobp; value(MAGIC) = omagic; if (tflag > 0) { value(WRAPSCAN) = owrapscan; if (savefirstpat) { globp = savefirstpat; tflag = -1; } else tflag = 0; } return; } /* end of "for each tag in file" */ /* * No such tag in this file. Close it and try the next. */ #ifdef FASTTAG tclose(ft_iof); #else close(io); #endif } /* end of "for each file in path" */ if (tfcount <= 0) error(catgets(catd, 1, 50, "No tags file")); else serror(catgets(catd, 1, 51, "%s: No such tag@in tags file"), lasttag); } /* * Save lines from addr1 thru addr2 as though * they had been deleted. */ /*ARGSUSED*/ void yank(int unused) { if (!FIXUNDO) error(catgets(catd, 1, 52, "Can't yank inside global/macro")); save12(); undkind = UNDNONE; killcnt(addr2 - addr1 + 1); } /* * z command; print windows of text in the file. * * If this seems unreasonably arcane, the reasons * are historical. This is one of the first commands * added to the first ex (then called en) and the * number of facilities here were the major advantage * of en over ed since they allowed more use to be * made of fast terminals w/o typing .,.22p all the time. */ bool zhadpr; bool znoclear; short zweight; void zop(int hadpr) { register int c, lines, op; bool excl; zhadpr = hadpr; notempty(); znoclear = 0; zweight = 0; excl = exclam(); switch (c = op = getchar()) { case '^': zweight = 1; case '-': case '+': while (peekchar() == op) { ignchar(); zweight++; } case '=': case '.': c = getchar(); break; case EOF: znoclear++; break; default: op = 0; break; } if (isdigit(c)) { lines = c - '0'; for(;;) { c = getchar(); if (!isdigit(c)) break; lines *= 10; lines += c - '0'; } if (lines < TLINES) znoclear++; value(WINDOW) = lines; if (op == '=') lines += 2; } else lines = op == EOF ? value(SCROLL) : excl ? TLINES - 1 : 2*value(SCROLL); if (inopen || c != EOF) { ungetchar(c); newline(); } addr1 = addr2; if (addr2 == 0 && dot < dol && op == 0) addr1 = addr2 = dot+1; setdot(); zop2(lines, op); } static void splitit(void) { register int l; for (l = TCOLUMNS > 80 ? 40 : TCOLUMNS / 2; l > 0; l--) putchar('-'); putnl(); } void zop2(register int lines, register int op) { register line *split; split = NULL; switch (op) { case EOF: if (addr2 == dol) error(catgets(catd, 1, 53, "\nAt EOF")); case '+': if (addr2 == dol) error(catgets(catd, 1, 54, "At EOF")); addr2 += lines * zweight; if (addr2 > dol) error(catgets(catd, 1, 55, "Hit BOTTOM")); addr2++; default: addr1 = addr2; addr2 += lines-1; dot = addr2; break; case '=': case '.': znoclear = 0; lines--; lines >>= 1; if (op == '=') lines--; addr1 = addr2 - lines; if (op == '=') dot = split = addr2; addr2 += lines; if (op == '.') { markDOT(); dot = addr2; } break; case '^': case '-': addr2 -= lines * zweight; if (addr2 < one) error(catgets(catd, 1, 56, "Hit TOP")); lines--; addr1 = addr2 - lines; dot = addr2; break; } if (addr1 <= zero) addr1 = one; if (addr2 > dol) addr2 = dol; if (dot > dol) dot = dol; if (addr1 > addr2) return; if (op == EOF && zhadpr) { getline(*addr1); putchar('\r' | QUOTE); shudclob = 1; } else if (znoclear == 0 && CL != NOSTR && !inopen) { flush1(); vclear(); } if (addr2 - addr1 > 1) pstart(); if (split) { plines(addr1, split - 1, 0); splitit(); plines(split, split, 0); splitit(); addr1 = split + 1; } plines(addr1, addr2, 0); } void plines(line *adr1, register line *adr2, bool movedot) { register line *addr; pofix(); for (addr = adr1; addr <= adr2; addr++) { getline(*addr); pline(lineno(addr)); if (inopen) { putchar('\n' | QUOTE); } if (movedot) dot = addr; } } void pofix(void) { if (inopen && Outchar != termchar) { vnfl(); setoutt(); } } /* * Be (almost completely) sure there really * was a change, before claiming to undo. */ void somechange(void) { register line *ip, *jp; switch (undkind) { case UNDMOVE: return; case UNDCHANGE: if (undap1 == undap2 && dol == unddol) break; return; case UNDPUT: if (undap1 != undap2) return; break; case UNDALL: if (unddol - dol != lineDOL()) return; for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++) if ((*ip &~ 01) != (*jp &~ 01)) return; break; case UNDNONE: error(catgets(catd, 1, 57, "Nothing to undo")); } error(catgets(catd, 1, 58, "Nothing changed|Last undoable command didn't change anything")); } /* * Dudley doright to the rescue. * Undo saves the day again. * A tip of the hatlo hat to Warren Teitleman * who made undo as useful as do. * * Command level undo works easily because * the editor has a unique temporary file * index for every line which ever existed. * We don't have to save large blocks of text, * only the indices which are small. We do this * by moving them to after the last line in the * line buffer array, and marking down info * about whence they came. * * Undo is its own inverse. */ void undo(bool c) { register int i, j; register line *jp, *kp; line *dolp1, *newdol, *newadot; #ifdef TRACE if (trace) vudump("before undo"); #endif if (inglobal && inopen <= 0) error(catgets(catd, 1, 59, "Can't undo in global@commands")); if (!c) somechange(); pkill[0] = pkill[1] = 0; change(); if (undkind == UNDMOVE) { /* * Command to be undone is a move command. * This is handled as a special case by noting that * a move "a,b m c" can be inverted by another move. */ if ((i = (jp = unddel) - undap2) > 0) { /* * when c > b inverse is a+(c-b),c m a-1 */ addr2 = jp; addr1 = (jp = undap1) + i; unddel = jp-1; } else { /* * when b > c inverse is c+1,c+1+(b-a) m b */ addr1 = ++jp; addr2 = jp + ((unddel = undap2) - undap1); } kp = undap1; move1(0, unddel); dot = kp; Command = "move"; killed(); } else { int cnt; newadot = dot; cnt = lineDOL(); newdol = dol; dolp1 = dol + 1; /* * If a mark is pointing to a line between undap1 and * undap2-1, it would be lost (i.e. pointing into the * block between dolp and undol) after the undo. Thus * these marks have to be changed to point to the line * after dolp1 that is restored later during this undo * operation. */ if (anymarks) for (i = 0; &undap1[i] < undap2; i++) for (j = 0; j <= 'z'-'a'; j++) if (names[j] == (undap1[i] & ~01)) names[j] = dolp1[i] & ~01; /* * Command to be undone is a non-move. * All such commands are treated as a combination of * a delete command and a append command. * We first move the lines appended by the last command * from undap1 to undap2-1 so that they are just before the * saved deleted lines. */ if ((i = (kp = undap2) - (jp = undap1)) > 0) { if (kp != dolp1) { reverse(jp, kp); reverse(kp, dolp1); reverse(jp, dolp1); } /* * Account for possible backward motion of target * for restoration of saved deleted lines. */ if (unddel >= jp) unddel -= i; newdol -= i; /* * For the case where no lines are restored, dot * is the line before the first line deleted. */ dot = jp-1; } /* * Now put the deleted lines, if any, back where they were. * Basic operation is: dol+1,unddol m unddel */ if (undkind == UNDPUT) { unddel = undap1 - 1; squish(); } jp = unddel + 1; if ((i = (kp = unddol) - dol) > 0) { if (jp != dolp1) { reverse(jp, dolp1); reverse(dolp1, ++kp); reverse(jp, kp); } /* * Account for possible forward motion of the target * for restoration of the deleted lines. */ if (undap1 >= jp) undap1 += i; /* * Dot is the first resurrected line. */ dot = jp; newdol += i; } /* * Clean up so we are invertible */ unddel = undap1 - 1; undap1 = jp; undap2 = jp + i; dol = newdol; netchHAD(cnt); if (undkind == UNDALL) { dot = undadot; undadot = newadot; } else undkind = UNDCHANGE; } /* * Defensive programming - after a munged undadot. * Also handle empty buffer case. */ if ((dot <= zero || dot > dol) && dot != dol) dot = one; #ifdef TRACE if (trace) vudump("after undo"); #endif } /* * Map command: * map src dest */ void mapcmd(int un, int ab) /* int un; /\* true if this is unmap command */ /*int ab; /\* true if this is abbr command */ { char lhs[100], rhs[100]; /* max sizes resp. */ register char *p; register int c; /* mjm: char --> int */ char *dname; struct maps *mp; /* the map structure we are working on */ mp = ab ? abbrevs : exclam() ? immacs : arrows; if (skipend()) { int i; /* print current mapping values */ if (peekchar() != EOF) ignchar(); if (un) error(catgets(catd, 1, 60, "Missing lhs")); if (inopen) pofix(); for (i=0; mp[i].mapto; i++) if (mp[i].cap) { lprintf("%s", mp[i].descr); putchar('\t'); lprintf("%s", mp[i].cap); putchar('\t'); lprintf("%s", mp[i].mapto); putNFL(); } return; } ignore(skipwh()); for (p=lhs; ; ) { c = getchar(); if (c == CTRL('v')) { c = getchar(); } else if (!un && any(c, " \t")) { /* End of lhs */ break; } else if (endcmd(c) && c!='"') { ungetchar(c); if (un) { newline(); *p = 0; addmac(lhs, NOSTR, NOSTR, mp); return; } else error(catgets(catd, 1, 61, "Missing rhs")); } *p++ = c; } *p = 0; if (skipend()) error(catgets(catd, 1, 62, "Missing rhs")); for (p=rhs; ; ) { c = getchar(); if (c == CTRL('v')) { c = getchar(); } else if (endcmd(c) && c!='"') { ungetchar(c); break; } *p++ = c; } *p = 0; newline(); /* * Special hack for function keys: #1 means key f1, etc. * If the terminal doesn't have function keys, we just use #1. */ if (lhs[0] == '#') { char *fnkey; char funkey[3]; fnkey = fkey(lhs[1] - '0'); funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0; if (fnkey) strcpy(lhs, fnkey); dname = funkey; } else { dname = lhs; } addmac(lhs,rhs,dname,mp); } /* * Create the integer version of a macro string, for processing in visual * mode. imapspace cannot overflow because an earlier overflow for mapspace * would have been detected already. */ static void intmac(int **dp, char *cp) { int c, n; if (imsnext == NULL) imsnext = imapspace; *dp = imsnext; for (;;) { nextc(c, cp, n); *imsnext++ = c; if (c == 0) break; cp += n; } } /* * Add a macro definition to those that already exist. The sequence of * chars "src" is mapped into "dest". If src is already mapped into something * this overrides the mapping. There is no recursion. Unmap is done by * using NOSTR for dest. Dname is what to show in listings. mp is * the structure to affect (arrows, etc). */ void addmac1(register char *src,register char *dest,register char *dname, register struct maps *mp, int force) { register int slot, zer; #ifdef TRACE if (trace) fprintf(trace, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src, dest, dname, mp); #endif if (dest && mp==arrows && !force) { /* Make sure user doesn't screw himself */ /* * Prevent tail recursion. We really should be * checking to see if src is a suffix of dest * but this makes mapping involving escapes that * is reasonable mess up. */ if (src[1] == 0 && src[0] == dest[strlen(dest)-1]) error(catgets(catd, 1, 63, "No tail recursion")); /* * We don't let the user rob himself of ":", and making * multi char words is a bad idea so we don't allow it. * Note that if user sets mapinput and maps all of return, * linefeed, and escape, he can screw himself. This is * so weird I don't bother to check for it. */ if (isalpha(src[0]&0377) && src[1] || any(src[0],":")) error(catgets(catd, 1, 64, "Too dangerous to map that")); } else if (dest) { /* check for tail recursion in input mode: fussier */ if (eq(src, dest+strlen(dest)-strlen(src))) error(catgets(catd, 1, 65, "No tail recursion")); } /* * If the src were null it would cause the dest to * be mapped always forever. This is not good. */ if (!force && (src == NOSTR || src[0] == 0)) error(catgets(catd, 1, 66, "Missing lhs")); /* see if we already have a def for src */ zer = -1; for (slot=0; mp[slot].mapto; slot++) { if (mp[slot].cap) { if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto)) break; /* if so, reuse slot */ } else { zer = slot; /* remember an empty slot */ } } if (dest == NOSTR) { /* unmap */ if (mp[slot].cap) { mp[slot].cap = NOSTR; mp[slot].descr = NOSTR; } else { error(catgets(catd, 1, 67, "Not mapped|That macro wasn't mapped")); } return; } /* reuse empty slot, if we found one and src isn't already defined */ if (zer >= 0 && mp[slot].mapto == 0) slot = zer; /* if not, append to end */ if (slot >= MAXNOMACS) error(catgets(catd, 1, 68, "Too many macros")); if (msnext == 0) /* first time */ msnext = mapspace; /* Check is a bit conservative, we charge for dname even if reusing src */ if (msnext - mapspace + strlen(dest) + (src ? strlen(src) : 0) + strlen(dname) + 3 > MAXCHARMACS) error(catgets(catd, 1, 69, "Too much macro text")); if (src) { CP(msnext, src); mp[slot].cap = msnext; msnext += strlen(src) + 1; /* plus 1 for null on the end */ intmac(&mp[slot].icap, src); } else mp[slot].cap = NULL; CP(msnext, dest); mp[slot].mapto = msnext; msnext += strlen(dest) + 1; if (dname) { CP(msnext, dname); mp[slot].descr = msnext; msnext += strlen(dname) + 1; } else { /* default descr to string user enters */ mp[slot].descr = src; } } /* * Implements macros from command mode. c is the buffer to * get the macro from. */ void cmdmac(char c) { char macbuf[BUFSIZ]; line *ad, *a1, *a2; char *oglobp; short pk; bool oinglobal; lastmac = c; oglobp = globp; oinglobal = inglobal; pk = peekc; peekc = 0; if (inglobal < 2) inglobal = 1; regbuf(c, macbuf, sizeof(macbuf)); a1 = addr1; a2 = addr2; for (ad=a1; ad<=a2; ad++) { globp = macbuf; dot = ad; commands(1,1); } globp = oglobp; inglobal = oinglobal; peekc = pk; }