/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1984. */ static char rcsid[] = "$Header: deco.c,v 2.3 84/07/19 11:45:12 guido Exp $"; /* * B editor -- Delete and copy commands. */ #include #include "b.h" #include "erro.h" #include "bobj.h" #include "node.h" #include "gram.h" #include "supr.h" #include "queu.h" value copyout(); /* Forward */ /* * DELETE and COPY currently share a buffer, called the copy buffer. * (Physically, there is one such a buffer in each environment.) * In ordinary use, the copy buffer receives the text deleted by the * last DELETE command (unless it just removed a hole); the COPY command * can then be used (with the focus on a hole) to copy it back. * When some portion of text must be held while other text is deleted, * the COPY command again, but now with the focus on the text to be held, * copies it to the buffer and deleted text won't overwrite the buffer * until it is copied back at least once. * If the buffer holds text that was explicitly copied out but not yet * copied back in, it is saved on a file when the editor exits, so it can * be used in the next session; but this is not true for text implicitly * placed in the buffer through DELETE. */ /* * Delete command -- delete the text in the focus, or delete the hole * if it is only a hole. */ Visible bool delete(ep) register environ *ep; { higher(ep); shrink(ep); if (ishole(ep)) return delhole(ep); if (!ep->copyflag) { release(ep->copybuffer); ep->copybuffer = copyout(ep); } return delbody(ep); } /* * Delete the focus under the assumption that it contains some text. */ Visible bool delbody(ep) register environ *ep; { ep->changed = Yes; subgrow(ep, No); /* Don't ignore spaces */ switch (ep->mode) { case SUBRANGE: if (ep->s1&1) return delfixed(ep); return delvarying(ep); case SUBSET: return delsubset(ep, Yes); case SUBLIST: return delsublist(ep); case WHOLE: return delwhole(ep); default: Abort(); /* NOTREACHED */ } } /* * Delete portion (ep->mode == SUBRANGE) of varying text ((ep->s1&1) == 0). */ Hidden bool delvarying(ep) register environ *ep; { auto queue q = Qnil; register node n = tree(ep->focus); auto value v = (value) child(n, ep->s1/2); register len = Length(v); Assert(ep->mode == SUBRANGE && !(ep->s1&1)); /* Wrong call */ Assert(Type(v) == Tex); /* Inconsistent parse tree */ if (ep->s2 == 0 && !mayinsert(tree(ep->focus), ep->s1/2, 0, Str(v)[ep->s3 + 1])) { /* Cannot do simple substring deletion. */ stringtoqueue(Str(v) + ep->s3 + 1, &q); delfocus(&ep->focus); ep->mode = WHOLE; return app_queue(ep, &q); } v = copy(v); putintrim(&v, ep->s2, len - ep->s3 - 1, ""); s_downi(ep, ep->s1/2); replace(&ep->focus, (node) v); s_up(ep); ep->mode = VHOLE; return Yes; } /* * Delete portion (ep->mode == SUBRANGE) of fixed text ((ep->s1&1) == 1). */ Hidden bool delfixed(ep) register environ *ep; { register node n = tree(ep->focus); char buf[15]; /* Long enough for all fixed texts */ register string repr = noderepr(n)[ep->s1/2]; register int len; queue q = Qnil; bool ok; Assert(ep->mode == SUBRANGE && (ep->s1&1)); if (ep->s1 > 1) { ep->mode = FHOLE; return Yes; } Assert(fwidth(repr) < sizeof buf - 1); len = ep->s2; ep->s2 = ep->s3 + 1; ep->mode = FHOLE; nosuggtoqueue(ep, &q); strcpy(buf, repr); if (nchildren(tree(ep->focus)) > 0) buf[len] = 0; else strcpy(buf+len, buf+ep->s2); delfocus(&ep->focus); ep->mode = WHOLE; markpath(&ep->focus, 1); ok = ins_string(ep, buf, &q, 0); if (!ok) { qrelease(q); return No; } firstmarked(&ep->focus, 1) || Abort(); unmkpath(&ep->focus, 1); fixfocus(ep, len); return app_queue(ep, &q); } /* * Delete focus if ep->mode == SUBSET. */ Hidden bool delsubset(ep, hack) register environ *ep; bool hack; { auto queue q = Qnil; auto queue q2 = Qnil; register node n = tree(ep->focus); register node nn; register string *rp = noderepr(n); register int nch = nchildren(n); register int i; if (hack) { shrsubset(ep); if (ep->s1 == ep->s2 && !(ep->s1&1)) { nn = child(tree(ep->focus), ep->s1/2); if (fwidth(noderepr(nn)[0]) < 0) { /* It starts with a newline, leave the newline */ s_downi(ep, ep->s1/2); ep->mode = SUBSET; ep->s1 = 2; ep->s2 = 2*nchildren(nn) + 1; return delsubset(ep, hack); } } subgrsubset(ep, No); /* Undo shrsubset */ if (ep->s2 == 3 && rp[1] && Strequ(rp[1], "\t")) --ep->s2; /* Hack for deletion of unit-head or if/for/wh. head */ } if (ep->s1 == 1 && Fw_negative(rp[0])) ++ep->s1; /* Hack for deletion of test-suite or refinement head */ if (Fw_zero(rp[0]) ? (ep->s2 < 3 || ep->s1 > 3) : ep->s1 > 1) { /* No deep structural change */ for (i = (ep->s1+1)/2; i <= ep->s2/2; ++i) { s_downi(ep, i); delfocus(&ep->focus); s_up(ep); } if (ep->s1&1) { ep->mode = FHOLE; ep->s2 = 0; } else if (Type(child(tree(ep->focus), ep->s1/2)) == Tex) { ep->mode = VHOLE; ep->s2 = 0; } else { s_downi(ep, ep->s1/2); ep->mode = ATBEGIN; } return Yes; } balance(ep); /* Make balanced \t - \b pairs */ subsettoqueue(n, 1, ep->s1-1, &q); subsettoqueue(n, ep->s2+1, 2*nch+1, &q2); nonewline(&q2); /* Wonder what will happen...? */ delfocus(&ep->focus); ep->mode = ATBEGIN; leftvhole(ep); if (!ins_queue(ep, &q, &q2)) { qrelease(q2); return No; } return app_queue(ep, &q2); } /* * Delete the focus if ep->mode == SUBLIST. */ delsublist(ep) register environ *ep; { register node n; register int i; register int sym; queue q = Qnil; bool flag; Assert(ep->mode == SUBLIST); n = tree(ep->focus); flag = fwidth(noderepr(n)[0]) < 0; for (i = ep->s3; i > 0; --i) { n = lastchild(n); Assert(n); } if (flag) { n = nodecopy(n); s_down(ep); do { delfocus(&ep->focus); } while (rite(&ep->focus)); if (!allowed(ep->focus, symbol(n))) { error(DEL_REM); /* The remains wouldn't fit */ noderelease(n); return No; } replace(&ep->focus, n); s_up(ep); s_down(ep); /* I.e., to leftmost sibling */ ep->mode = WHOLE; return Yes; } sym = symbol(n); if (sym == Optional || sym == Hole) { delfocus(&ep->focus); ep->mode = WHOLE; } else if (!allowed(ep->focus, sym)) { preptoqueue(n, &q); delfocus(&ep->focus); ep->mode = WHOLE; return app_queue(ep, &q); } else { replace(&ep->focus, nodecopy(n)); ep->mode = ATBEGIN; } return Yes; } /* * Delete the focus if ep->mode == WHOLE. */ Hidden bool delwhole(ep) register environ *ep; { register int sym = symbol(tree(ep->focus)); Assert(ep->mode == WHOLE); if (sym == Optional || sym == Hole) return No; delfocus(&ep->focus); return Yes; } /* * Delete the focus if it is only a hole. * Assume shrink() has been called before! */ Hidden bool delhole(ep) register environ *ep; { node n; int sym; bool flag = No; switch (ep->mode) { case ATBEGIN: case VHOLE: case FHOLE: case ATEND: return widen(ep); case WHOLE: Assert((sym = symbol(tree(ep->focus))) == Optional || sym == Hole); if (ichild(ep->focus) != 1) break; if (!up(&ep->focus)) return No; higher(ep); ep->mode = SUBSET; ep->s1 = 2; ep->s2 = 2; if (fwidth(noderepr(tree(ep->focus))[0]) < 0) { flag = Yes; ep->s2 = 3; /* Extend to rest of line */ } } ep->changed = Yes; grow(ep); switch (ep->mode) { case SUBSET: if (!delsubset(ep, No)) return No; if (!flag) return widen(ep); leftvhole(ep); oneline(ep); return Yes; case SUBLIST: n = tree(ep->focus); n = lastchild(n); sym = symbol(n); if (!allowed(ep->focus, sym)) { error(DEL_REM); /* The remains wouldn't fit */ return No; } flag = samelevel(sym, symbol(tree(ep->focus))); replace(&ep->focus, nodecopy(n)); if (flag) { ep->mode = SUBLIST; ep->s3 = 1; } else ep->mode = WHOLE; return Yes; case WHOLE: Assert(!parent(ep->focus)); /* Must be at root! */ return No; default: Abort(); /* NOTREACHED */ } } /* * Subroutine to delete the focus. */ Visible Procedure delfocus(pp) register path *pp; { register path pa = parent(*pp); register int sympa = pa ? symbol(tree(pa)) : Rootsymbol; replace(pp, child(gram(sympa), ichild(*pp))); } /* * Copy command -- copy the focus to the copy buffer if it contains * some text, copy the copy buffer into the focus if the focus is * empty (just a hole). */ Visible bool copyinout(ep) register environ *ep; { shrink(ep); if (!ishole(ep)) { release(ep->copybuffer); ep->copybuffer = copyout(ep); ep->copyflag = !!ep->copybuffer; return ep->copyflag; } else { fixit(ep); /* Make sure it looks like a hole now */ if (!copyin(ep, (queue) ep->copybuffer)) return No; ep->copyflag = No; return Yes; } } /* * Copy the focus to the copy buffer. */ Visible value copyout(ep) register environ *ep; { auto queue q = Qnil; auto path p; register node n; register value v; char buf[15]; register string *rp; register int i; switch (ep->mode) { case WHOLE: preptoqueue(tree(ep->focus), &q); break; case SUBLIST: p = pathcopy(ep->focus); for (i = ep->s3; i > 0; --i) downrite(&p) || Abort(); for (i = ep->s3; i > 0; --i) { up(&p) || Abort(); n = tree(p); subsettoqueue(n, 1, 2*nchildren(n) - 1, &q); } pathrelease(p); break; case SUBSET: balance(ep); subsettoqueue(tree(ep->focus), ep->s1, ep->s2, &q); break; case SUBRANGE: Assert(ep->s3 >= ep->s2); if (ep->s1&1) { /* Fixed text */ Assert(ep->s3 - ep->s2 + 1 < sizeof buf); rp = noderepr(tree(ep->focus)); Assert(ep->s2 < Fwidth(rp[ep->s1/2])); strncpy(buf, rp[ep->s1/2] + ep->s2, ep->s3 - ep->s2 + 1); buf[ep->s3 - ep->s2 + 1] = 0; stringtoqueue(buf, &q); } else { /* Varying text */ v = (value) child(tree(ep->focus), ep->s1/2); Assert(Type(v) == Tex); v = trim(v, ep->s2, Length(v) - ep->s3 - 1); preptoqueue((node)v, &q); release(v); } break; default: Abort(); } nonewline(&q); return (value)q; } /* * Subroutine to ensure the copy buffer doesn't start with a newline. */ Hidden Procedure nonewline(pq) register queue *pq; { register node n; register int c; if (!emptyqueue(*pq)) { for (;;) { n = queuebehead(pq); if (Type(n) == Tex) { if (Str((value) n)[0] != '\n') preptoqueue(n, pq); noderelease(n); break; } else { c = nodechar(n); if (c != '\n') preptoqueue(n, pq); else splitnode(n, pq); noderelease(n); if (c != '\n') break; } } } } /* * Refinement for copyout, case SUBSET: make sure that \t is balanced with \b. * Actually it can only handle the case where a \t is in the subset and the * matching \b is immediately following. */ Hidden Procedure balance(ep) environ *ep; { string *rp = noderepr(tree(ep->focus)); int i; int level = 0; Assert(ep->mode == SUBSET); for (i = ep->s1/2; i*2 < ep->s2; ++i) { if (rp[i]) { if (index(rp[i], '\t')) ++level; else if (index(rp[i], '\b')) --level; } } if (level > 0 && i*2 == ep->s2 && rp[i] && index(rp[i], '\b')) ep->s2 = 2*i + 1; } /* * Copy the copy buffer to the focus. */ Hidden bool copyin(ep, q) register environ *ep; /*auto*/ queue q; { auto queue q2 = Qnil; if (!q) { error(COPY_EMPTY); /* Empty copy buffer */ return No; } ep->changed = Yes; q = qcopy(q); if (!ins_queue(ep, &q, &q2)) { qrelease(q2); return No; } return app_queue(ep, &q2); } /* * Find out whether the focus looks like a hole or if it has some real * text in it. * Assumes shrink(ep) has already been performed. */ Visible bool ishole(ep) register environ *ep; { register int sym; switch (ep->mode) { case ATBEGIN: case ATEND: case VHOLE: case FHOLE: return Yes; case SUBLIST: case SUBRANGE: return No; case SUBSET: return colonhack(ep); /* (Side-effect!) */ case WHOLE: sym = symbol(tree(ep->focus)); return sym == Optional || sym == Hole; default: Abort(); /* NOTREACHED */ } } /* * Amendment to ishole so that it categorizes '?: ?' as a hole. * This makes deletion of empty refinements / alternative-suites * easier (Steven). */ Hidden bool colonhack(ep) environ *ep; { node n = tree(ep->focus); node n1; string *rp = noderepr(n); int i; int sym; for (i = ep->s1; i <= ep->s2; ++i) { if (i&1) { if (!allright(rp[i/2])) return No; } else { n1 = child(n, i/2); if (Type(n1) == Tex) return No; sym = symbol(n1); if (sym != Hole && sym != Optional) return No; } } return Yes; } /* * Refinement for colonhack. Recognize strings that are almost blank * (i.e. containing only spaces, colons and the allowed control characters). */ Hidden bool allright(repr) string repr; { if (repr) { for (; *repr; ++repr) { if (!index(": \t\b\n\r", *repr)) return No; } } return Yes; }