/*
 * undo.c --
 *
 * Undo/redo module.
 *
 * The undo package records a series of invertible editing events
 * in a log maintained in main memory.
 *
 * The current state may be rewound back toward the time the editing
 * session began, and it may be replayed forward as well.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-7.5/utils/undo.c,v 1.1.1.1 2006/04/10 22:03:14 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <sys/types.h>

#include "utils/magic.h"
#include "utils/utils.h"
#include "utils/malloc.h"
#include "utils/undo.h"

/* ------------------------------------------------------------------------ */

/*
 * CONFIGURATION INFORMATION
 */

#define	MAXUNDOCLIENTS	50	/* Maximum number of calls to UndoAddClient */

    /*
     * MAXCOMMANDS is the maximum number of delimited event sequences
     * ("commands") retained in main memory.  LOWCOMMANDS is a low-water
     * mark for the number of commands in memory; whenever we free up
     * commands, we do so until there are no more than LOWCOMMANDS in
     * main memory.
     *
     * >>>>> In the current implementation, these two are the same <<<<<
     */

#define	MAXCOMMANDS	1000
#define	LOWCOMMANDS	1000	/* Must be > 0 ! */

/* ------------------------------------------------------------------------ */

/*
 * The following structure describes the basic information
 * required by the undo package for each event it stores.
 * This information is NOT intended to be visible to any of
 * the clients of the undo package and is susceptible to being
 * changed arbitrarily.
 *
 * To enforce the absolute ignorance of undo's clients, when
 * we allocate an internalUndoEvent, we only give the client
 * a pointer to the iue_client part, which is of a size determined
 * by the client when it calls UndoNewEvent().  This pointer is
 * what the client sees as an (UndoEvent *) (really a (char *)).
 */

    typedef struct ue
    {
	UndoType	 iue_type;	/* Event type */
	struct ue	*iue_back;	/* Previous event on list */
	struct ue	*iue_forw;	/* Next event on list */
	int		 iue_client;	/* Client data area.  This is merely a
					 * dummy placeholder; the actual size
					 * of one of these structures is
					 * determined at the time of
					 * UndoNewEvent().
					 */
    } internalUndoEvent;

#define	UT_DELIM	(-1)

/*
 * The following macro is used to compute the number of bytes we must
 * allocate in order to give the user an UndoEvent capable of holding
 * n bytes.
 */

#define	undoSize(n)	(sizeof (struct ue) + (n) \
			    - sizeof (((struct ue *) 0)->iue_client))

/*
 * Mapping between internal and external undo event pointers.
 * When the undo package hands an (UndoEvent *) to a client, it is
 * really a pointer to the iue_client part of the structure
 * above.
 */

#define	CLIENTOFFSET	((int) &((internalUndoEvent) 0)->iue_client)
#define	undoExport(p)	((UndoEvent *) (&(p)->iue_client))
#define	undoImport(p)	((internalUndoEvent) (((char *) (p)) - CLIENTOFFSET))

/*
 * The following table is used to record the information about clients
 * of the undo package.  The number of such clients is stored in
 * undoNumClients.
 */

    typedef struct
    {
	char		  *uc_name;	/* Name (for error messages) */
	void		 (*uc_init)();	/* Called before playing log */
	void		 (*uc_done)();	/* Called after playing log */
	void		 (*uc_forw)();	/* Play event forward */
	void		 (*uc_back)();	/* Play event backward */
    } undoClient;

undoClient undoClientTable[MAXUNDOCLIENTS];
int undoNumClients = 0;

/*
 * undoState is used by UndoNewEvent() to figure out the context from which
 *	it was called in order that it may link the newly allocated event
 *	in the appropriate way.  Events are only linked in when the state
 *	is US_APPEND.  The only reason for having two other states instead
 *	of one is for ease in debugging.
 * UndoDisableCount counts the number of times UndoDisable() has been called.
 */

#define	US_APPEND	0	/* Normal state: appending to log */
#define	US_FORWARD	1	/* Playing log forward */
#define	US_BACKWARD	2	/* Playing log backward */

int undoState = US_APPEND;
global int UndoDisableCount = 0;

/*
 * Log of events kept in main memory.
 *
 *	undoLogHead	Pointer to first entry stored in main memory.
 *			    - NULL, indicating no events are in memory
 *			    - a pointer to the first event of a command
 *	undoLogTail	Pointer to last entry stored in main memory.
 *			    - Undefined (if undoLogHead == NULL)
 *			    - a pointer to a UT_DELIM event if
 *			      undoNumRecentEvents == 0
 *			    - a pointer to a non-UT_DELIM event if
 *			      undoNumRecentEvents != 0
 *	undoLogCur	Pointer to "current" event, ie, one after which
 *			next event will be added.
 *			    - NULL if at beginning of event list
 *			    - a pointer to a UT_DELIM event if
 *			      undoNumRecentEvents == 0
 *			    - a pointer to a non-UT_DELIM event if
 *			      undoNumRecentEvents != 0
 *
 *	undoNumRecentEvents
 *			Number of events written since last call to
 *			UndoNext().
 *	undoNumCommands
 *			Number of complete commands in main memory.
 */

internalUndoEvent *undoLogCur;
internalUndoEvent *undoLogHead;
internalUndoEvent *undoLogTail;
int undoNumRecentEvents;
int undoNumCommands;

/*
 * ============================================================================
 *
 *	The following collection of procedures completely defines
 *	the interface the undo package presents to its clients.
 *
 * ============================================================================
 */

extern internalUndoEvent *undoGetForw();
extern internalUndoEvent *undoGetBack();
extern void undoFreeHead();
extern void undoMemTruncate();

/*
 * ----------------------------------------------------------------------------
 *
 * UndoInit --
 *
 * Initialize the undo package.
 * Opens the given file as an undo log.
 * One of several modes may be specified.  In all cases, the log file
 * is opened so that playback will commence from the beginning of the
 * file.
 *
 *	"r"	The log file is opened for playback only; no new events
 *		may be played out to the file.
 *		This mode is intended for use in recovering from a crash
 *		without clobbering the crash log.
 *
 *	"w"	The log file is created afresh and is available to the
 *		undo package for use in logging events.
 *		This is the normal mode for starting a new session.
 *
 *	"rw"	The log file is opened for playback and logging.
 *		Any new events written before the entire log has played
 *		forward will cause the remaining events in the log to be
 *		truncated.
 *		This is the normal mode for crash recovery.
 *
 * Results:
 *	TRUE if the log file could be successfully created/opened, FALSE
 *	if an error was encountered.  In the latter case, the external
 *	variable errno is set to the UNIX error encountered.
 *
 * Side effects:
 *	Initializes all the variables in the undo package.
 *
 * ----------------------------------------------------------------------------
 */

bool
UndoInit(logFileName, mode)
    char *logFileName;	/* Name of log file.  This may contain tilde
			 * abbreviations.
			 */
    char *mode;		/* Mode for opening.  Must be "r", "rw", or "w" */
{
    UndoDisableCount = 0;
    undoLogTail = NULL;
    undoLogCur = NULL;
    undoNumRecentEvents = 0;
    undoNumCommands = 0;

    /*
     * Deallocate any events stored in main memory
     */

    while (undoLogHead != (internalUndoEvent *) NULL)
    {
	freeMagic((char *) undoLogHead);
	undoLogHead = undoLogHead->iue_forw;
    }

    return (TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoAddClient --
 *
 * Used by a client to make itself known to the undo package.
 * The client must supply the following information:
 *
 *	1. A procedure to call before starting an undo or redo
 *	   operation.
 *
 *	2. A procedure to call upon completion of an undo or
 *	   redo operation.
 *
 *	3. A procedure to parse a line in the undo log file and
 *	   return a filled-in UndoEvent (allocated, of course,
 *	   by a call to UndoNewEvent()) that is the internal
 *	   representation of the event externally represented
 *	   by the supplied line of text from the log file.
 *
 *	   UndoEvent *
 *	   readEvent(line)
 *	       char *line;
 *	   {
 *	   }
 *
 *	4. A procedure to fill in a line (null-terminated with
 *	   no newlines embedded or trailing) with the external
 *	   representation of an event based on an UndoEvent
 *	   supplied to it.  This prodedure returns the number
 *	   of bytes it stored in the line (which must be less
 *	   than a constant, UNDOLINESIZE).
 *
 *	   int
 *	   writeEvent(event, line)
 *	       UndoEvent *event;
 *	       char *line;
 *	   {
 *	   }
 *
 * If either readEvent or writeEvent are NULL, a default procedure
 * is used which simply uses the same internal and external representation
 * for the undo event (ie, a text string).
 *
 *	5. A procedure to play an event forward.
 *
 *	   void
 *	   forwEvent(event)
 *	       UndoEvent *event;
 *	   {
 *	   }
 *
 *	6. A procedure to play an event backward.
 *
 *	   void
 *	   backEvent(event)
 *	       UndoEvent *event;
 *	   {
 *	   }
 *
 *	7. A client name, for error messages.  This is a character
 *	   string.
 *
 * Results:
 *	Returns an UndoType which must be passed in future calls
 *	to UndoNewEvent().  If -1 is returned, this means that there
 *	are too many clients of the undo package.
 *
 * Side effects:
 *	Initializes local state in the undo package.
 *
 * ----------------------------------------------------------------------------
 */

UndoType
UndoAddClient(init, done, readEvent, writeEvent, forwEvent, backEvent, name)
    void (*init)();
    void (*done)();
    UndoEvent *(*readEvent)();
    int (*writeEvent)();
    void (*forwEvent)(), (*backEvent)();
    char *name;
{
    if (undoNumClients >= MAXUNDOCLIENTS)
	return ((UndoType) -1);

    undoClientTable[undoNumClients].uc_name = StrDup((char **) NULL, name);
    undoClientTable[undoNumClients].uc_forw = forwEvent;
    undoClientTable[undoNumClients].uc_back = backEvent;
    undoClientTable[undoNumClients].uc_init = init;
    undoClientTable[undoNumClients].uc_done = done;

    return (undoNumClients++);
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoFlush --
 *
 * Flush the current undo list.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Deletes everything from the undo list.
 *
 * ----------------------------------------------------------------------------
 */

void
UndoFlush()
{
    if (undoLogHead == (internalUndoEvent *) NULL)
	return;

    while (undoLogTail != undoLogHead)
    {
	freeMagic((char *) undoLogTail);
	undoLogTail = undoLogTail->iue_back;
	ASSERT(undoLogTail != (internalUndoEvent *) NULL, "UndoFlush");
    }
    freeMagic((char *) undoLogHead);

    undoLogHead = undoLogTail = undoLogCur = (internalUndoEvent *) NULL;
    undoNumCommands = 0;
    undoNumRecentEvents = 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoDisable --
 *
 * Turn the undo package off.
 * Future calls to UndoNewEvent() will return NULL, and future calls
 * to UndoIsEnabled() will return FALSE, until the next call to
 * UndoEnable();
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Disables undoing until the next call to UndoEnable().
 *
 * ----------------------------------------------------------------------------
 */

void
UndoDisable()
{
    UndoDisableCount++;
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoEnable --
 *
 * Turn the undo package on.
 * Re-enables the undo package after a call to UndoDisable().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Re-enables undoing.
 *
 * ----------------------------------------------------------------------------
 */

void
UndoEnable()
{
    if (UndoDisableCount > 0)
	UndoDisableCount--;
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoNewEvent --
 *
 * Return a pointer to a new UndoEvent of the specified type and capable
 * of holding size bytes of client data.
 *
 * Results:
 *	A pointer to a new UndoEvent.
 *
 * WARNING:
 *	The pointer to the new UndoEvent must not be retained past the
 *	next call to any of the routines in the undo package, as the
 *	event is liable to be reallocated.
 *
 * Side effects:
 *	Appends the event read to the undo list in the appropriate place,
 *	depending on the context from which it was called.  If called by
 *	a client under normal circumstances, appends the event to the end
 * 	of the undo log.  If called by the readEvent() procedure during
 *	replaying of the undo log, places the event at whichever end of
 *	the log is appropriate to the operation (back/forw) being performed.
 *
 * ----------------------------------------------------------------------------
 */

UndoEvent *
UndoNewEvent(clientType, size)
    UndoType clientType;	/* Type of event to allocate */
    unsigned int size;		/* Number of bytes of client data to allocate */
{
    internalUndoEvent *iup;
    int usize;

    if (UndoDisableCount > 0)
	return ((UndoEvent *) NULL);

    usize = undoSize(size);
    iup = (internalUndoEvent *) mallocMagic((unsigned) usize);
    ASSERT(clientType >= 0 && clientType < undoNumClients, "UndoNewEvent");
    iup->iue_type = clientType;
    if (undoState == US_APPEND)
    {
	/*
	 * Normal state:
	 * Append the new event after the event pointed to by
	 * undoLogCur.
	 */
	iup->iue_forw = (internalUndoEvent *) NULL;
	iup->iue_back = undoLogCur;
	if (undoLogCur == (internalUndoEvent *) NULL)
	{
	    if (undoLogHead != (internalUndoEvent *) NULL)
		undoMemTruncate();
	    undoLogHead = undoLogCur = undoLogTail = iup;
	}
	else
	{
	    if (undoLogCur->iue_forw != (internalUndoEvent *) NULL)
		undoMemTruncate();
	    undoLogCur->iue_forw = iup;
	    undoLogCur = undoLogTail = iup;
	}
	undoNumRecentEvents++;
    }

    return (undoExport(iup));
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoNext --
 *
 * Delimit a sequence of operations to the undo package with an event
 * delimiter.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Appends a marker to the undo list signifying the boundary of
 *	a unit of events.  UndoBackward() and UndoForward() operate in
 *	terms of complete units of this sort.
 *
 * ----------------------------------------------------------------------------
 */

void
UndoNext()
{
    internalUndoEvent *iup;
    int usize;

    if (UndoDisableCount > 0 || undoNumRecentEvents == 0)
	return;

    undoNumRecentEvents = 0;
    undoNumCommands++;
    usize = undoSize(0);
    iup = (internalUndoEvent *) mallocMagic((unsigned) usize);
    iup->iue_type = UT_DELIM;
    iup->iue_back = undoLogTail;
    iup->iue_forw = (internalUndoEvent *) NULL;
    if (undoLogTail != (internalUndoEvent *) NULL)
	undoLogTail->iue_forw = iup;
    undoLogCur = undoLogTail = iup;
    if (undoNumCommands >= MAXCOMMANDS)
	undoFreeHead();
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoBackward --
 *
 * Play the undo log backward n events.
 *
 * Results:
 *	The number of events actually played backward.  Normally, this
 *	will be equal to n unless we encounter the beginning of the log.
 *
 * Side effects:
 *	Applies the client backEvent() procedures to each event encountered
 *	in playing the log backward.
 *
 * ----------------------------------------------------------------------------
 */

int
UndoBackward(n)
    int n;		/* Number of events to unplay */
{
    internalUndoEvent *iup;
    int client, count;

#ifdef MAGIC_WRAPPER
    /* This condition appears to happen just prior to a	program	*/
    /* crash, apparently due to a race condition which is	*/
    /* difficult to reproduce.  Tcl_DoOneEvent() inside the	*/
    /* DRCContinuous routine is implicated.  Can't find the	*/
    /* error source, but refusing to execute the undo command	*/
    /* appears to prevent it.					*/
 
    if (UndoDisableCount > 0)
    {
	TxError("Attempted undo with undo disabled. . . abort function.\n");
	return 0;
    }
#endif

    /* Call the initialization routines of all clients */
    for (client = 0; client < undoNumClients; client++)
	if (undoClientTable[client].uc_init)
	    (*undoClientTable[client].uc_init)();

    iup = undoLogCur;
    undoNumRecentEvents = 0;
    UndoDisableCount++;
    for (count = 0; (count < n) && (iup != NULL); count++)
    {
	do
	{
	    if (iup->iue_type != UT_DELIM)
		if (undoClientTable[iup->iue_type].uc_back != NULL)
		    (*undoClientTable[iup->iue_type].uc_back)(undoExport(iup));

	    /* fprintf(stderr, "Undo record: type=%d back=0x%x, forw=0x%x\n",
	     *	iup->iue_type, iup->iue_back, iup->iue_forw);
	     * fprintf(stderr, "   UndoDisableCount = %d, undoNumRecentEvents = %d\n",
	     *		UndoDisableCount, undoNumRecentEvents);
	     * fflush(stderr);
	     */

	    iup = undoGetBack(iup);
	}
	while ((iup != (internalUndoEvent *) NULL) && (iup->iue_type != UT_DELIM));
    }
    UndoDisableCount--;

    undoLogCur = iup;

    /* fprintf(stderr, "UndoBackward: undoLogCur set to 0x%x\n", undoLogCur);
     * fflush(stderr);
     */

    /* Call the termination routines of all clients */
    for (client = 0; client < undoNumClients; client++)
	if (undoClientTable[client].uc_done)
	    (*undoClientTable[client].uc_done)();
    return (count);
}

/*
 * ----------------------------------------------------------------------------
 *
 * UndoForward --
 *
 * Play the undo log forward n events.
 *
 * Results:
 *	The number of events actually played forward.  Normally, this
 *	will be equal to n unless we encounter the end of the log.
 *
 * Side effects:
 *	Applies the client forwEvent() procedures to each event encountered
 *	in playing the log forward.
 *
 * ----------------------------------------------------------------------------
 */

int
UndoForward(n)
    int n;		/* Number of events to replay */
{
    internalUndoEvent *iup;
    int count, client;

    /* Call the initialization routines of all clients */
    for (client = 0; client < undoNumClients; client++)
	if (undoClientTable[client].uc_init)
	    (*undoClientTable[client].uc_init)();

    count = 0;
    iup = undoGetForw(undoLogCur);
    if (iup == NULL) goto done;

    undoNumRecentEvents = 0;
    UndoDisableCount++;
    for ( ; count < n; count++)
    {
	do
	{
	    if (iup->iue_type != UT_DELIM)
		if (undoClientTable[iup->iue_type].uc_forw != NULL)
		    (*undoClientTable[iup->iue_type].uc_forw)(undoExport(iup));
	    iup = undoGetForw(iup);
	}
	while (iup != (internalUndoEvent *) NULL && iup->iue_type != UT_DELIM);
	if (iup == (internalUndoEvent *) NULL)
	{
	    iup = undoLogTail;
	    break;
	}
    }
    UndoDisableCount--;

    undoLogCur = iup;

done:
    /* Call the termination routines of all clients */
    for (client = 0; client < undoNumClients; client++)
	if (undoClientTable[client].uc_done)
	    (*undoClientTable[client].uc_done)();
    return (count);
}

/*
 * ============================================================================
 *
 *	All of the remaining procedures in the file are invisible
 *	to the clients of the undo package and should not be used.
 *
 * ============================================================================
 */


/*
 * ----------------------------------------------------------------------------
 *
 * undoGetForw --
 *
 * Return a pointer to the next undo event in the list.
 *
 * Results:
 *	A pointer to an undo event.
 *
 * Side effects:
 *	None.
 *
 * Directly modifies:
 *	undoLogHead, undoLogTail
 *	undoNumCommands
 *	undoNumRecentEvents = 0
 *
 * Indirectly modifies:
 *	Nothing.
 *
 * ----------------------------------------------------------------------------
 */

internalUndoEvent *
undoGetForw(iup)
    internalUndoEvent *iup;
{
    if (iup != (internalUndoEvent *) NULL)
    {
	/*
	 * Return the next event in memory if there is one.
	 */
	if (iup->iue_forw != (internalUndoEvent *) NULL)
	    return (iup->iue_forw);
    }
    else
    {
	/*
	 * A NULL initial iup means to start at the very beginning of
	 * the main-memory undo list.  If there is anything there, return
	 * it; otherwise return NULL.
	 */
	if (undoLogHead != (internalUndoEvent *) NULL)
	    return (undoLogHead);
    }

    return ((internalUndoEvent *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * undoGetBack --
 *
 * Return a pointer to the previous undo event in the list.
 *
 * Results:
 *	A pointer to an undo event.
 *
 * Side effects:
 *	None.
 *
 * Directly modifies:
 *
 * Indirectly modifies:
 *
 * ----------------------------------------------------------------------------
 */

internalUndoEvent *
undoGetBack(iup)
    internalUndoEvent *iup;
{
    if (iup == (internalUndoEvent *) NULL) return (iup);
    if (iup->iue_back != (internalUndoEvent *) NULL) return (iup->iue_back);
    return ((internalUndoEvent *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * undoFreeHead --
 *
 * Free up space by throwing away events from the front of the in-memory
 * event list until the total number of in-memory commands falls below
 * LOWCOMMANDS
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Deallocates events from the front of the in-memory event list.
 *	Updates undoLogHead, undoNumCommands.
 *	Guaranteed to leave undoLogHead pointing to the first event
 *	in a command (not of type UT_DELIM).
 *
 * WARNING:
 *	It is important that undoLogCur point beyond the region
 *	to be freed.  Also, it is important that the in-core list
 *	be terminated by an UT_DELIM event.
 *
 * ----------------------------------------------------------------------------
 */

void
undoFreeHead()
{
    if (undoNumCommands <= LOWCOMMANDS)
	return;

    while (undoNumCommands > LOWCOMMANDS)
    {
	do
	{
	    ASSERT(undoLogHead != undoLogCur, "undoFreeHead");
	    freeMagic((char *) undoLogHead);
	    undoLogHead = undoLogHead->iue_forw;
	    ASSERT(undoLogHead != (internalUndoEvent *) NULL, "undoFreeHead");
	}
	while (undoLogHead->iue_type != UT_DELIM);
	undoNumCommands--;
    }
    freeMagic((char *) undoLogHead);
    undoLogHead = undoLogHead->iue_forw;
    undoLogHead->iue_back = (internalUndoEvent *) NULL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * undoMemTruncate --
 *
 * Delete events in memory which are later than the current event.
 * NOTE: This expects to be called only when undoNumRecentEvents == 0.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Truncates the event list so there are no events past undoLogCur.
 *
 *	Updates undoLogTail.
 *
 * ----------------------------------------------------------------------------
 */

void
undoMemTruncate()
{
    internalUndoEvent *up;

    /*
     * If there are events forward of the current event that
     * will get overwritten by the new event, delete them from
     * memory.
     */

    if (undoLogCur == (internalUndoEvent *) NULL)
    {
	/*
	 * Delete ALL events from memory
	 */
	up = undoLogHead;
	while (up != (internalUndoEvent *) NULL)
	{
	    freeMagic((char *) up);
	    up = up->iue_forw;
	}
	undoLogTail = undoLogHead = (internalUndoEvent *) NULL;
	undoNumCommands = 0;
    }
    else
    {
	ASSERT(undoLogCur->iue_type == UT_DELIM, "undoMemTruncate");
	/*
	 * Delete only some of the events in main memory.
	 */
	up = undoLogCur->iue_forw;
	while (up != (internalUndoEvent *) NULL)
	{
	    if (up->iue_type == UT_DELIM)
		undoNumCommands--;
	    freeMagic((char *) up);
	    up = up->iue_forw;
	}
	undoLogCur->iue_forw = (internalUndoEvent *) NULL;
	undoLogTail = undoLogCur;
    }

}

/*
 * ============================================================================
 *
 *			    DEBUGGING PROCEDURES
 *
 * ============================================================================
 */

void
undoPrintEvent(iup)
    internalUndoEvent *iup;
{
    char *client_name;
    if (iup->iue_type < 0)
	client_name = "(delimiter)";
    else
	client_name = undoClientTable[iup->iue_type].uc_name;

    (void) TxPrintf("0x%x: \t%s \tf=0x%x \tb=0x%x\n",
		iup, client_name, iup->iue_forw, iup->iue_back);
}

/* Print events forward from "iup".  If n is 0 or negative, print to	*/
/* the end of the stack.  Otherwise, print the next n events.		*/

void
undoPrintForw(iup, n)
    internalUndoEvent *iup;
    int n;
{
    int i = 0;

    (void) TxPrintf("head=0x%x\ttail=0x%x\tcur=0x%x\n",
		undoLogHead, undoLogTail, undoLogCur);
    if (iup == (internalUndoEvent *) NULL)
	iup = undoLogHead;
    while (iup != (internalUndoEvent *) NULL)
    {
	undoPrintEvent(iup);
	iup = iup->iue_forw;
	i++;
	if (i == n) break;
    }
}

/* Print events backward from "iup".  If n is 0 or negative, print to	*/
/* the beginning of the stack.  Otherwise, print the previous n events.	*/

void
undoPrintBack(iup, n)
    internalUndoEvent *iup;
    int n;
{
    int i = 0;

    (void) TxPrintf("head=0x%x\ttail=0x%x\tcur=0x%x\n",
		undoLogHead, undoLogTail, undoLogCur);
    if (iup == (internalUndoEvent *) NULL)
	iup = undoLogTail;
    while (iup != (internalUndoEvent *) NULL)
    {
	undoPrintEvent(iup);
	iup = iup->iue_back;
	i++;
	if (i == n) break;
    }
}

/* Called from windUndoCmd() or windRedoCmd() with n either positive, 	*/
/* or negative with 1 offset to differentiate between n = 0 (forward)	*/
/* and n = 0 (backward).						*/

void
UndoStackTrace(n)
    int n;
{
    if (n < 0)
       undoPrintBack(undoLogCur, -(n + 1));
    else
       undoPrintForw(undoLogCur, n);
}

