/* these routines take care of undo operations
 * code by Paul Fox, original algorithm mostly by Julia Harper May, 89
 *
 * written for vile: Copyright (c) 1990, 1995-2001 by Paul Fox
 *
 * $Header: /usr/build/vile/vile/RCS/undo.c,v 1.84 2002/10/09 19:56:46 tom Exp $
 *
 */

#include "estruct.h"
#include "edef.h"

/*
 * There are two stacks and two "dots" saved for undo operations.
 *
 * The first stack, the "backstack", or the "undo stack", is used to move
 * back in history -- that is, as changes are made, they go on this stack,
 * and as we undo those changes, they come off of this stack.  The second
 * stack, the "forward stack" or "redo stack", is used when going forward
 * in history, i.e., when undoing an undo.
 *
 * The "backdot" is the last value of DOT _before_ a change occurred, and
 * therefore must be restored _after_ undoing that change.  The "forwdot"
 * is the first value of DOT _after_ a change occurred, and therefore must
 * be restored _after_ re-doing that change.
 *
 * Distinct sets of changes (i.e.  single user-operations) are separated on
 * the stacks by "stack separator" (natch) entries.  The lforw and lback
 * pointers of these entries are used to save the values of the forward and
 * backward dots.
 *
 * The undo strategy is this:
 *
 * 1) For any deleted line, push it onto the undo stack.
 *
 * 2) On any change to a line, make a copy of it, push the copy to the undo
 * stack, and mark the original as having been copied.  Do not copy/push
 * lines that are already marked as having been copied.  Push a tag
 * matching up the copy with the original.  Later, when undoing, and the
 * copy is being put into the file, we can go down through the undo stack,
 * find all references to the original line, and make them point at the
 * copy instead.  We could do this immediately, instead of pushing the
 * "patch" and waiting for the undo, but it seems better to pay the price
 * of that stack walk at undo time, rather than at the time of the change.
 * This patching wouldn't be necessary at all if we used line no's as the
 * pointers, instead of real pointers.
 *
 * On the actual undo, we pop these things (lines or tags) one by one.
 * There should be either a) no lines where it goes (it was a deleted line)
 * and we can just put it back, or b) exactly one line where it goes (it
 * was a changed/copied line) and it can be replaced, or if this is a tag
 * we're popping (not a real line), then the line it points at was a
 * fresh insert (and should be deleted now).  That makes it easy to undo
 * the changes one by one.  Of course, we need to build a different,
 * inverse stack (the "forward", or "redo" stack) as we go, so that undo
 * can itself be undone.
 *
 * The "copied" cookie in the LINE structure is unioned with the stack link
 * pointer on the undo stack, since they aren't both needed at once.
 *
 * There are basically three interface routines for code making buffer changes:
 *  toss_to_undo() -- called when deleting a whole line
 *  tag_for_undo() -- called when inserting a whole line
 *  copy_for_undo() -- called when modifying a line
 * These routines should be called _before_ calling chg_buff to mark the
 * buffer as modified, since they want to record the current
 * modified/unmodified state in the undo stack, so it can be restored later.
 *
 * In addition:
 *  freeundostacks() cleans up any undo data structures for a buffer,
 *  nounmodifiable() is called if a change is happening to a
 *	buffer that is not undoable.
 *  mayneedundo() is called before starting an operation that might call
 *	one of the toss/copy/tag routines above.
 *  dumpuline() is called if the whole-line undo (the 'U' command) line
 *	may need to be flushed due to the current change.
 *  redo_ok() and undo_ok() return T if something is on the appropriate stack.
 *
 * The command functions are:
 *  backundo() -- undo changes going back in history  (^X-u)
 *  forwredo() -- redo changes going forward in history  (^X-r)
 *  undo() -- undo changes, toggling backwards and forwards  (u)
 *  lineundo() -- undo all changes to currently-being-modified line (U)
 *	(The lineundo() command is kind of separate, and acts independently
 *	 of the normal undo stacks -- an extra copy of the line is simply
 *	 made when the first change is made.)
 *
 *
 * Notes on how the "copied" marks work:
 *
 * Say you do a change to a line, like you go into insertmode, and type
 * three characters.  The first character causes linsert to be called,
 * which calls copy_for_undo(), which copies the line, and marks it as
 * "copied".  Then, the second and third characters also call
 * copy_for_undo(), but since the line is marked, nothing happens.  Now you
 * hit ESC.  Now enter insertmode again.  So far, nothing has happened,
 * except that the "needundocleanup" flag has been set (by execute()s call
 * to mayneedundo()), since we're in an undo-able command.  Type a
 * character.  linsert() calls copy_for_undo() which calls preundocleanup()
 * (based on the "needundocleanup" flag.  In previous versions of vile,
 * this cleanup required walking the entire buffer, to reset the "copied"
 * flag.  Now, the "copied" flag is actually a word-sized "cookie", which
 * matches the global "current_undo_cookie" when the line has been copied.
 * By incrementing the global "current_undo_cookie" in preundocleanup(), we
 * are effectively resetting all of the lines' "marks", since the cookie is
 * now _guaranteed_ to not match against any of them.  Which is why, when
 * the cookie wraps around to 0, we _do_ need to clean the buffer, because
 * now there's a chance that the current_undo_cookie might match a very old
 * marked line.
 */

#define FORW 0
#define BACK 1

/* shorthand for the two stacks and the two dots */
#define BACKSTK(bp) (&(bp->b_udstks[BACK]))
#define FORWSTK(bp) (&(bp->b_udstks[FORW]))
#define BACKDOT(bp) (bp->b_uddot[BACK])
#define FORWDOT(bp) (bp->b_uddot[FORW])

/* these let us refer to the current and other stack in relative terms */
#define STACK(i)	(&(curbp->b_udstks[i]))
#define OTHERSTACK(i)	(&(curbp->b_udstks[1^i]))

static LINE *popline(LINEPTR * stkp, int force);
static LINEPTR copyline(LINE *lp);
static int linesmatch(LINE *lp1, register LINE *lp2);
static int undoworker(int stkindx);
static void freshstack(int stkindx);
static void make_undo_patch(LINEPTR olp, LINEPTR nlp);
static void preundocleanup(void);
static void pushline(LINEPTR lp, LINEPTR * stk);
static void repointstuff(register LINEPTR nlp, register LINEPTR olp);
static void setupuline(LINEPTR lp);

static short needundocleanup;

/* this could be per-buffer, but i don't think it matters in practice */
static USHORT current_undo_cookie = 1;	/* see L_FLAG.cook */

/* #define UNDOLOG 1 */
#ifdef UNDOLOG
static void
undolog(char *s, LINEPTR lp)
{
    char *t;
    if (lisreal(lp))
	t = "real";
    else if (lispurestacksep(lp))
	t = "purestacksep";
    else if (lisstacksep(lp))
	t = "stacksep";
    else if (lispatch(lp))
	t = "patch";
    else
	t = "unknown";

    dbgwrite("%s %s lp 0x%x", s, t, lp);
}
#else
# define undolog(s,l)
#endif

/*
 * Test if the buffer is modifiable
 */
static int
OkUndo(void)
{
    if (curbp == bminip)
	return FALSE;
    if (!b_val(curbp, MDUNDOABLE))
	return FALSE;
#define SCRATCH 1
#if SCRATCH
    if (b_is_scratch(curbp))
#else
    if (b_val(curbp, MDVIEW))
#endif
	return FALSE;
    return TRUE;
}

/* push a deleted line onto the undo stack. */
void
toss_to_undo(LINEPTR lp)
{
    register LINEPTR next;
    register LINEPTR prev;
    int fc;

    if (!OkUndo())
	return;

    if (needundocleanup)
	preundocleanup();

    pushline(lp, BACKSTK(curbp));

    next = lforw(lp);

    /* need to save a dot -- either the next line or
       the previous one */
    if (next == buf_head(curbp)) {
	prev = lback(lp);
	FORWDOT(curbp).l = prev;
	fc = firstchar(prev);
	if (fc < 0)		/* all white */
	    FORWDOT(curbp).o = llength(prev) - 1;
	else
	    FORWDOT(curbp).o = fc;
    } else {
	FORWDOT(curbp).l = next;
	fc = firstchar(next);
	if (fc < 0)		/* all white */
	    FORWDOT(curbp).o = b_left_margin(curbp);
	else
	    FORWDOT(curbp).o = fc;
    }

    dumpuline(lp);
}

/*
 * Push a copy of a line onto the undo stack.  Push a patch so we can
 * later fix up any references to this line that might already be in the
 * stack.  When the undo happens, the later pops (i.e. those lines still
 * in the stack) will point at the _original_ (which will by then be on the
 * redo stack) instead of at the copy, which will have just been popped,
 * unless we fix them by popping and using the patch.
 */

void
copy_for_undo(LINEPTR lp)
{
    register LINEPTR nlp;

    if (!OkUndo())
	return;

    if (needundocleanup)
	preundocleanup();

    if (liscopied(lp))
	return;

    /* take care of the normal undo stack */
    nlp = copyline(lp);
    if (nlp == null_ptr)
	return;

    pushline(nlp, BACKSTK(curbp));

    make_undo_patch(lp, nlp);

    lsetcopied(lp);

    setupuline(lp);

    FORWDOT(curbp).l = lp;
    FORWDOT(curbp).o = DOT.o;
}

/* push an unreal line onto the undo stack
 * lp should be the new line, _after_ insertion, so
 *	lforw() and lback() are right
 */
void
tag_for_undo(LINEPTR lp)
{
    register LINEPTR nlp;

    if (!OkUndo())
	return;

    if (needundocleanup)
	preundocleanup();

    if (liscopied(lp))
	return;

    nlp = lalloc(LINENOTREAL, curbp);
    if (nlp == null_ptr)
	return;
    set_lforw(nlp, lforw(lp));
    set_lback(nlp, lback(lp));

    pushline(nlp, BACKSTK(curbp));

    lsetcopied(lp);
    FORWDOT(curbp).l = lp;
    FORWDOT(curbp).o = DOT.o;
}

/* Change all PURESTACKSEPS on the stacks to STACKSEPS, so that undo won't
 * reset the BFCHG bit.  This should be called anytime a non-undoable change is
 * made to a buffer.
 */
void
nounmodifiable(BUFFER *bp)
{
    register LINE *tlp;
    for (tlp = *BACKSTK(bp); tlp != NULL; tlp = tlp->l_nxtundo) {
	if (lispurestacksep(tlp))
	    tlp->l_used = STACKSEP;
    }
    for (tlp = *FORWSTK(bp); tlp != NULL; tlp = tlp->l_nxtundo) {
	if (lispurestacksep(tlp))
	    tlp->l_used = STACKSEP;
    }
}

/* before any undoable command (except undo itself), clean the undo list */
/* clean the copied flag on the line we're the copy of */
void
freeundostacks(register BUFFER *bp, int both)
{
    register LINE *lp;

    while ((lp = popline(FORWSTK(bp), TRUE)) != NULL) {
	lfree(lp, bp);
    }
    if (both) {
	while ((lp = popline(BACKSTK(bp), TRUE)) != NULL)
	    lfree(lp, bp);
	bp->b_udtail = null_ptr;
	bp->b_udlastsep = null_ptr;
	bp->b_udcount = 0;
    }

}

/* ARGSUSED */
int
undo(int f GCC_UNUSED, int n GCC_UNUSED)
{
    int s;
    L_NUM before;

    if (b_val(curbp, MDVIEW))
	return (rdonly());

    before = vl_line_count(curbp);
    if ((s = undoworker(curbp->b_udstkindx)) == TRUE) {
	if (!line_report(before)) {
	    mlwrite("[change %sdone]",
		    curbp->b_udstkindx == BACK ? "un" : "re");
	}
	curbp->b_udstkindx ^= 1;	/* flip to other stack */
    } else {
	mlwarn("[No changes to undo]");
    }
    return s;
}

int
inf_undo(int f, int n)
{
    int s = TRUE;

    if (!f || n < 1)
	n = 1;

    if (b_val(curbp, MDVIEW))
	return (rdonly());

    curbp->b_udstkindx ^= 1;	/* flip to other stack */
    while (s && n--) {
	if ((s = undoworker(curbp->b_udstkindx)) == TRUE) {
	    mlwrite("[change %sdone]",
		    curbp->b_udstkindx == BACK ? "un" : "re");
	} else {
	    mlwarn("[No more changes to %s]",
		   curbp->b_udstkindx == BACK ? "undo" : "redo");
	}
    }
    curbp->b_udstkindx ^= 1;	/* flip to other stack */
    return s;
}

int
backundo(int f, int n)
{
    int s = TRUE;

    if (b_val(curbp, MDVIEW))
	return (rdonly());

    if (!f || n < 1)
	n = 1;

    while (s && n--) {
	s = undoworker(BACK);
	if (s) {
	    mlwrite("[change undone]");
	} else {
	    mlwarn("[No more changes to undo]");
	}
    }

    curbp->b_udstkindx = FORW;	/* flip to other stack */

    return s;
}

int
forwredo(int f, int n)
{
    int s = TRUE;

    if (b_val(curbp, MDVIEW))
	return (rdonly());

    if (!f || n < 1)
	n = 1;

    while (s && n--) {
	s = undoworker(FORW);
	if (s) {
	    mlwrite("[change redone]");
	} else {
	    mlwarn("[No more changes to redo]");
	}
    }

    curbp->b_udstkindx = BACK;	/* flip to other stack */

    return s;
}

void
mayneedundo(void)
{
    if (in_autocolor)
	return;
    needundocleanup = TRUE;
}

static void
preundocleanup(void)
{
    register LINE *lp;

    freeundostacks(curbp, FALSE);

    /* clear the flags in the buffer */
    /* there may be a way to clean these less drastically, by
       using the information on the stacks above, but I
       couldn't figure it out.  -pgf  */
    if (++current_undo_cookie == 0) {
	current_undo_cookie++;	/* never let it be zero */
	for_each_line(lp, curbp) {	/* once in while, feel the pain */
	    lsetnotcopied(lp);
	}
    }

    curbp->b_udstkindx = BACK;

    if (doingopcmd)
	BACKDOT(curbp) = pre_op_dot;
    else
	BACKDOT(curbp) = DOT;

    /* be sure FORWDOT has _some_ value (may be null the first time)
       if (sameline(FORWDOT(curbp), nullmark))
       FORWDOT(curbp) = BACKDOT(curbp);
     */
    freshstack(BACK);
    FORWDOT(curbp) = BACKDOT(curbp);

    needundocleanup = FALSE;
}

static void
pushline(LINEPTR lp, LINEPTR * stk)
{
    lp->l_nxtundo = *stk;
    *stk = lp;
    undolog("pushing", lp);
}

/* get a line from the specified stack.  unless force'ing, don't
	go past a false bottom stack-separator */
static LINE *
popline(LINEPTR * stkp, int force)
{
    LINE *lp;

    lp = *stkp;

    if (lp == NULL || (!force && lisstacksep(lp))) {
	undolog("popping null", lp);
	return NULL;
    }

    *stkp = lp->l_nxtundo;
    lp->l_nxtundo = null_ptr;
    undolog("popped", lp);
    return (lp);
}

static LINE *
peekline(LINEPTR * stkp)
{
    return *stkp;
}

static void
freshstack(int stkindx)
{
    register LINEPTR plp;
    /* push on a stack delimiter, so we know where this undo ends */
    if (b_is_changed(curbp)) {
	plp = lalloc(STACKSEP, curbp);
    } else {			/* if the buffer is unmodified, use special separator */
	plp = lalloc(PURESTACKSEP, curbp);

	/* and make sure there are no _other_ special separators */
	nounmodifiable(curbp);
    }
    if (plp == null_ptr)
	return;
    set_lback(plp, BACKDOT(curbp).l);
    plp->l_back_offs = BACKDOT(curbp).o;
    set_lforw(plp, FORWDOT(curbp).l);
    plp->l_forw_offs = FORWDOT(curbp).o;
    pushline(plp, STACK(stkindx));
    if (stkindx == BACK) {
	plp->l_nextsep = null_ptr;
	if (curbp->b_udtail == null_ptr) {
	    if (curbp->b_udcount != 0) {
		mlforce("BUG: null tail with non-0 undo count");
		curbp->b_udcount = 0;
	    }
	    curbp->b_udtail = plp;
	    curbp->b_udlastsep = plp;
	} else {
	    if (curbp->b_udlastsep == null_ptr) {
		/* then we need to find lastsep */
		int i;
		curbp->b_udlastsep = curbp->b_udtail;
		for (i = curbp->b_udcount - 1; i > 0; i--) {
		    curbp->b_udlastsep =
			curbp->b_udlastsep->l_nextsep;
		}
	    }
	    curbp->b_udlastsep->l_nextsep = plp;
	    curbp->b_udlastsep = plp;
	}
	/* enforce stack growth limit */
	curbp->b_udcount++;
	/* dbgwrite("bumped undocount %d", curbp->b_udcount); */
	if (b_val(curbp, VAL_UNDOLIM) != 0 &&
	    curbp->b_udcount > b_val(curbp, VAL_UNDOLIM)) {
	    LINEPTR newtail;
	    LINEPTR lp;

	    newtail = curbp->b_udtail;
	    while (curbp->b_udcount > b_val(curbp, VAL_UNDOLIM)) {
		newtail = newtail->l_nextsep;
		curbp->b_udcount--;
	    }

	    curbp->b_udtail = newtail;
	    newtail = newtail->l_nxtundo;
	    if (newtail != null_ptr) {
		do {
		    lp = newtail;
		    if (newtail == curbp->b_udlastsep)
			mlforce("BUG: tail passed lastsep");
		    newtail = newtail->l_nxtundo;
		    lfree(lp, curbp);
		} while (newtail != null_ptr);
	    }
	    curbp->b_udtail->l_nxtundo = null_ptr;

	}
    }
}

static void
make_undo_patch(LINEPTR olp, LINEPTR nlp)
{
    register LINEPTR plp;
    /* push on a tag that matches up the copy with the original */
    plp = lalloc(LINEUNDOPATCH, curbp);
    if (plp == null_ptr)
	return;
    set_lforw(plp, olp);	/* lforw() is the original line */
    set_lback(plp, nlp);	/* lback() is the copy */
    pushline(plp, BACKSTK(curbp));
}

static void
applypatch(LINEPTR newlp, LINEPTR oldlp)
{
    register LINE *tlp;
    for (tlp = *BACKSTK(curbp); tlp != NULL;
	 tlp = tlp->l_nxtundo) {
	if (!lispatch(tlp)) {
	    if (lforw(tlp) == oldlp)
		set_lforw(tlp, newlp);
	    if (lback(tlp) == oldlp)
		set_lback(tlp, newlp);
	} else {		/* it's a patch */
	    if (lforw(tlp) == oldlp) {
		set_lforw(tlp, newlp);
	    }
	    if (lback(tlp) == oldlp) {
		mlforce("BUG? copy is an old line");
		break;
	    }
	}
    }
}

static LINEPTR
copyline(register LINE *lp)
{
    register LINE *nlp;

    nlp = lalloc(lp->l_used, curbp);
    if (nlp == NULL)
	return null_ptr;
    /* copy the text and forward and back pointers.  everything else
       matches already */
    set_lforw(nlp, lforw(lp));
    set_lback(nlp, lback(lp));
    /* copy the rest */
    if (lp->l_text && nlp->l_text)
	(void) memcpy(nlp->l_text, lp->l_text, (size_t) lp->l_used);
    return nlp;
}

static int
undoworker(int stkindx)
{
    register LINEPTR lp;
    register LINEPTR alp;
    int nopops = TRUE;

    while ((lp = popline(STACK(stkindx), FALSE)) != 0) {
	if (nopops)		/* first pop -- establish a new stack base */
	    freshstack(1 ^ stkindx);
	nopops = FALSE;
	if (lislinepatch(lp)) {
	    applypatch(lback(lp), lforw(lp));
	    lfree(lp, curbp);
	    continue;
	}
	if (lforw(lback(lp)) != lforw(lp)) {	/* there's something there */
	    if (lforw(lforw(lback(lp))) == lforw(lp)) {
		/* then there is exactly one line there */
		/* alp is the line to remove */
		/* lp is the line we're putting in */
		alp = lforw(lback(lp));
		repointstuff(lp, alp);
		/* remove it */
		set_lforw(lback(lp), lforw(alp));
		set_lback(lforw(alp), lback(alp));
	    } else {		/* there is more than one line there */
		mlforce("BUG: no stacked line for an insert");
		/* cleanup ? naw, a bugs a bug */
		return (FALSE);
	    }
	} else {		/* there is no line where we're going */
	    /* create an "unreal" tag line to push */
	    alp = lalloc(LINENOTREAL, curbp);
	    if (alp == null_ptr)
		return (FALSE);
	    set_lforw(alp, lforw(lp));
	    set_lback(alp, lback(lp));
	}

	/* insert real lines into the buffer
	   throw away the markers */
	if (lisreal(lp)) {
	    set_lforw(lback(lp), lp);
	    set_lback(lforw(lp), lp);
	} else {
	    lfree(lp, curbp);
	}

	pushline(alp, OTHERSTACK(stkindx));
    }

    if (nopops) {
	if (stkindx == BACK && curbp->b_udcount != 0) {
	    mlforce("BUG: nopop, non-0 undo count");
	}
	return (FALSE);
    }
#define bug_checks 1
#ifdef bug_checks
    if ((lp = peekline(STACK(stkindx))) == 0) {
	mlforce("BUG: found null after undo/redo");
	return FALSE;
    }

    if (!lisstacksep(lp)) {
	mlforce("BUG: found non-sep after undo/redo");
	return FALSE;
    }
#endif

    lp = popline(STACK(stkindx), TRUE);
    FORWDOT(curbp).l = lforw(lp);
    FORWDOT(curbp).o = lp->l_forw_offs;
    BACKDOT(curbp).l = lback(lp);
    BACKDOT(curbp).o = lp->l_back_offs;
    if (stkindx == FORW) {
	/* if we moved, update the "last dot" mark */
	if (!sameline(DOT, FORWDOT(curbp)))
	    curwp->w_lastdot = DOT;
	DOT = FORWDOT(curbp);
    } else {
	/* if we moved, update the "last dot" mark */
	if (!sameline(DOT, BACKDOT(curbp)))
	    curwp->w_lastdot = DOT;
	DOT = BACKDOT(curbp);
	/* dbgwrite("about to decr undocount %d", curbp->b_udcount); */
	curbp->b_udcount--;
	curbp->b_udlastsep = null_ptr;	/* it's only a hint */
	if (curbp->b_udtail == lp) {
	    if (curbp->b_udcount != 0) {
		mlforce("BUG: popped tail; non-0 undo count");
		curbp->b_udcount = 0;
	    }
	    /* dbgwrite("clearing tail 0x%x and lastsep 0x%x", curbp->b_udtail,
	       curbp->b_udlastsep); */
	    curbp->b_udtail = null_ptr;
	    curbp->b_udlastsep = null_ptr;
	}
    }

    b_clr_counted(curbp);	/* don't know the size! */
    if (lispurestacksep(lp))
	unchg_buff(curbp, WFHARD);
    else
	chg_buff(curbp, WFHARD | WFINS | WFKILLS);

    lfree(lp, curbp);

    return TRUE;
}

static void
setupuline(LINEPTR lp)
{
    register LINE *ulp;
    /* take care of the U line */
    if ((ulp = curbp->b_ulinep) == NULL
	|| (ulp->l_nxtundo != lp)) {
	if (ulp != NULL)
	    lfree(curbp->b_ulinep, curbp);
	ulp = curbp->b_ulinep = copyline(lp);
	if (ulp != NULL)
	    ulp->l_nxtundo = lp;
    }
}

void
dumpuline(LINEPTR lp)
{
    register LINE *ulp = curbp->b_ulinep;

    if ((ulp != NULL) && (ulp->l_nxtundo == lp)) {
	lfree(curbp->b_ulinep, curbp);
	curbp->b_ulinep = null_ptr;
    }
}

/* ARGSUSED */
int
lineundo(int f GCC_UNUSED, int n GCC_UNUSED)
{
    register LINE *ulp;		/* the Undo line */
    register LINE *lp;		/* the line we may replace */
    register WINDOW *wp;
    register char *ntext;

    ulp = curbp->b_ulinep;
    if (ulp == NULL) {
	kbd_alarm();
	return FALSE;
    }

    lp = ulp->l_nxtundo;

    if (lforw(ulp) != lforw(lp) ||
	lback(ulp) != lback(lp)) {
	/* then the last change affected more than one line,
	   and we can't use the saved U-line */
	dumpuline(curbp->b_ulinep);
	kbd_alarm();
	return FALSE;
    }

    /* avoid losing our undo stacks needlessly */
    if (linesmatch(ulp, lp) == TRUE)
	return TRUE;

    DOT.l = lp;

    preundocleanup();

    ntext = NULL;
    if (ulp->l_size && (ntext = typeallocn(char, ulp->l_size)) == NULL)
	  return (FALSE);

    copy_for_undo(lp);

    if (ntext && lp->l_text) {
	(void) memcpy(ntext, ulp->l_text, (size_t) llength(ulp));
	ltextfree(lp, curbp);
    }

    lp->l_text = ntext;
    lp->l_used = ulp->l_used;
    lp->l_size = ulp->l_size;

#if ! WINMARK
    if (MK.l == lp)
	MK.o = b_left_margin(curbp);
#endif
    /* let's be defensive about this */
    for_each_window(wp) {
	if (wp->w_dot.l == lp)
	    wp->w_dot.o = b_left_margin(curbp);
#if WINMARK
	if (wp->w_mark.l == lp)
	    wp->w_mark.o = b_left_margin(curbp);
#endif
	if (wp->w_lastdot.l == lp)
	    wp->w_lastdot.o = b_left_margin(curbp);
    }
    do_mark_iterate(mp, {
	if (mp->l == lp)
	    mp->o = b_left_margin(curbp);
    });
#if OPT_LINE_ATTRS
    FreeAndNull(lp->l_attrs);
#endif

    chg_buff(curbp, WFEDIT | WFKILLS | WFINS);

    return TRUE;

}

#define _min(a,b) ((a) < (b)) ? (a) : (b)

static void
repointstuff(register LINEPTR nlp, register LINEPTR olp)
{
    register WINDOW *wp;
    int usenew = lisreal(nlp);
    register LINEPTR point;
    register LINE *ulp;

    point = usenew ? nlp : lforw(olp);
#if ! WINMARK
    if (MK.l == olp) {
	MK.l = point;
	MK.o = _min(MK.o, llength(point));
    }
#endif
    /* fix anything important that points to it */
    for_each_window(wp) {
	if (wp->w_dot.l == olp) {
	    wp->w_dot.l = point;
	    wp->w_dot.o = _min(wp->w_dot.o, llength(point));
	}
	if (wp->w_line.l == olp)
	    wp->w_line.l = point;
#if WINMARK
	if (wp->w_mark.l == olp) {
	    wp->w_mark.l = point;
	    wp->w_mark.o = _min(wp->w_mark.o, llength(point));
	}
#endif
	if (wp->w_lastdot.l == olp) {
	    wp->w_lastdot.l = point;
	    wp->w_lastdot.o = _min(wp->w_lastdot.o, llength(point));
	}
    }
    do_mark_iterate(mp, {
	if (mp->l == olp) {
	    mp->l = point;
	    mp->o = _min(mp->o, llength(point));
	}
    });
#if OPT_LINE_ATTRS
    FreeAndNull(olp->l_attrs);
#endif

    /* reset the uline */
    if ((ulp = curbp->b_ulinep) != NULL
	&& (ulp->l_nxtundo == olp)) {
	if (usenew) {
	    ulp->l_nxtundo = point;
	} else {
	    /* we lose the ability to undo all changes
	       to this line, since it's going away */
	    curbp->b_ulinep = null_ptr;
	}
    }

}

static int
linesmatch(register LINE *lp1, register LINE *lp2)
{
    if (llength(lp1) != llength(lp2))
	return FALSE;
    if (llength(lp1) == 0)
	return TRUE;
    return !memcmp(lp1->l_text, lp2->l_text, (size_t) llength(lp1));
}

int
undo_ok(void)
{
    LINE **lp = STACK(BACK);

    return (*lp != NULL);
}

int
redo_ok(void)
{
    LINE **lp = STACK(FORW);

    return (*lp != NULL);
}
