1 /*
2  * undo.c --
3  *
4  * Undo/redo module.
5  *
6  * The undo package records a series of invertible editing events
7  * in a log maintained in main memory.
8  *
9  * The current state may be rewound back toward the time the editing
10  * session began, and it may be replayed forward as well.
11  *
12  *     *********************************************************************
13  *     * Copyright (C) 1985, 1990 Regents of the University of California. *
14  *     * Permission to use, copy, modify, and distribute this              *
15  *     * software and its documentation for any purpose and without        *
16  *     * fee is hereby granted, provided that the above copyright          *
17  *     * notice appear in all copies.  The University of California        *
18  *     * makes no representations about the suitability of this            *
19  *     * software for any purpose.  It is provided "as is" without         *
20  *     * express or implied warranty.  Export of this software outside     *
21  *     * of the United States of America may require an export license.    *
22  *     *********************************************************************
23  */
24 
25 #ifndef lint
26 static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/utils/undo.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
27 #endif  /* not lint */
28 
29 #include <stdio.h>
30 #include <sys/types.h>
31 
32 #include "utils/magic.h"
33 #include "utils/utils.h"
34 #include "utils/malloc.h"
35 #include "utils/undo.h"
36 
37 /* ------------------------------------------------------------------------ */
38 
39 /*
40  * CONFIGURATION INFORMATION
41  */
42 
43 #define	MAXUNDOCLIENTS	50	/* Maximum number of calls to UndoAddClient */
44 
45     /*
46      * MAXCOMMANDS is the maximum number of delimited event sequences
47      * ("commands") retained in main memory.  LOWCOMMANDS is a low-water
48      * mark for the number of commands in memory; whenever we free up
49      * commands, we do so until there are no more than LOWCOMMANDS in
50      * main memory.
51      *
52      * >>>>> In the current implementation, these two are the same <<<<<
53      */
54 
55 #define	MAXCOMMANDS	1000
56 #define	LOWCOMMANDS	1000	/* Must be > 0 ! */
57 
58 /* ------------------------------------------------------------------------ */
59 
60 /*
61  * The following structure describes the basic information
62  * required by the undo package for each event it stores.
63  * This information is NOT intended to be visible to any of
64  * the clients of the undo package and is susceptible to being
65  * changed arbitrarily.
66  *
67  * To enforce the absolute ignorance of undo's clients, when
68  * we allocate an internalUndoEvent, we only give the client
69  * a pointer to the iue_client part, which is of a size determined
70  * by the client when it calls UndoNewEvent().  This pointer is
71  * what the client sees as an (UndoEvent *) (really a (char *)).
72  */
73 
74     typedef struct ue
75     {
76 	UndoType	 iue_type;	/* Event type */
77 	struct ue	*iue_back;	/* Previous event on list */
78 	struct ue	*iue_forw;	/* Next event on list */
79 	int		 iue_client;	/* Client data area.  This is merely a
80 					 * dummy placeholder; the actual size
81 					 * of one of these structures is
82 					 * determined at the time of
83 					 * UndoNewEvent().
84 					 */
85     } internalUndoEvent;
86 
87 #define	UT_DELIM	(-1)
88 
89 /*
90  * The following macro is used to compute the number of bytes we must
91  * allocate in order to give the user an UndoEvent capable of holding
92  * n bytes.
93  */
94 
95 #define	undoSize(n)	(sizeof (struct ue) + (n) \
96 			    - sizeof (((struct ue *) 0)->iue_client))
97 
98 /*
99  * Mapping between internal and external undo event pointers.
100  * When the undo package hands an (UndoEvent *) to a client, it is
101  * really a pointer to the iue_client part of the structure
102  * above.
103  */
104 
105 #define	CLIENTOFFSET	((int) &((internalUndoEvent) 0)->iue_client)
106 #define	undoExport(p)	((UndoEvent *) (&(p)->iue_client))
107 #define	undoImport(p)	((internalUndoEvent) (((char *) (p)) - CLIENTOFFSET))
108 
109 /*
110  * The following table is used to record the information about clients
111  * of the undo package.  The number of such clients is stored in
112  * undoNumClients.
113  */
114 
115     typedef struct
116     {
117 	char		  *uc_name;	/* Name (for error messages) */
118 	void		 (*uc_init)();	/* Called before playing log */
119 	void		 (*uc_done)();	/* Called after playing log */
120 	void		 (*uc_forw)();	/* Play event forward */
121 	void		 (*uc_back)();	/* Play event backward */
122     } undoClient;
123 
124 undoClient undoClientTable[MAXUNDOCLIENTS];
125 int undoNumClients = 0;
126 
127 /*
128  * undoState is used by UndoNewEvent() to figure out the context from which
129  *	it was called in order that it may link the newly allocated event
130  *	in the appropriate way.  Events are only linked in when the state
131  *	is US_APPEND.  The only reason for having two other states instead
132  *	of one is for ease in debugging.
133  * UndoDisableCount counts the number of times UndoDisable() has been called.
134  */
135 
136 #define	US_APPEND	0	/* Normal state: appending to log */
137 #define	US_FORWARD	1	/* Playing log forward */
138 #define	US_BACKWARD	2	/* Playing log backward */
139 
140 int undoState = US_APPEND;
141 global int UndoDisableCount = 0;
142 
143 /*
144  * Log of events kept in main memory.
145  *
146  *	undoLogHead	Pointer to first entry stored in main memory.
147  *			    - NULL, indicating no events are in memory
148  *			    - a pointer to the first event of a command
149  *	undoLogTail	Pointer to last entry stored in main memory.
150  *			    - Undefined (if undoLogHead == NULL)
151  *			    - a pointer to a UT_DELIM event if
152  *			      undoNumRecentEvents == 0
153  *			    - a pointer to a non-UT_DELIM event if
154  *			      undoNumRecentEvents != 0
155  *	undoLogCur	Pointer to "current" event, ie, one after which
156  *			next event will be added.
157  *			    - NULL if at beginning of event list
158  *			    - a pointer to a UT_DELIM event if
159  *			      undoNumRecentEvents == 0
160  *			    - a pointer to a non-UT_DELIM event if
161  *			      undoNumRecentEvents != 0
162  *
163  *	undoNumRecentEvents
164  *			Number of events written since last call to
165  *			UndoNext().
166  *	undoNumCommands
167  *			Number of complete commands in main memory.
168  */
169 
170 internalUndoEvent *undoLogCur;
171 internalUndoEvent *undoLogHead;
172 internalUndoEvent *undoLogTail;
173 int undoNumRecentEvents;
174 int undoNumCommands;
175 
176 /*
177  * ============================================================================
178  *
179  *	The following collection of procedures completely defines
180  *	the interface the undo package presents to its clients.
181  *
182  * ============================================================================
183  */
184 
185 extern internalUndoEvent *undoGetForw();
186 extern internalUndoEvent *undoGetBack();
187 extern void undoFreeHead();
188 extern void undoMemTruncate();
189 
190 /*
191  * ----------------------------------------------------------------------------
192  *
193  * UndoInit --
194  *
195  * Initialize the undo package.
196  * Opens the given file as an undo log.
197  * One of several modes may be specified.  In all cases, the log file
198  * is opened so that playback will commence from the beginning of the
199  * file.
200  *
201  *	"r"	The log file is opened for playback only; no new events
202  *		may be played out to the file.
203  *		This mode is intended for use in recovering from a crash
204  *		without clobbering the crash log.
205  *
206  *	"w"	The log file is created afresh and is available to the
207  *		undo package for use in logging events.
208  *		This is the normal mode for starting a new session.
209  *
210  *	"rw"	The log file is opened for playback and logging.
211  *		Any new events written before the entire log has played
212  *		forward will cause the remaining events in the log to be
213  *		truncated.
214  *		This is the normal mode for crash recovery.
215  *
216  * Results:
217  *	TRUE if the log file could be successfully created/opened, FALSE
218  *	if an error was encountered.  In the latter case, the external
219  *	variable errno is set to the UNIX error encountered.
220  *
221  * Side effects:
222  *	Initializes all the variables in the undo package.
223  *
224  * ----------------------------------------------------------------------------
225  */
226 
227 bool
UndoInit(logFileName,mode)228 UndoInit(logFileName, mode)
229     char *logFileName;	/* Name of log file.  This may contain tilde
230 			 * abbreviations.
231 			 */
232     char *mode;		/* Mode for opening.  Must be "r", "rw", or "w" */
233 {
234     UndoDisableCount = 0;
235     undoLogTail = NULL;
236     undoLogCur = NULL;
237     undoNumRecentEvents = 0;
238     undoNumCommands = 0;
239 
240     /*
241      * Deallocate any events stored in main memory
242      */
243 
244     while (undoLogHead != (internalUndoEvent *) NULL)
245     {
246 	freeMagic((char *) undoLogHead);
247 	undoLogHead = undoLogHead->iue_forw;
248     }
249 
250     return (TRUE);
251 }
252 
253 /*
254  * ----------------------------------------------------------------------------
255  *
256  * UndoAddClient --
257  *
258  * Used by a client to make itself known to the undo package.
259  * The client must supply the following information:
260  *
261  *	1. A procedure to call before starting an undo or redo
262  *	   operation.
263  *
264  *	2. A procedure to call upon completion of an undo or
265  *	   redo operation.
266  *
267  *	3. A procedure to parse a line in the undo log file and
268  *	   return a filled-in UndoEvent (allocated, of course,
269  *	   by a call to UndoNewEvent()) that is the internal
270  *	   representation of the event externally represented
271  *	   by the supplied line of text from the log file.
272  *
273  *	   UndoEvent *
274  *	   readEvent(line)
275  *	       char *line;
276  *	   {
277  *	   }
278  *
279  *	4. A procedure to fill in a line (null-terminated with
280  *	   no newlines embedded or trailing) with the external
281  *	   representation of an event based on an UndoEvent
282  *	   supplied to it.  This prodedure returns the number
283  *	   of bytes it stored in the line (which must be less
284  *	   than a constant, UNDOLINESIZE).
285  *
286  *	   int
287  *	   writeEvent(event, line)
288  *	       UndoEvent *event;
289  *	       char *line;
290  *	   {
291  *	   }
292  *
293  * If either readEvent or writeEvent are NULL, a default procedure
294  * is used which simply uses the same internal and external representation
295  * for the undo event (ie, a text string).
296  *
297  *	5. A procedure to play an event forward.
298  *
299  *	   void
300  *	   forwEvent(event)
301  *	       UndoEvent *event;
302  *	   {
303  *	   }
304  *
305  *	6. A procedure to play an event backward.
306  *
307  *	   void
308  *	   backEvent(event)
309  *	       UndoEvent *event;
310  *	   {
311  *	   }
312  *
313  *	7. A client name, for error messages.  This is a character
314  *	   string.
315  *
316  * Results:
317  *	Returns an UndoType which must be passed in future calls
318  *	to UndoNewEvent().  If -1 is returned, this means that there
319  *	are too many clients of the undo package.
320  *
321  * Side effects:
322  *	Initializes local state in the undo package.
323  *
324  * ----------------------------------------------------------------------------
325  */
326 
327 UndoType
328 UndoAddClient(init, done, readEvent, writeEvent, forwEvent, backEvent, name)
329     void (*init)();
330     void (*done)();
331     UndoEvent *(*readEvent)();
332     int (*writeEvent)();
333     void (*forwEvent)(), (*backEvent)();
334     char *name;
335 {
336     if (undoNumClients >= MAXUNDOCLIENTS)
337 	return ((UndoType) -1);
338 
339     undoClientTable[undoNumClients].uc_name = StrDup((char **) NULL, name);
340     undoClientTable[undoNumClients].uc_forw = forwEvent;
341     undoClientTable[undoNumClients].uc_back = backEvent;
342     undoClientTable[undoNumClients].uc_init = init;
343     undoClientTable[undoNumClients].uc_done = done;
344 
345     return (undoNumClients++);
346 }
347 
348 /*
349  * ----------------------------------------------------------------------------
350  *
351  * UndoFlush --
352  *
353  * Flush the current undo list.
354  *
355  * Results:
356  *	None.
357  *
358  * Side effects:
359  *	Deletes everything from the undo list.
360  *
361  * ----------------------------------------------------------------------------
362  */
363 
364 void
UndoFlush()365 UndoFlush()
366 {
367     if (undoLogHead == (internalUndoEvent *) NULL)
368 	return;
369 
370     while (undoLogTail != undoLogHead)
371     {
372 	freeMagic((char *) undoLogTail);
373 	undoLogTail = undoLogTail->iue_back;
374 	ASSERT(undoLogTail != (internalUndoEvent *) NULL, "UndoFlush");
375     }
376     freeMagic((char *) undoLogHead);
377 
378     undoLogHead = undoLogTail = undoLogCur = (internalUndoEvent *) NULL;
379     undoNumCommands = 0;
380     undoNumRecentEvents = 0;
381 }
382 
383 /*
384  * ----------------------------------------------------------------------------
385  *
386  * UndoDisable --
387  *
388  * Turn the undo package off.
389  * Future calls to UndoNewEvent() will return NULL, and future calls
390  * to UndoIsEnabled() will return FALSE, until the next call to
391  * UndoEnable();
392  *
393  * Results:
394  *	None.
395  *
396  * Side effects:
397  *	Disables undoing until the next call to UndoEnable().
398  *
399  * ----------------------------------------------------------------------------
400  */
401 
402 void
UndoDisable()403 UndoDisable()
404 {
405     UndoDisableCount++;
406 }
407 
408 /*
409  * ----------------------------------------------------------------------------
410  *
411  * UndoEnable --
412  *
413  * Turn the undo package on.
414  * Re-enables the undo package after a call to UndoDisable().
415  *
416  * Results:
417  *	None.
418  *
419  * Side effects:
420  *	Re-enables undoing.
421  *
422  * ----------------------------------------------------------------------------
423  */
424 
425 void
UndoEnable()426 UndoEnable()
427 {
428     if (UndoDisableCount > 0)
429 	UndoDisableCount--;
430 }
431 
432 /*
433  * ----------------------------------------------------------------------------
434  *
435  * UndoNewEvent --
436  *
437  * Return a pointer to a new UndoEvent of the specified type and capable
438  * of holding size bytes of client data.
439  *
440  * Results:
441  *	A pointer to a new UndoEvent.
442  *
443  * WARNING:
444  *	The pointer to the new UndoEvent must not be retained past the
445  *	next call to any of the routines in the undo package, as the
446  *	event is liable to be reallocated.
447  *
448  * Side effects:
449  *	Appends the event read to the undo list in the appropriate place,
450  *	depending on the context from which it was called.  If called by
451  *	a client under normal circumstances, appends the event to the end
452  * 	of the undo log.  If called by the readEvent() procedure during
453  *	replaying of the undo log, places the event at whichever end of
454  *	the log is appropriate to the operation (back/forw) being performed.
455  *
456  * ----------------------------------------------------------------------------
457  */
458 
459 UndoEvent *
UndoNewEvent(clientType,size)460 UndoNewEvent(clientType, size)
461     UndoType clientType;	/* Type of event to allocate */
462     unsigned int size;		/* Number of bytes of client data to allocate */
463 {
464     internalUndoEvent *iup;
465     int usize;
466 
467     if (UndoDisableCount > 0)
468 	return ((UndoEvent *) NULL);
469 
470     usize = undoSize(size);
471     iup = (internalUndoEvent *) mallocMagic((unsigned) usize);
472     ASSERT(clientType >= 0 && clientType < undoNumClients, "UndoNewEvent");
473     iup->iue_type = clientType;
474     if (undoState == US_APPEND)
475     {
476 	/*
477 	 * Normal state:
478 	 * Append the new event after the event pointed to by
479 	 * undoLogCur.
480 	 */
481 	iup->iue_forw = (internalUndoEvent *) NULL;
482 	iup->iue_back = undoLogCur;
483 	if (undoLogCur == (internalUndoEvent *) NULL)
484 	{
485 	    if (undoLogHead != (internalUndoEvent *) NULL)
486 		undoMemTruncate();
487 	    undoLogHead = undoLogCur = undoLogTail = iup;
488 	}
489 	else
490 	{
491 	    if (undoLogCur->iue_forw != (internalUndoEvent *) NULL)
492 		undoMemTruncate();
493 	    undoLogCur->iue_forw = iup;
494 	    undoLogCur = undoLogTail = iup;
495 	}
496 	undoNumRecentEvents++;
497     }
498 
499     return (undoExport(iup));
500 }
501 
502 /*
503  * ----------------------------------------------------------------------------
504  *
505  * UndoNext --
506  *
507  * Delimit a sequence of operations to the undo package with an event
508  * delimiter.
509  *
510  * Results:
511  *	None.
512  *
513  * Side effects:
514  *	Appends a marker to the undo list signifying the boundary of
515  *	a unit of events.  UndoBackward() and UndoForward() operate in
516  *	terms of complete units of this sort.
517  *
518  * ----------------------------------------------------------------------------
519  */
520 
521 void
UndoNext()522 UndoNext()
523 {
524     internalUndoEvent *iup;
525     int usize;
526 
527     if (UndoDisableCount > 0 || undoNumRecentEvents == 0)
528 	return;
529 
530     undoNumRecentEvents = 0;
531     undoNumCommands++;
532     usize = undoSize(0);
533     iup = (internalUndoEvent *) mallocMagic((unsigned) usize);
534     iup->iue_type = UT_DELIM;
535     iup->iue_back = undoLogTail;
536     iup->iue_forw = (internalUndoEvent *) NULL;
537     if (undoLogTail != (internalUndoEvent *) NULL)
538 	undoLogTail->iue_forw = iup;
539     undoLogCur = undoLogTail = iup;
540     if (undoNumCommands >= MAXCOMMANDS)
541 	undoFreeHead();
542 }
543 
544 /*
545  * ----------------------------------------------------------------------------
546  *
547  * UndoBackward --
548  *
549  * Play the undo log backward n events.
550  *
551  * Results:
552  *	The number of events actually played backward.  Normally, this
553  *	will be equal to n unless we encounter the beginning of the log.
554  *
555  * Side effects:
556  *	Applies the client backEvent() procedures to each event encountered
557  *	in playing the log backward.
558  *
559  * ----------------------------------------------------------------------------
560  */
561 
562 int
UndoBackward(n)563 UndoBackward(n)
564     int n;		/* Number of events to unplay */
565 {
566     internalUndoEvent *iup;
567     int client, count;
568 
569 #ifdef MAGIC_WRAPPER
570     /* This condition appears to happen just prior to a	program	*/
571     /* crash, apparently due to a race condition which is	*/
572     /* difficult to reproduce.  Tcl_DoOneEvent() inside the	*/
573     /* DRCContinuous routine is implicated.  Can't find the	*/
574     /* error source, but refusing to execute the undo command	*/
575     /* appears to prevent it.					*/
576 
577     if (UndoDisableCount > 0)
578     {
579 	TxError("Attempted undo with undo disabled. . . abort function.\n");
580 	return 0;
581     }
582 #endif
583 
584     /* Call the initialization routines of all clients */
585     for (client = 0; client < undoNumClients; client++)
586 	if (undoClientTable[client].uc_init)
587 	    (*undoClientTable[client].uc_init)();
588 
589     iup = undoLogCur;
590     undoNumRecentEvents = 0;
591     UndoDisableCount++;
592     for (count = 0; (count < n) && (iup != NULL); count++)
593     {
594 	do
595 	{
596 	    if (iup->iue_type != UT_DELIM)
597 		if (undoClientTable[iup->iue_type].uc_back != NULL)
598 		    (*undoClientTable[iup->iue_type].uc_back)(undoExport(iup));
599 
600 	    /* fprintf(stderr, "Undo record: type=%d back=0x%x, forw=0x%x\n",
601 	     *	iup->iue_type, iup->iue_back, iup->iue_forw);
602 	     * fprintf(stderr, "   UndoDisableCount = %d, undoNumRecentEvents = %d\n",
603 	     *		UndoDisableCount, undoNumRecentEvents);
604 	     * fflush(stderr);
605 	     */
606 
607 	    iup = undoGetBack(iup);
608 	}
609 	while ((iup != (internalUndoEvent *) NULL) && (iup->iue_type != UT_DELIM));
610     }
611     UndoDisableCount--;
612 
613     undoLogCur = iup;
614 
615     /* fprintf(stderr, "UndoBackward: undoLogCur set to 0x%x\n", undoLogCur);
616      * fflush(stderr);
617      */
618 
619     /* Call the termination routines of all clients */
620     for (client = 0; client < undoNumClients; client++)
621 	if (undoClientTable[client].uc_done)
622 	    (*undoClientTable[client].uc_done)();
623     return (count);
624 }
625 
626 /*
627  * ----------------------------------------------------------------------------
628  *
629  * UndoForward --
630  *
631  * Play the undo log forward n events.
632  *
633  * Results:
634  *	The number of events actually played forward.  Normally, this
635  *	will be equal to n unless we encounter the end of the log.
636  *
637  * Side effects:
638  *	Applies the client forwEvent() procedures to each event encountered
639  *	in playing the log forward.
640  *
641  * ----------------------------------------------------------------------------
642  */
643 
644 int
UndoForward(n)645 UndoForward(n)
646     int n;		/* Number of events to replay */
647 {
648     internalUndoEvent *iup;
649     int count, client;
650 
651     /* Call the initialization routines of all clients */
652     for (client = 0; client < undoNumClients; client++)
653 	if (undoClientTable[client].uc_init)
654 	    (*undoClientTable[client].uc_init)();
655 
656     count = 0;
657     iup = undoGetForw(undoLogCur);
658     if (iup == NULL) goto done;
659 
660     undoNumRecentEvents = 0;
661     UndoDisableCount++;
662     for ( ; count < n; count++)
663     {
664 	do
665 	{
666 	    if (iup->iue_type != UT_DELIM)
667 		if (undoClientTable[iup->iue_type].uc_forw != NULL)
668 		    (*undoClientTable[iup->iue_type].uc_forw)(undoExport(iup));
669 	    iup = undoGetForw(iup);
670 	}
671 	while (iup != (internalUndoEvent *) NULL && iup->iue_type != UT_DELIM);
672 	if (iup == (internalUndoEvent *) NULL)
673 	{
674 	    iup = undoLogTail;
675 	    break;
676 	}
677     }
678     UndoDisableCount--;
679 
680     undoLogCur = iup;
681 
682 done:
683     /* Call the termination routines of all clients */
684     for (client = 0; client < undoNumClients; client++)
685 	if (undoClientTable[client].uc_done)
686 	    (*undoClientTable[client].uc_done)();
687     return (count);
688 }
689 
690 /*
691  * ============================================================================
692  *
693  *	All of the remaining procedures in the file are invisible
694  *	to the clients of the undo package and should not be used.
695  *
696  * ============================================================================
697  */
698 
699 
700 /*
701  * ----------------------------------------------------------------------------
702  *
703  * undoGetForw --
704  *
705  * Return a pointer to the next undo event in the list.
706  *
707  * Results:
708  *	A pointer to an undo event.
709  *
710  * Side effects:
711  *	None.
712  *
713  * Directly modifies:
714  *	undoLogHead, undoLogTail
715  *	undoNumCommands
716  *	undoNumRecentEvents = 0
717  *
718  * Indirectly modifies:
719  *	Nothing.
720  *
721  * ----------------------------------------------------------------------------
722  */
723 
724 internalUndoEvent *
undoGetForw(iup)725 undoGetForw(iup)
726     internalUndoEvent *iup;
727 {
728     if (iup != (internalUndoEvent *) NULL)
729     {
730 	/*
731 	 * Return the next event in memory if there is one.
732 	 */
733 	if (iup->iue_forw != (internalUndoEvent *) NULL)
734 	    return (iup->iue_forw);
735     }
736     else
737     {
738 	/*
739 	 * A NULL initial iup means to start at the very beginning of
740 	 * the main-memory undo list.  If there is anything there, return
741 	 * it; otherwise return NULL.
742 	 */
743 	if (undoLogHead != (internalUndoEvent *) NULL)
744 	    return (undoLogHead);
745     }
746 
747     return ((internalUndoEvent *) NULL);
748 }
749 
750 /*
751  * ----------------------------------------------------------------------------
752  *
753  * undoGetBack --
754  *
755  * Return a pointer to the previous undo event in the list.
756  *
757  * Results:
758  *	A pointer to an undo event.
759  *
760  * Side effects:
761  *	None.
762  *
763  * Directly modifies:
764  *
765  * Indirectly modifies:
766  *
767  * ----------------------------------------------------------------------------
768  */
769 
770 internalUndoEvent *
undoGetBack(iup)771 undoGetBack(iup)
772     internalUndoEvent *iup;
773 {
774     if (iup == (internalUndoEvent *) NULL) return (iup);
775     if (iup->iue_back != (internalUndoEvent *) NULL) return (iup->iue_back);
776     return ((internalUndoEvent *) NULL);
777 }
778 
779 /*
780  * ----------------------------------------------------------------------------
781  *
782  * undoFreeHead --
783  *
784  * Free up space by throwing away events from the front of the in-memory
785  * event list until the total number of in-memory commands falls below
786  * LOWCOMMANDS
787  *
788  * Results:
789  *	None.
790  *
791  * Side effects:
792  *	Deallocates events from the front of the in-memory event list.
793  *	Updates undoLogHead, undoNumCommands.
794  *	Guaranteed to leave undoLogHead pointing to the first event
795  *	in a command (not of type UT_DELIM).
796  *
797  * WARNING:
798  *	It is important that undoLogCur point beyond the region
799  *	to be freed.  Also, it is important that the in-core list
800  *	be terminated by an UT_DELIM event.
801  *
802  * ----------------------------------------------------------------------------
803  */
804 
805 void
undoFreeHead()806 undoFreeHead()
807 {
808     if (undoNumCommands <= LOWCOMMANDS)
809 	return;
810 
811     while (undoNumCommands > LOWCOMMANDS)
812     {
813 	do
814 	{
815 	    ASSERT(undoLogHead != undoLogCur, "undoFreeHead");
816 	    freeMagic((char *) undoLogHead);
817 	    undoLogHead = undoLogHead->iue_forw;
818 	    ASSERT(undoLogHead != (internalUndoEvent *) NULL, "undoFreeHead");
819 	}
820 	while (undoLogHead->iue_type != UT_DELIM);
821 	undoNumCommands--;
822     }
823     freeMagic((char *) undoLogHead);
824     undoLogHead = undoLogHead->iue_forw;
825     undoLogHead->iue_back = (internalUndoEvent *) NULL;
826 }
827 
828 /*
829  * ----------------------------------------------------------------------------
830  *
831  * undoMemTruncate --
832  *
833  * Delete events in memory which are later than the current event.
834  * NOTE: This expects to be called only when undoNumRecentEvents == 0.
835  *
836  * Results:
837  *	None.
838  *
839  * Side effects:
840  *	Truncates the event list so there are no events past undoLogCur.
841  *
842  *	Updates undoLogTail.
843  *
844  * ----------------------------------------------------------------------------
845  */
846 
847 void
undoMemTruncate()848 undoMemTruncate()
849 {
850     internalUndoEvent *up;
851 
852     /*
853      * If there are events forward of the current event that
854      * will get overwritten by the new event, delete them from
855      * memory.
856      */
857 
858     if (undoLogCur == (internalUndoEvent *) NULL)
859     {
860 	/*
861 	 * Delete ALL events from memory
862 	 */
863 	up = undoLogHead;
864 	while (up != (internalUndoEvent *) NULL)
865 	{
866 	    freeMagic((char *) up);
867 	    up = up->iue_forw;
868 	}
869 	undoLogTail = undoLogHead = (internalUndoEvent *) NULL;
870 	undoNumCommands = 0;
871     }
872     else
873     {
874 	ASSERT(undoLogCur->iue_type == UT_DELIM, "undoMemTruncate");
875 	/*
876 	 * Delete only some of the events in main memory.
877 	 */
878 	up = undoLogCur->iue_forw;
879 	while (up != (internalUndoEvent *) NULL)
880 	{
881 	    if (up->iue_type == UT_DELIM)
882 		undoNumCommands--;
883 	    freeMagic((char *) up);
884 	    up = up->iue_forw;
885 	}
886 	undoLogCur->iue_forw = (internalUndoEvent *) NULL;
887 	undoLogTail = undoLogCur;
888     }
889 
890 }
891 
892 /*
893  * ============================================================================
894  *
895  *			    DEBUGGING PROCEDURES
896  *
897  * ============================================================================
898  */
899 
900 void
undoPrintEvent(iup)901 undoPrintEvent(iup)
902     internalUndoEvent *iup;
903 {
904     char *client_name;
905     if (iup->iue_type < 0)
906 	client_name = "(delimiter)";
907     else
908 	client_name = undoClientTable[iup->iue_type].uc_name;
909 
910     (void) TxPrintf("0x%x: \t%s \tf=0x%x \tb=0x%x\n",
911 		iup, client_name, iup->iue_forw, iup->iue_back);
912 }
913 
914 /* Print events forward from "iup".  If n is 0 or negative, print to	*/
915 /* the end of the stack.  Otherwise, print the next n events.		*/
916 
917 void
undoPrintForw(iup,n)918 undoPrintForw(iup, n)
919     internalUndoEvent *iup;
920     int n;
921 {
922     int i = 0;
923 
924     (void) TxPrintf("head=0x%x\ttail=0x%x\tcur=0x%x\n",
925 		undoLogHead, undoLogTail, undoLogCur);
926     if (iup == (internalUndoEvent *) NULL)
927 	iup = undoLogHead;
928     while (iup != (internalUndoEvent *) NULL)
929     {
930 	undoPrintEvent(iup);
931 	iup = iup->iue_forw;
932 	i++;
933 	if (i == n) break;
934     }
935 }
936 
937 /* Print events backward from "iup".  If n is 0 or negative, print to	*/
938 /* the beginning of the stack.  Otherwise, print the previous n events.	*/
939 
940 void
undoPrintBack(iup,n)941 undoPrintBack(iup, n)
942     internalUndoEvent *iup;
943     int n;
944 {
945     int i = 0;
946 
947     (void) TxPrintf("head=0x%x\ttail=0x%x\tcur=0x%x\n",
948 		undoLogHead, undoLogTail, undoLogCur);
949     if (iup == (internalUndoEvent *) NULL)
950 	iup = undoLogTail;
951     while (iup != (internalUndoEvent *) NULL)
952     {
953 	undoPrintEvent(iup);
954 	iup = iup->iue_back;
955 	i++;
956 	if (i == n) break;
957     }
958 }
959 
960 /* Called from windUndoCmd() or windRedoCmd() with n either positive, 	*/
961 /* or negative with 1 offset to differentiate between n = 0 (forward)	*/
962 /* and n = 0 (backward).						*/
963 
964 void
UndoStackTrace(n)965 UndoStackTrace(n)
966     int n;
967 {
968     if (n < 0)
969        undoPrintBack(undoLogCur, -(n + 1));
970     else
971        undoPrintForw(undoLogCur, n);
972 }
973 
974