#include<stdio.h>
 
/* 




                 VV                   VV    EEEEEEEEEEEEEE   
                  VV                 VV     EE  
                   VV               VV      EE
                    VV             VV       EE
                     VV           VV        EE
                      VV         VV         EEEEEEEEEEEEEE
                       VV       VV          EE
                        VV     VV           EE
                         VV   VV            EE
                          VV VV             EE
                           VVV              EE
                            V               EEEEEEEEEEEEEE
                                                       

 
 
 
                       MODULE  : undo.c
 
                       VERSION : v3.5
 
                       REVISED : 14 Jul 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module implements VE's undo functions. The undo queue, 
 
               all interface functions for viewing and manipulating it, and

               all of the undo action implementations are contained herein.
                                                     
 
*/

/* .page */

/*****************
 * INCLUDE FILES *
 *****************/

#include <string.h>
#include <curses.h>

#include "../def/common.def"
#include "../def/cmdix.def"
#include "../def/kbdif.def"
#include "../def/undo.def"

#define NUM_UNDO_CMDS   12   /* The number of undo commands                 */
#define MAX_UNDO_CHARS  82   /* Size of UT_del_char buffer                  */
#define MAX_UNDO_ITEMS  128  /* Maximum items on undo queue                 */
#define SAFETY_PAD      16   /* Extra bytes for safety on char buffers      */

#define UNDO_QUEUE_STACK_SIZE 16  /* Depth of undo queue stack              */

#include "../str/vestruct.str"
#include "../str/stacks.str"
#include "../str/blockutil.str"

#include "../inc/vedbase.inc"
#include "../inc/termif.inc"
#include "../inc/kbdif.inc"

#include "../ext/veutils.ext"
#include "../ext/blockutil.ext"
#include "../ext/columncmd.ext"
#include "../ext/lnklst.ext"
#include "../ext/graemeut.ext"

/* Forward reference to undo_cmd, so that undo_macro can call it, and to    */
/* flush_undo_queue, so that free_undo_item can call it                     */

void undo_cmd ();
void flush_undo_queue ();

/* Forward references for the undo dispatch table                           */

void undo_del_char ();
void undo_del_right ();
void undo_del_line ();
void undo_del_block ();
void undo_substitute ();
void undo_block_paste ();
void undo_del_word ();
void undo_ins_char ();
void undo_wrap_para ();
void undo_macro ();
void undo_column_paste ();
void undo_column_delete ();

/* .page */

/***********************
 * PRIVATE TYPES       *
 ***********************/

/* Undo item - this is the entity that gets queued on the undo queue. It    */
/* contains all the relevant information needed to undo an action that has  */
/* been taken by the user (for the supported undo types)                    */
 
struct str_undo_item {

   struct str_undo_item *fwd_link; /* Link to next undo item on the queue   */
   struct str_undo_item *bwd_link; /* Link to last undo item on the queue   */

   int         length;        /* Used to indicate number of actions in item */
   int         attributes;    /* Used various ways by various actions       */

   int         cmd_ix;        /* The command to undo                        */
   int         cmd_arg;       /* Any optional command argument              */
   int         line_num;      /* The line number of the changed line        */
   int         cx;            /* The x,y co-ordinates where change was made */
   int         cy;

   int         data_type;     /* What type is the data? Char, Line, etc.    */
   int         max_datum;     /* Max number of items of this type herein    */
   int         num_datum;     /* The number of undo data elements herein    */
   char        *undo_data;    /* Undo data buffer  - will be cast as chars, */
                              /* lines etc. as needed by each command.      */
};

/* Undo data for the Substitute command. This data contains the original    */
/* string and the string that was substituted for it.                       */

struct str_subs_undo {
   struct asc_buf orig;
   struct asc_buf subs;
};

/* .page */

/***********************
 * PRIVATE VARIABLES   *
 ***********************/

/* The undo queue. This is where undo actions are queued by commands, via   */
/* the interfaces in this module. The undo queue is referenced via a        */
/* pointer because the macro command redirects the undo queue during the    */
/* recording and undo of a macro.                                           */

static struct file_ctl ve_undo_queue;
static struct file_ctl *undo_queue;

/* A push down stack of undo queues. When a macro undo action is pushed, it */
/* saves the current undo queue pointer in this stack, and substitutes its  */
/* own. When the complete_push_undo_macro occurs, it pops the last undo     */
/* queue pointer off of this stack and restores it to the global undo queue */
/* pointer. This allows a macro execution to generate just one undo item on */
/* the main undo queue, and allows all changes made by the macro to be      */
/* undone with one user entered undo command.                               */

static struct stack_type undo_queue_stack;

/* Dispatch table for undo procedures called by the undo command. The       */
/* undo_types and undo_procs tables must match one for one.                 */

char undo_types [NUM_UNDO_CMDS] = {
     UT_del_char,
     UT_del_right,
     UT_del_line,
     UT_del_block,
     UT_substitute,
     UT_block_paste,
     UT_del_word,
     UT_ins_char,
     UT_wrap_para,
     UT_macro,
     UT_column_paste,
     UT_column_delete
};

void (*undo_procs[NUM_UNDO_CMDS]) ( struct str_undo_item *curr_item ) = {
    &undo_del_char,
    &undo_del_right,
    &undo_del_line,
    &undo_del_block,
    &undo_substitute,
    &undo_block_paste,
    &undo_del_word,
    &undo_ins_char,
    &undo_wrap_para,
    &undo_macro,
    &undo_column_paste,
    &undo_column_delete
};

/* .page */

/**********************
 * PRIVATE PROCEDURES *
 **********************/

struct str_undo_item *create_undo_item ( cmd_ix, line_num, cx, cy, data_type )

/****************************************************************************
 * This function creates a generic undo item and copies the supplied        *
 * fields into it. This function returns the pointer to the created item.   *
 ****************************************************************************/

int cmd_ix;      /* The VE command index of the command to undo             */
int line_num;    /* The line number at which the command is executing       */
int cx, cy;      /* The cursor position at the time of execution            */
int data_type;   /* The undo type of this undo action                       */

begin
   struct str_undo_item *curr_item;

   curr_item   = (struct str_undo_item *) ve_alloc (sizeof(struct str_undo_item));
   setb ( 0, curr_item, sizeof(struct str_undo_item) );

   curr_item->fwd_link  = NULL;
   curr_item->bwd_link  = NULL;

   curr_item->cmd_ix    = cmd_ix;
   curr_item->line_num  = line_num;
   curr_item->cx        = cx;
   curr_item->cy        = cy;

   curr_item->data_type = data_type;
   curr_item->length    = 1;
   curr_item->max_datum = 1;
   curr_item->num_datum = 1;

   curr_item->undo_data = NULL;

   return ( curr_item );

end /* create_undo_item */

/* .page */

void free_undo_item ( undo_item )

/***************************************************************************
 * This function frees the supplied undo item by freeing any linked undo   *
 * data, and then freeing the supplied item itself. This function does     *
 * not update the ".length" or the ".nodes" fields of the undo_queue. This *
 * is left to the caller.                                                  *
 ***************************************************************************/

struct str_undo_item *undo_item;

begin
   struct file_ctl *undo_block, *temp_queue;
   struct node *curr_element, *next_element;

   /* Items on the undo queue frequently have data buffers linked to  */
   /* them containing the actual undo data. Free any buffers linked   */
   /* this item.                                                      */

   switch ( undo_item->data_type ) begin

      case UT_del_char:

         /* A single buffer is linked to this item, which contains    */
         /* the deleted characters from multiple Delete or Rubout     */
         /* commands.                                                 */

         free ( undo_item->undo_data );
         break;

      case UT_del_right:

         /* A single buffer is linked to this item, which contains    */
         /* a block of deleted chars from a single DeleteRight cmd    */

         free ( undo_item->undo_data );
         break;

      case UT_del_line:

         /* Many "struct node" elements are linked to this item in a  */
         /* one way linked list. These lines are the result of        */
         /* multiple single DeleteLine commands (F9).                 */

         curr_element = (struct node *) undo_item->undo_data;

         while ( curr_element != NULL ) 
         begin
            next_element = curr_element->fwd_link;
            free ( curr_element );

            curr_element = next_element;
         end

         break;

      case UT_del_block:
      case UT_column_paste:
      case UT_column_delete:

         /* The item's undo_data points to a linked list header for   */
         /* the block of lines that make up the undo data. Free each  */
         /* of the lines, and then free the linked list header.       */

         undo_block   = (struct file_ctl *) undo_item->undo_data;
         curr_element = undo_block->head;

         while ( curr_element != NULL )
         begin
            next_element = curr_element->fwd_link;
            free ( curr_element );

            curr_element = next_element;
         end

         free ( undo_block );

         break;

      case UT_substitute:

         /* A single buffer is linked to this item, which contains    */
         /* the original and substituted strings.                     */

         free ( undo_item->undo_data );
         break;

      case UT_block_paste:

         /* There is no undo data attached to this item, as the undo  */
         /* action is to delete text, not restore it.                 */

         break;

      case UT_del_word:

         /* A single buffer is attached to this item, with the word   */
         /* that was deleted as its contents.                         */

         free ( undo_item->undo_data );
         break;

      case UT_ins_char:

         /* A single buffer is attached to this item, with the cx     */
         /* values of potentially many typed chars.                   */

         free ( undo_item->undo_data );
         break;

      case UT_wrap_para:

         /* A single buffer is attached to this item, containing      */
         /* an empty linked list descriptor. Release that.            */

         free ( undo_item->undo_data );
         break;

      case UT_macro:

         /* A single buffer is attached to this item, containing      */
         /* an undo queue descriptor. If there is anything still on   */
         /* that queue, release it, then release the descriptor.      */

         temp_queue = undo_queue;
         undo_queue = (struct file_ctl *) undo_item->undo_data;

         if ( undo_queue->nodes > 0 )
            flush_undo_queue ();

         undo_queue = temp_queue;

         free ( undo_item->undo_data );
         break;

      default:
         break;

   end /* switch undo_item->data_type */

   free ( undo_item );

end /* free_undo_item */

/* .page */

void queue_undo_item ( undo_item )

/****************************************************************************
 * This function accepts an undo item and adds it to the undo_queue. The    *
 * undo_queue is a FIFO queue, and so the added is added at the head. The   *
 * undo queue is limited to maximum size, and so this function will delete  *
 * the last item in the queue if the new item causes the queue to become    *
 * larger than the maximum.                                                 *
 ****************************************************************************/

struct str_undo_item *undo_item;  /* item to place on the undo_queue        */

begin
   int status;                    /* status of linked list operation        */
   struct node *del_item;         /* deleted item                           */

   add ( undo_queue, (struct node *) undo_queue, 
         (struct node *) undo_item,  &status       );

   if ( undo_queue->nodes > MAX_UNDO_ITEMS )
   begin
      /* The undo queue has gotten too large. This is a FIFO queue, and so  */
      /* to restrain the size, we delete the last item on the queue         */

      del_item = delete ( undo_queue, undo_queue->tail, &status );
      undo_item = (struct str_undo_item *) del_item;

      /* Before freeing the item, update the undo_queue to reflect that the */
      /* the number of available undo actions is decreased by the number    */
      /* that were in this item. Note that decrementing the undo_queue's    */
      /* ".nodes" field is handled by the "delete" call.                    */

      undo_queue->length -= undo_item->length;

      free_undo_item ( del_item );
   end

end /* queue_undo_item */

/* .page */

/*********************
 * PUBLIC PROCEDURES *
 *********************/

void init_undo_system ()

/****************************************************************************
 * This function initializes the undo system by initializing the undo       *
 * queue and the undo queue stack. This function is only called from main.c *
 * when VE starts.                                                          *
 ****************************************************************************/

begin
   /* Set up the master undo queue */

   undo_queue = &ve_undo_queue;
   init_list ( undo_queue );

   /* Set up the undo queue stack */

   init_stack ( &undo_queue_stack, UNDO_QUEUE_STACK_SIZE );

   undo_state = US_monitoring;

end /* init_undo_system */

/* .page */

int get_num_undo_queue_actions ()

/****************************************************************************
 * This function returns the number of undo actions presently queued on the *
 * undo_queue. This is recorded in the ".length" element of the queue       *
 * record                                                                   *
 ****************************************************************************/

begin

   return ( undo_queue->length );

end /* get_num_undo_queue_actions */



int get_num_undo_queue_items ()

/****************************************************************************
 * This function returns the number of undo items (each one of which may    *
 * contain multiple undo actions) presently queued on the undo_queue. This  *
 * is recorded in the ".nodes" element of the queue record                  *
 ****************************************************************************/

begin

   return ( undo_queue->nodes );

end /* get_num_undo_queue_items */



int get_undo_queue_size ()

/****************************************************************************
 * This function returns the number of bytes of memory consumed by the undo *
 * queue. Right now, it only addresses the memory used by the queue items   *
 * themselves. The size of the attached undo data is not dealt with yet.    *
 ****************************************************************************/

begin

   return ( undo_queue->nodes * sizeof(struct str_undo_item) );

end /* get_undo_queue_size */

/* .page */

void flush_undo_queue ()

/****************************************************************************
 * This function flushes the undo queue, freeing all associated memory      *
 ****************************************************************************/

begin
   register struct str_undo_item *curr_item, *next_item;

   if ( undo_queue->nodes > 0 ) 
   begin
      curr_item = (struct str_undo_item *) undo_queue->head;

      while ( curr_item != NULL )
      begin
         next_item = curr_item->fwd_link;

         free_undo_item ( curr_item );

         curr_item = next_item;

      end /* while */

      /* Now that everything is off the queue, re-initialize it and mark it */
      /* as flushed. This later marking is used by undo_macro, if the queue */
      /* happens to be the queue for an executing macro.                    */

      init_list ( undo_queue );

      undo_queue->file_format = 'f';   /* file_format used as "flushed"     */
                                       /* marker: 'f' = "flushed"           */
   end /* if undo_queue->nodes > 0 */
         
end /* flush_undo_queue */

/* .page */

void push_undo_del_char ( deleted_char, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo item for the supplied deleted char, and    *
 * queues that item on the undo queue. If an existing item of the right     *
 * type is already open on the queue, it simply adds to it, for efficiency  *
 *                                                                          *
 ****************************************************************************/

char deleted_char;          /* The char that was deleted                    */
int  line_num;              /* The line number of the line it was on        */
int  cx;                    /* The x index in the line                      */
int  cy;                    /* The y index on the line on screen - not used */

begin
   register struct str_undo_item *curr_item;
   char *char_buffer;

   register int cmd_ix, increment;
   register int done = FALSE;

   cmd_ix = (curr_cmd == commands[del_char_ix]) ? del_char_ix : rubout_ix;

   if ( undo_queue->nodes > 0 ) 
   begin
      /* If there is already an item on the undo queue, check to see if it  */
      /* is a UT_del_char type. If it is, and it is not full, and           */
      /* the character being deleted is contiguous with the last char in    */
      /* current undo item, THEN we can simply extend the existing item. If */
      /* full, we will need to create a one.                                */

      curr_item = (struct str_undo_item *) undo_queue->head;
      increment = (cmd_ix == rubout_ix) ? curr_item->num_datum : 0;

      if ( ( curr_item->data_type == UT_del_char          ) &&
           ( curr_item->num_datum <  curr_item->max_datum ) &&
           ( curr_item->cmd_ix    == cmd_ix               ) &&
           ( curr_item->line_num  == line_num             ) &&
           ( curr_item->cx        == (cx + increment)     ) )
      begin
         char_buffer = (char *) curr_item->undo_data;
         char_buffer[curr_item->num_datum] = deleted_char;

         done = TRUE;
      end

   end

   if ( !done )
   begin
      /* The item at the top of the queue was of the wrong type, or it was */
      /* completely filled. Create a new item and queue it.                */

      curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_del_char );

      char_buffer          = (char *) ve_alloc ( MAX_UNDO_CHARS );
      char_buffer[0]       = deleted_char;
      curr_item->undo_data = char_buffer;

      curr_item->max_datum = MAX_UNDO_CHARS;
      curr_item->num_datum = 0;
      curr_item->length    = 0;

      /* Now queue the item on the undo queue and we are done               */

      queue_undo_item ( curr_item );

   end /* not done */

   /* Update the count of data items in this undo item, and the number of  */
   /* undo actions in the undo_queue.                                      */

   curr_item->num_datum++;
   curr_item->length++;

   undo_queue->length++;

end /* push_undo_del_char */

/* .page */

void push_undo_del_right ( full_line, start_ix, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the delete of all chars     *
 * on the supplied line starting at the start_ix and ending at the end of   *
 * the line. Each such undo action is stored in its own undo item.          *
 *                                                                          *
 ****************************************************************************/

struct node *full_line;     /* The line on which chars will be deleted      */
int  start_ix;              /* The position of the first char deleted       */
int  line_num;              /* The line number of the line it was on        */
int  cx;                    /* The x index in the line                      */
int  cy;                    /* The y index on the line on screen - not used */

begin
   char *char_buffer;
   register int data_size;
   register struct str_undo_item *curr_item;

   curr_item = create_undo_item ( del_right_ix, line_num, cx, cy, UT_del_right );

   data_size            = full_line->length - start_ix;
   curr_item->max_datum = start_ix;
   curr_item->num_datum = data_size;

   char_buffer = (char *) ve_alloc ( data_size + SAFETY_PAD );
   movb ( &full_line->text[start_ix], char_buffer, data_size );
   curr_item->undo_data = char_buffer;

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_del_right */

/* .page */

void push_undo_del_line ( deleted_line, end_of_file, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo item for the supplied deleted line, and    *
 * queues that item on the undo queue. If an existing item of the right     *
 * type is already open on the queue, it simply adds to it, for efficiency  *
 *                                                                          *
 ****************************************************************************/

struct node *deleted_line;  /* The line that was deleted                    */
BOOL end_of_file;           /* Was the line deleted the last line of file?  */
int  line_num;              /* The line number of the line it was on        */
int  cx;                    /* The x index in the line                      */
int  cy;                    /* The y index on the line on screen - not used */

begin
   register struct str_undo_item *curr_item;
   register int done = FALSE;

   if ( (!end_of_file) && (undo_queue->nodes > 0) ) 
   begin
      /* If there is already an item on the undo queue, check to see if it  */
      /* is a UT_del_line type. If it is and the line being deleted         */
      /* is contiguous with the last line deleted, then simply add this     */
      /* line to the existing undo item. Else, create a new one. Note that  */
      /* lines being deleted which are the last line of the file ALWAYS get */
      /* a new item, since they must be treated specially - the undo must   */
      /* insert the undeleted line AFTER the current line, not BEFORE, as   */
      /* is the usual case.                                                 */

      curr_item = (struct str_undo_item *) undo_queue->head;

      if ( ( curr_item->data_type == UT_del_line  ) &&
           ( curr_item->line_num  == line_num     ) )
      begin
         /* The undo data in a UT_del_line item is a pointer to a   */
         /* one way linked list of lines. Simply insert the newly deleted   */
         /* line to the head of the queue.                                  */

         deleted_line->fwd_link = (struct node *) curr_item->undo_data;
         curr_item->undo_data   = (char *) deleted_line;

         done = TRUE;
      end

   end

   if ( !done )
   begin
      /* The item at the top of the queue was of the wrong type, or it was */
      /* completely filled. Create a new item and queue it.                */

      curr_item = create_undo_item ( del_line_ix, line_num, cx, cy, UT_del_line );
      curr_item->attributes = (end_of_file) ? UA_end_of_file : UA_nil_attr;

      /* The undo data for an item of UT_del_line is a pointer to a         */
      /* one way linked list. Since this is the first item going into the   */
      /* item simply set the data to point to the newly deleted line,       */
      /* effectively queueing it against this item.                         */

      deleted_line->fwd_link = NULL;
      curr_item->length      = 0;
      curr_item->num_datum   = 0;
      curr_item->undo_data   = (char *) deleted_line;

      /* Now queue the item on the undo queue and we are done               */

      queue_undo_item ( curr_item );

   end /* not done */

   /* Update the count of data items in this undo item, and the number of  */
   /* undo actions in the undo_queue.                                      */

   curr_item->num_datum++;
   curr_item->length++;

   undo_queue->length++;

end /* push_undo_del_line */

/* .page */

void push_undo_del_block ( cmd_ix, deleted_block, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the delete of all chars     *
 * on the supplied line starting at the start_ix and ending at the end of   *
 * the line. Each such undo action is stored in its own undo item.          *
 *                                                                          *
 ****************************************************************************/

int    cmd_ix;                  /* The command pushing the undo             */
struct file_ctl *deleted_block; /* BlockBuffer or DelLineBuffer header      */
int    line_num;                /* The line number of the line it was on    */
int    cx, cy;                  /* The cursor x,y at the time of the undo   */

begin
   register struct str_undo_item *curr_item;
   register struct node *deleted_line, *undo_line;
   register struct file_ctl *undo_block;

   curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_del_block );

   undo_block  = (struct file_ctl *) ve_alloc ( sizeof(struct file_ctl) );
   init_list ( undo_block );
   curr_item->undo_data = (char *) undo_block;

   /* Now simply copy the lines from the deleted block to the undo block.   */
   /* The lines remain on the original block when done - they are there     */
   /* because the user did a block delete or a CTL-X line delete, both of   */
   /* delete the lines from the file, but save them in their respective     */
   /* blocks for later possible pasting. In providing an undo action, we    */
   /* are merely making a copy of the saved deleted lines. Odd but true.    */

   deleted_line = deleted_block->head;

   while ( deleted_line != NULL )
   begin
      undo_line = (struct node *) ve_alloc (deleted_line->length + DES_SIZE);
      movb ( &deleted_line->length, &undo_line->length, deleted_line->length + sizeof(int));
      send_node ( undo_block, undo_line );

      deleted_line = deleted_line->fwd_link;
   end

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_del_block */

/* .page */

void push_undo_substitute ( orig, subs, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the substitution of one     *
 * string for another via the Substitute/Replace command. Each such undo    *
 * action is stored in its own undo item.                                   *
 *                                                                          *
 ****************************************************************************/

struct asc_buf *orig;  /* The original text in the file                     */
struct asc_buf *subs;  /* The string that was substituted for the original  */
int  line_num;         /* The line number of the line the subs was made on  */
int  cx;               /* The x index in the line                           */
int  cy;               /* The y index on the line on screen - not used      */

begin
   struct str_subs_undo *subs_undo;
   register struct str_undo_item *curr_item;

   /* Check for data sanity first */

   if ( orig->length >= 80 || subs->length >= 80 )
   begin
      write_ve_msg ( "Push Undo Error: strings too long" );
      return;
   end

   curr_item = create_undo_item ( substitute_ix, line_num, cx, cy, UT_substitute );

   subs_undo = (struct str_subs_undo *) ve_alloc ( sizeof(struct str_subs_undo) );

   movb ( orig, &subs_undo->orig, sizeof(struct asc_buf) );
   movb ( subs, &subs_undo->subs, sizeof(struct asc_buf) );

   curr_item->undo_data = (char *) subs_undo;

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_substitute */

/* .page */

void push_undo_block_paste ( cmd_ix, num_lines, line_num, cx, cy ) 

/****************************************************************************
 *                                                                          *
 * This function creates and queues an undo item for the paste of a block   *
 * of lines. Each such action is stored in its own item.                    *
 *                                                                          *
 ****************************************************************************/

int cmd_ix;              /* The command that decided to take no action      */
int num_lines;           /* The number of lines pasted in                   */
int line_num;            /* The line number at which the command was run    */
int cx, cy;              /* The cursor x,y at the time of the command       */

begin
   struct str_undo_item *curr_item;

   curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_block_paste );

   curr_item->num_datum = num_lines;

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_block_paste */

/* .page */

void push_undo_del_word ( full_line, word_size, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the delete of a single word *
 * on the supplied line, starting at the start_ix and ending at the end of  *
 * the word. Each such undo action is stored in its own undo item.          *
 *                                                                          *
 ****************************************************************************/

struct node *full_line;     /* The line containing the word being deleted   */
int  word_size;             /* The number of chars in the deleted word      */
int  line_num;              /* The line number of the line it was on        */
int  cx;                    /* The x index in the line                      */
int  cy;                    /* The y index on the line on screen - not used */

begin
   char *char_buffer;
   register int data_size;
   register struct str_undo_item *curr_item;

   /* Create a new undo item for this undo action.                      */

   curr_item = create_undo_item ( del_word_ix, line_num, cx, cy, UT_del_word );

   char_buffer = (char *) ve_alloc ( word_size + SAFETY_PAD );
   movb ( &full_line->text[cx], char_buffer, word_size );

   curr_item->num_datum = word_size;
   curr_item->undo_data = char_buffer;

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_del_word */

/* .page */

void push_undo_ins_char ( curr_char, cmd_ix, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo item for the entry of single character of  *
 * text resulting from the user typing that text. This single undo function *
 * handles text entry in both INSERT and OVERWRITE modes, although text     *
 * entry from each mode is kept in a different undo action. This is         *
 * enforced by ensuring that only text entries from the same cmd_ix (either *
 * insert_ix or overwrite_ix) are stored in the same undo item.  If an      *
 * existing item of the right type and the right cmd_ix is already open on  *
 * the queue, it simply adds to it, for efficiency                          *
 *                                                                          *
 ****************************************************************************/

char curr_char;             /* The char at .cx (used for OVERWRITE)         */
int  cmd_ix;                /* Is INS or OVR running this undo?             */
int  line_num;              /* The line number of the line it was on        */
int  cx;                    /* The x index in the line                      */
int  cy;                    /* The y index on the line on screen - not used */

begin
   register struct str_undo_item *curr_item;
   char *char_buffer;

   register int done = FALSE;
   register int prev_cx;

   if ( undo_queue->nodes > 0 ) 
   begin
      /* If there is already an item on the undo queue, check to see if it  */
      /* is a UT_ins_char type. If it is, and it is not full, and the char  */
      /* being inserted is contiguous with the last char in the current     */
      /* undo item, THEN we can simply extend the existing item. If full,   */
      /* we will need to create a one.                                      */

      curr_item = (struct str_undo_item *) undo_queue->head;
      
      char_buffer = curr_item->undo_data;
      prev_cx     = curr_item->cx + curr_item->num_datum - 1;

      if ( ( curr_item->data_type == UT_ins_char          ) &&
           ( curr_item->num_datum <  curr_item->max_datum ) &&
           ( curr_item->cmd_ix    == cmd_ix               ) &&
           ( curr_item->line_num  == line_num             ) &&
           ( prev_cx              == cx - 1               ) )
      begin
         char_buffer[curr_item->num_datum] = curr_char;
         done = TRUE;
      end

   end

   if ( !done )
   begin
      /* The item at the top of the queue was of the wrong type, or it was */
      /* completely filled. Create a new item and queue it.                */

      curr_item   = create_undo_item ( cmd_ix, line_num, cx, cy, UT_ins_char );
      char_buffer = ve_alloc ( MAX_UNDO_CHARS );

      char_buffer[0]       = curr_char;
      curr_item->undo_data = char_buffer;

      curr_item->max_datum = MAX_UNDO_CHARS;
      curr_item->num_datum = 0;
      curr_item->length    = 0;

      /* Now queue the item on the undo queue and we are done               */

      queue_undo_item ( curr_item );

   end /* not done */

   /* Update the count of data items in this undo item, and the number of  */
   /* undo actions in the undo_queue.                                      */

   curr_item->num_datum++;
   curr_item->length++;

   undo_queue->length++;

end /* push_undo_ins_char */

/* .page */

void push_undo_wrap_para ( first_line, last_line, wrapped_lines, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the wrapping of a paragraph of  *
 * text starting with the supplied first_line and ending with the supplied  *
 * last_line (inclusive). Each such undo action is stored in its own undo   *
 * item.                                                                    *
 *                                                                          *
 ****************************************************************************/

struct node *first_line;        /* The first line of area to be wrapped     */
struct node *last_line;         /* The last line of the area to be wrapped  */
int    wrapped_lines;           /* The number of lines AFTER the wrap done  */
int    line_num;                /* The line number of the line it was on    */
int    cx, cy;                  /* The cursor x,y at the time of the undo   */

begin
   register struct str_undo_item *curr_item;
   register struct node *this_line, *undo_line;
   register struct file_ctl *undo_block;

   curr_item = create_undo_item ( wrap_para_ix, line_num, cx, cy, UT_wrap_para );

   curr_item->num_datum = wrapped_lines;  /* AFTER the wrap, not BEFORE! */

   undo_block  = (struct file_ctl *) ve_alloc ( sizeof(struct file_ctl) );
   init_list ( undo_block );
   curr_item->undo_data = (char *) undo_block;

   this_line = first_line;

   /* Iterate through the prewrapped lines, and make a copy of them as the  */
   /* undo data for this action.                                            */

   do
   begin
      undo_line = (struct node *) ve_alloc (this_line->length + DES_SIZE);
      movb ( &this_line->length, &undo_line->length, this_line->length + sizeof(int));
      send_node ( undo_block, undo_line );
   end
   while ( ( (this_line = this_line->fwd_link) != last_line->fwd_link ) &&
           (  this_line != NULL                                       )    );

   /* Now queue the item on the undo queue and we are done */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_wrap_para */

/* .page */

void update_undo_wrap_para ( wrapped_lines )

/****************************************************************************
 * Update the current undo_wrap_para undo item with the number of lines     *
 * that resulted from the wrapping operation. A separate function is used   *
 * for this since the number of wrapped lines is not known before the wrap, *
 * but the original unwrapped lines are no longer available AFTER the wrap. *
 * Hence we have to record the undo information in two steps.               *
 ****************************************************************************/

int wrapped_lines;    /* The number of lines AFTER the wrap                 */

begin
   register struct str_undo_item *curr_item;

   /* This should be being called from the wrap_paragraph_cmd, which should */
   /* have an undo item queued at the head of the undo queue at this very   */
   /* moment. If that is not the case, make a stink and go away             */

   curr_item = (struct str_undo_item *) undo_queue->head;

   if ( undo_queue->nodes == 0 ) 
      write_ve_msg ( "update_undo_wrap_para - no action" );
   else if ( curr_item->data_type != UT_wrap_para )
      write_ve_msg ( "update_undo_wrap_para - wrong type" );

   else
      curr_item->num_datum = wrapped_lines;

end /* update_undo_wrap_para */

/* .page */

void push_undo_macro ( cmd_ix, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for execution of a macro. To allow  *
 * an entire macro to be "undone" in one command, the undo action created   *
 * here creates its own undo queue, and substitutes it for the main undo    *
 * queue. This ensures that all undo actions from commands executed by the  *
 * macro collect in one place and can be undone as a group by the           *
 * undo_macro function.                                                     *
 *                                                                          *
 ****************************************************************************/

int    cmd_ix;                  /* The command pushing the undo             */
int    line_num;                /* The line number of the line it was on    */
int    cx, cy;                  /* The cursor x,y at the time of the undo   */

begin
   register struct str_undo_item *curr_item;
   register struct node *deleted_line, *undo_line;
   register struct file_ctl *macro_undo_queue;

   curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_macro );

   macro_undo_queue     = (struct file_ctl *) ve_alloc ( sizeof(struct file_ctl) );
   curr_item->undo_data = (char *) macro_undo_queue;
      
   /* Init the macro undo queue */

   init_list ( macro_undo_queue );
   macro_undo_queue->file_format = 'n';    /* file_format used as "flushed" */
                                           /* field: 'n' = not-flushed      */

   /* Now queue the item on the main undo queue and switch the undo queue   */
   /* to the one we have just created. All undo actions from the macro's    */
   /* execution will accumulate on that queue.                              */

   queue_undo_item ( curr_item );
   undo_queue->length++;

   /* Push the current undo queue pointer onto the undo queue stack, and    */
   /* replace it with the new one                                           */

   push ( &undo_queue_stack, undo_queue );
   undo_queue = macro_undo_queue;

end /* push_undo_macro */

/* .page */

void complete_push_undo_macro ()

/*****************************************************************************
 * Complete the push of an undo action for an executing macro. This function *
 * is called when macro execution completes. It resets the undo queue back   *
 * to the global ve_undo_queue. It also checks to see if the macro undo      *
 * queue was flushed during macro execution. If it was, the queue is         *
 * considered to be incomplete, and anything that was added after the last   *
 * flush is itself flushed. If the user attempts an undo of the macro, they  *
 * will get a message saying that it couldn't be undone because it used      *
 * commands for which undo was not supported.                                *
 *****************************************************************************/

begin
   if ( undo_queue == &ve_undo_queue )
   begin
      /* WHOOPS! This shouldn't have happened. We are calling the completion */
      /* of a macro undo, which means that a macro has just finished         */
      /* executing. During macro execution, the undo queue should be the     */
      /* undo queue for the macro's undo item. Instead, it appears to be the */
      /* global queue. Serious trouble. Inform that user and clean up.       */

      write_ve_msg ( "Undo Macro - Wrong Queue" );
      flush_undo_queue ();
      return;
   end

   if ( undo_queue->file_format == 'f' )
   begin
      /* Whoops, the user did SOMETHING during their macro that wasn't       */
      /* supported by an undo action. So, flush the whole thing...           */

      flush_undo_queue ();
   end

   /* OK, the fun is over. Put the original undo queue back in place - hope  */
   /* it enjoyed it's brief vacation!                                        */

   undo_queue = (struct file_ctl *) pop ( &undo_queue_stack );

   if ( undo_queue == NULL )
   begin
      /* YaW - Yet another Whoops. This should NEVER have happened - the     */
      /* undo queue stack was empty. Yikes! Restore the main queue and       */
      /* complain at the user.                                               */

      undo_queue = &ve_undo_queue;
      write_ve_msg ( "Undo Macro - No Undo Queue" );
   end

end /* complete_push_undo_macro */

/* .page */

void push_undo_column_paste ( cmd_ix, col_block, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the paste of a column block *
 * (rectangular area of text) into the file, at the specified line number   *
 * and cx. Each column paste undo action is stored as its own undo item.    *
 *                                                                          *
 ****************************************************************************/

int    cmd_ix;                  /* The command pushing the undo             */
struct file_ctl *col_block;     /* The column block being pasted into file  */
int    line_num;                /* The line number the paste starts at      */
int    cx, cy;                  /* The cursor x,y at the time of the paste  */

begin
   register struct file_ctl      *undo_block;  /* List for undo lines       */
   register struct node          *col_line;    /* A line of column block    */
   register struct node          *undo_line;   /* Its copy in undo block    */
   register struct str_undo_item *curr_item;   /* Current undo item         */

   curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_column_paste );

   undo_block  = (struct file_ctl *) ve_alloc ( sizeof(struct file_ctl) );
   init_list ( undo_block );
   curr_item->undo_data = (char *) undo_block;

   /* Now simply copy the lines from the column block to the undo block.    */
   /* By saving the contents that are pasted in, we can delete them later.  */

   col_line = col_block->fwd_link;

   while ( col_line != NULL )
   begin
      undo_line = (struct node *) ve_alloc (col_line->length + DES_SIZE + 1);
      movb ( &col_line->length, &undo_line->length, col_line->length + sizeof(int));
      send_node ( undo_block, undo_line );

      col_line = col_line->fwd_link;
   end

   /* Now queue the item on the undo queue and we are done               */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_column_paste */

/* .page */

void push_undo_column_delete ( cmd_ix, file_area, line_num, cx, cy )

/****************************************************************************
 *                                                                          *
 * This function creates an undo action for the the delete of column(s) of  *
 * text from the file. The rectangular area containing the column(s) is     *
 * described by the file_area structure. Each undo of a column delete uses  *
 * its own undo item.                                                       *
 *                                                                          *
 ****************************************************************************/

int    cmd_ix;                  /* The command pushing the undo             */
struct t_file_area *file_area;  /* Description of area to be deleted        */
int    line_num;                /* The line number the area starts on       */
int    cx, cy;                  /* The cursor x,y at the time of the del    */

begin
   register struct str_undo_item *curr_item;
   register struct file_ctl *undo_block;
   register char   temp_char;

   curr_item = create_undo_item ( cmd_ix, line_num, cx, cy, UT_column_delete );

   undo_block  = (struct file_ctl *) ve_alloc ( sizeof(struct file_ctl) );
   init_list ( undo_block );
   curr_item->undo_data = (char *) undo_block;

   /* Now simply copy the lines from the file area to the undo block. This  */
   /* saves a copy of what is about to be deleted, so that we can restore   */
   /* it later.                                                             */

   temp_char = file_area->parm1; /* Indicate undo system is the caller      */
   file_area->parm1 = 'u';       /* Tells it "copy", and "no messages"      */

   column_copy_delete ( undo_block, file_area );

   file_area->parm1 = temp_char; /* Restore parm1 to pre-call value         */

   /* Now queue the item on the undo queue and we are done                  */

   queue_undo_item ( curr_item );
   undo_queue->length++;

end /* push_undo_column_delete */

/* .page */

void undo_del_char ( curr_item )

/****************************************************************************
 * Undo a Delete or Rubout command by moving to the location of the         *
 * deletion, and then calling visual_processor with the deleted character,  *
 * exactly as if it had been typed in again.                                *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   int curr_ve_mode;         /* VE's mode at time of the undo               */
   char deleted_char;        /* The deleted char to be re-inserted          */
   BOOL ai_state;            /* Auto indent state                           */

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action). If we are undoing a Rubout,  */
   /* we move the cursor left by the number of stored actions, as the       */
   /* cursor moves left on each rubout, but the cx in the item is the cx of */
   /* the very first of the Rubouts.                                        */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   if ( curr_item->cmd_ix == rubout_ix )
      curr_screen.cx = curr_item->cx - curr_item->num_datum + 1;
   display_new_screen ( curr_screen.cx );

   /* Force VE into insert mode, so that the undeleted character will be    */
   /* inserted, and then call the visual_processor, exactly as if the user  */
   /* had just typed this character                                         */

   curr_ve_mode = ve_mode;          /* Save the current VE mode             */
   ve_mode = M_INSERT;              /* Go into INSERT mode                  */
   ai_state = options.auto_indent;  /* Temporarily disable auto indent      */
   options.auto_indent = FALSE;

   deleted_char = curr_item->undo_data[curr_item->num_datum - 1];
   visual_processor ( deleted_char );
   if ( curr_item->cmd_ix == del_char_ix )
      cursor_left_cmd ();

   options.auto_indent = ai_state;  /* Restore auto indent state            */
   ve_mode = curr_ve_mode;          /* Restore the current mode             */

   /* Decrement the count of available undo actions, both globally and in   */
   /* this item                                                             */

   undo_queue->length--;
   curr_item->num_datum--;

   /* Check if the current undo item is finished. If it is, free it.        */
   /* Since the next undo could conceivably move us to a new line, update   */
   /* the current one with the changes to date.                             */

   if ( curr_item->num_datum == 0 )
   begin
      update_curr_line ();

      curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
      free_undo_item ( curr_item );
   end

end /* undo_del_char */

/* .page */

void undo_del_right ( curr_item )

/****************************************************************************
 * Undo a DeleteRight command by moving to the location of the deletion,    *
 * and reinserting the deleted characters.                                  *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   register int data_size, start_ix;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   /* Make sure that the line buffer is loaded with the current line, and   */
   /* then append the deleted block of chars to it                          */

   load_line_buf ();
   data_size = curr_item->num_datum;

   movb ( curr_item->undo_data, &line_buf.text[line_buf.length], data_size );

   /* Update the line on screen, then the line size and we are done!        */

   start_ix  = line_buf.length;
   line_buf.length += data_size;
   writeln ( line_buf.text, line_buf.length, start_ix );
   update_curr_line ();

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_del_right */

/* .page */

void undo_del_line ( curr_item )

/*****************************************************************************
 * Undo a delete line command by moving to the location of the deleted line, *
 * issuing ENTER to create a new line, linking the previously deleted line   *
 * back into the file, and finally, writing that line out on the display.    *
 *****************************************************************************/

struct str_undo_item *curr_item;

begin
   int curr_ve_mode;              /* VE's mode at time of the undo          */
   int actual, status;            /* Status of linked list operation        */
   struct node *deleted_line;     /* The deleted char to be re-inserted     */
   struct node *line_from_enter;  /* Blank line from the ENTER              */

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line = goto_line_num ( curr_item->line_num );
   display_new_screen ( curr_screen.cx );

   curr_line = curr_line->bwd_link;  /* Insert ahead of current line        */

   if ( curr_item->attributes == UA_end_of_file )
   begin
      /* The deleted line was the last line of the file at the time when it */
      /* was deleted. Hence, we must insert AFTER the current line, not     */
      /* before.                                                            */

      curr_line = curr_line->fwd_link;

      /* If we are not on the last line of the display, simply move done a  */
      /* line - else, shift the display down by one line by deleting the    */
      /* the top line of the display and then returning to our (cx, cy)     */

      if ( curr_screen.cy != lines - 1 ) 
         ti_move_cursor ( curr_screen.cx, ++curr_screen.cy );
      else
         ti_delete_line ( 2 );

   end /* the deleted line was the last line of the file */

   /* Now create a blank line on the display for the newly re-inserted line */
   /* If we are on the last line of the display now, just clear it, else    */
   /* insert a new line.                                                    */

   if ( curr_screen.cy == lines - 1 )
      ti_clear_to_eol ( lines-1 );
   else
      ti_insert_line ( curr_screen.cy );

   /* Now unlink the previously deleted line from the undo_data and link it */
   /* into the file                                                         */

   deleted_line = (struct node *) curr_item->undo_data;
   curr_item->undo_data = (char *) deleted_line->fwd_link;
   add ( &curr_file, curr_line, deleted_line, &status );
   curr_line = deleted_line;

   /* Finally, update the screen by writing out the new current line,       */
   /* updating the current screen data structure (in case we changed the    */
   /* top or bottom line of the screen) and restoring the cursor x position */

   writeln ( curr_line->text, curr_line->length, 0 );
   actual = update_curr_screen ( curr_screen.cy );
   curr_screen.cx = curr_item->cx;

   /* Decrement the count of available undo actions, both globally and in   */
   /* this item                                                             */

   undo_queue->length--;
   curr_item->num_datum--;

   /* Check if the current undo item is finished. If it is, free it.        */

   if ( curr_item->num_datum == 0 )
   begin
      curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
      free_undo_item ( curr_item );
   end

end /* undo_del_line */

/* .page */

void undo_del_block ( curr_item )

/****************************************************************************
 * Undo a Block Delete or a CTL-X type LineDelete. Do this very, very       *
 * cleverly, by saving off the block buffer's control block, setting up the *
 * deleted block in it, and then calling block_get. Restore the block       *
 * buffer's control block afterwards and we are done.                       *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   struct file_ctl temp_block;   /* Holding spot for the block buffer       */
   struct file_ctl *undo_block;  /* Control header for deleted lines        */

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   update_curr_line ();  /* block_get inserts above the curr_line           */

   /* Save off the current block buffer and copy the undo block into it     */

   movb ( &line_block,          &temp_block, sizeof(temp_block) );
   movb ( curr_item->undo_data, &line_block, sizeof(line_block) );
  
   /* Undo the original block delete by pasting back in. The block_get      */
   /* takes care of the screen updating.                                    */

   block_get (); 

   /* Restore the original line buffer to clean up                          */

   movb ( &temp_block, &line_block, sizeof(line_block) );

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_del_block */

/* .page */

void undo_substitute ( curr_item )

/****************************************************************************
 * Undo a Substitute/Replace command by moving to the site of the original  *
 * substitution, and replacing the original characters.                     *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   struct str_subs_undo *subs_undo;
   struct node temp;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   /* Make sure that the line buffer is loaded with the current line        */

   load_line_buf ();

   subs_undo = (struct str_subs_undo *) curr_item->undo_data;

   /* Undo the substitution by copying the line_buf over to the temp buf,   */
   /* up to the start of the substitution, then appending the original text */
   /* and then apppending to that the text from line buf that follows the   */
   /* substituted text.                                                     */

   movb ( &line_buf.text, &temp.text, curr_item->cx );
   movb ( &subs_undo->orig.text, &temp.text[curr_item->cx], subs_undo->orig.length );
   movb ( &line_buf.text[curr_item->cx + subs_undo->subs.length],
          &temp.text[curr_item->cx+subs_undo->orig.length],
          line_buf.length - curr_item->cx - subs_undo->subs.length );

   temp.length = line_buf.length - subs_undo->subs.length + subs_undo->orig.length;
   movb ( &temp.length, &line_buf.length, temp.length + sizeof(int) );
   update_curr_line ();

   /* Clear to end of line, and then write out the revised part of line     */

   ti_clear_to_eol ( curr_screen.cy );
   writeln ( &temp.text, temp.length, curr_item->cx );

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_substitute */

/* .page */

void undo_block_paste ( curr_item )

/****************************************************************************
 * Undo the paste of a block of lines, by moving to the site of the paste,  *
 * and deleting the lines.                                                  *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   register int i;
   struct node *next_line;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   /* Do the undo by simply deleting the lines and updating the screen      */

   for ( i=0; i < curr_item->num_datum; i++ )
   begin
      next_line = curr_line->fwd_link;

      if ( curr_line != NULL ) 
      begin
         /* Checking for a null line is for safety's sake only. This        */
         /* condition should NEVER occur, since we know in advance how many */
         /* lines were pasted. If we hit a NULL, then the indicated number  */
         /* of lines was not in fact pasted                                 */

         delete ( &curr_file, curr_line, &status );
         free ( curr_line );
      end

      curr_line = next_line;

   end /* for */

   /* Now update the screen and we are done. Check for the case where we    */
   /* had deleted the last line of the file, and move back one if that is   */
   /* the case.                                                             */

   if ( curr_line == NULL ) 
   begin
      curr_line = curr_file.tail;
      curr_screen.cy--;
   end

   curr_screen.cy = update_curr_screen ( curr_screen.cy );
   draw_to_endof_screen ();

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_block_paste */

/* .page */

void undo_del_word ( curr_item )

/****************************************************************************
 * Undo the delete of a word by re-inserting the deteled chars back into    *
 * the line at the position saved during the undo action push.              *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   char *char_buffer;
   struct node temp;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   /* Make sure that the line buffer is loaded with the current line        */

   load_line_buf ();

   char_buffer = curr_item->undo_data;

   /* Undo the word delete by copying the line_buf over to the temp buf,    */
   /* up to the start of the word, then appending the deleted word, and     */
   /* then finally appending the rest of the original line_buf.             */

   movb ( &line_buf.text, &temp.text, curr_item->cx );
   movb ( char_buffer, &temp.text[curr_item->cx], curr_item->num_datum );
   movb ( &line_buf.text[curr_item->cx],
          &temp.text[curr_item->cx+curr_item->num_datum],
          line_buf.length - curr_item->cx );

   temp.length = line_buf.length + curr_item->num_datum;
   movb ( &temp.length, &line_buf.length, temp.length + sizeof(int) );
   update_curr_line ();

   /* Clear to end of line, and then write out the revised part of line     */

   ti_clear_to_eol ( curr_screen.cy );
   writeln ( &temp.text, temp.length, curr_item->cx );

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_del_word */

/* .page */

void undo_ins_char ( curr_item )

/****************************************************************************
 * Undo the insertion of a single char by moving to the location of the     *
 * text insertion. If we are undoing a character typed while in INSERT mode *
 * we undo by deleting the character with the del_char_cmd, exactly as if   *
 * the user had typed the DEL key. If we are undoing a character that was   *
 * typed while in OVERWRITE mode, we undo with the rubout_cmd, exactly as   *
 * if the user had pressed the RUBOUT key.                                  *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   register char *char_buffer;  /* The undo data - an array of cx values    */
   register char curr_char;     /* The char to replace in OVERWRITE         */
   register int cx, i;          /* The current cx to use, and loop counter  */
   register int indent_spaces = 0; /* Leading white space at head of line   */
   struct node *next_line;         /* Line after current line               */

   /* Move the display to the inserted char's cx, cy (which may be off      */
   /* screen at the time of this undo action)                               */

   cx             = curr_item->cx + curr_item->num_datum - 1;
   char_buffer    = curr_item->undo_data;
   curr_char      = char_buffer[curr_item->num_datum - 1];

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = cx;
   display_new_screen ( cx );

   load_line_buf ();

   if ( curr_item->cmd_ix == insert_ix )
   begin
      /* For INSERT mode, to undo, we just now delete the char that we      */
      /* inserted                                                           */

      if ( (line_buf.length > 0) && (cx == line_buf.length) && 
           (options.auto_indent)                               )
      begin
         /* We are at the end of a line - the delete of this char will      */
         /* cause a line_join. Hence, we may need to undo auto indent too.  */
         /* Figure out how much white space we are indented by. That is how */
         /* much auto indent we may need to undo. If the next line is not   */
         /* at least that long, then it was not auto indented and we are OK */

         indent_spaces = white_space ( &line_buf );

         next_line = curr_line->fwd_link;
         if ( next_line != NULL )
            if ( next_line->length < indent_spaces )
               indent_spaces = 0;
      end

      ve_mode = last_ve_mode = M_INSERT;
      delete_char_cmd ();

      if ( indent_spaces > 0 )
         for ( i = 0; i < indent_spaces; i++ )
               delete_char_cmd ();
   end

   else
   begin
      /* For OVERWRITE, we use the RUBOUT command to do the work. Since the */
      /* Rubout command simply restores data from curr_line, we overwrite   */
      /* the relevant character in curr_line and call rubout_cmd.           */

      ve_mode = last_ve_mode = M_OVERWRITE;
      line_buf.text[cx - 1] = curr_line->text[cx - 1] = curr_char;
      rubout_cmd ();
   end

   /* Decrement the count of available undo actions, both globally and in   */
   /* this item                                                             */

   undo_queue->length--;
   curr_item->num_datum--;

   /* Check if the current undo item is finished. If it is, free it.        */
   /* Since the next undo could conceivably move us to a new line, update   */
   /* the current one with the changes to date.                             */

   if ( curr_item->num_datum == 0 )
   begin
      update_curr_line ();

      curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
      free_undo_item ( curr_item );
   end

end /* undo_ins_char */

/* .page */

void undo_wrap_para ( curr_item )

/****************************************************************************
 * Undo the wrap of a paragraph by deleting the newly wrapped lines, and    *
 * re-inserting the previous, unwrapped lines.                              *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   struct file_ctl temp_block;   /* Holding spot for the block buffer       */
   struct file_ctl *undo_block;  /* Control header for deleted lines        */

   register struct node *this_line, *prev_line, *next_line, *last_line;
   register int i, cy;
   int status;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = 0;
   display_new_screen ( curr_screen.cx );

   update_curr_line ();
   this_line = curr_line;
   prev_line = this_line->bwd_link;
   
   /* Start by deleting the newly wrapped lines, both in the file and on    */
   /* on the screen.                                                        */

   for ( i = 0; ((i < curr_item->num_datum) && (this_line != NULL)); i++ )
   begin
      next_line = this_line->fwd_link;

      this_line = delete ( &curr_file, this_line, &status );
      free ( this_line );
      ti_delete_line ( curr_screen.cy );

      this_line = next_line;
   end

   /* Now that the newly wrapped lines have been deleted, paste back in     */
   /* their predecessors                                                    */

   ti_move_cursor ( ti_get_left_cx(), (cy = curr_screen.cy) );
   ti_clear_to_eol ( cy );

   undo_block = (struct file_ctl *) curr_item->undo_data;
   this_line  = undo_block->head;
   last_line  = prev_line;

   while ( this_line != NULL )
   begin
      next_line = this_line->fwd_link;

      add ( &curr_file, last_line, this_line, &status );

      if ( cy <= lines - 1 )
      begin
         if ( cy == lines - 1 )
         begin
            ti_move_cursor ( ti_get_left_cx(), cy );
            ti_clear_to_eol ( cy );
         end
         else
            ti_insert_line ( cy++ );

         writeln ( this_line->text, this_line->length, 0 );
      end

      last_line = this_line;
      this_line = next_line;
   end

   /* We may have messed up curr_screen, so take care of that               */
   
   curr_line = prev_line->fwd_link;
   curr_screen.cy = update_curr_screen ( curr_screen.cy );   

   /* Some final cleanup on the screen. If the number of lines we deleted   */
   /* is greater than the number we inserted, there may be blank lines left */
   /* on the screen that need refilling. Draw to end of screen to do this.  */

   if ( curr_item->num_datum > undo_block->nodes )
      draw_to_endof_screen ();

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete ( undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_wrap_para */

/* .page */

void undo_macro ( curr_item )

/****************************************************************************
 * Undo the effects of a macro execution. During the macro, the commands    *
 * pushed their undo actions onto an undo queue associated with the current *
 * undo item. So, make that undo queue the current undo queue, and then     *
 * repetitively call the undo command until the queue is empty.             *
 * A bit of a violation of structured top down code design, but it is a     *
 * simple and elegant way to achieve the result.                            *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   struct file_ctl *macro_undo_queue, *temp_queue;

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   update_curr_line ();  /* block_get inserts above the curr_line           */

   macro_undo_queue = (struct file_ctl *) curr_item->undo_data;

   if ( macro_undo_queue->length == 0 )
      write_ve_msg ( "Nothing to undo, or unsupported cmds" );

   else
   begin
      temp_queue = undo_queue;
      undo_queue = macro_undo_queue;

      while ( undo_queue->length > 0 )
         undo_cmd ();

      undo_queue = temp_queue;
   end

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_macro */

/* .page */

void undo_column_paste ( curr_item )

/*****************************************************************************
 * Undo a column paste. Do this by iterating through the lines that have     *
 * been pasted into, deleting the same number of characters as are contained *
 * in the undo data, which is a copy of what was pasted into the file.       *
 *****************************************************************************/

struct str_undo_item *curr_item;

begin
   register struct node *paste_line; /* The contents pasted into file_line  */
   register struct node *file_line;  /* The line they were pasted into      */
   struct file_ctl *undo_block;      /* Control header for pasted lines     */
   register int cx1, cx2;            /* Start and end columns for deletion  */
   register int cy;                  /* Line co-ord for line being done     */
   register int lines_done = 0;      /* Lines modified                      */
   register int cols_done = 0;       /* Maximum columns removed             */
   char resp[42];                    /* Message buffer                      */

   update_curr_line ();

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   undo_block = (struct file_ctl *) curr_item->undo_data;
   paste_line = undo_block->head;
   file_line  = curr_line;

   cx1 = curr_item->cx;
   cy  = curr_screen.cy;

   while ( paste_line != NULL )
   begin
      /* Now simply examine each line that was pasted in. If there was      */
      /* content pasted, delete it. Then move on to next line until done    */

      lines_done++;
      cols_done = (paste_line->length > cols_done) ? paste_line->length : cols_done;

      if ( (cx2 = paste_line->length) > 0 )
      begin
         cx2 = cx1 + cx2 - 1;
         delete_chars ( file_line, cx1, cx2, cy++ );
      end

      file_line  = file_line->fwd_link;
      paste_line = paste_line->fwd_link;
   end

   ti_move_cursor ( curr_screen.cx, curr_screen.cy );

   sprintf ( resp, "%d %s %d %s", cols_done, "columns x", lines_done, "lines deleted" );
   write_ve_msg ( resp );

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_column_paste */

/* .page */

void undo_column_delete ( curr_item )

/****************************************************************************
 * Undo a column delete. Do this by moving to where the column delete       *
 * happened, and then doing a column paste of the undo data, which is a     *
 * copy of what was deleted.                                                *
 ****************************************************************************/

struct str_undo_item *curr_item;

begin
   struct file_ctl    *undo_block;  /* Control header for deleted lines     */
   struct t_file_area file_area;    /* Rectangle definition for pasting     */

   update_curr_line ();  /* block_get inserts above the curr_line           */

   /* Move the display to the deleted item's cx, cy (which may be off       */
   /* screen at the time of this undo action)                               */

   curr_line      = goto_line_num ( curr_item->line_num );
   curr_screen.cx = curr_item->cx;
   display_new_screen ( curr_screen.cx );

   undo_block = (struct file_ctl *) curr_item->undo_data;

   file_area.first_line   = curr_line;
   file_area.last_line    = curr_line;
   file_area.num_lines    = 1;
   file_area.first_column = curr_screen.cx;
   file_area.last_column  = curr_screen.cx;

   column_paste ( undo_block, &file_area );

   /* Decrement the count of available undo actions and release this undo   */
   /* item - only one action per item for this type of undo.                */

   undo_queue->length--;

   curr_item = (struct str_undo_item *) delete (undo_queue, curr_item, &status );
   free_undo_item ( curr_item );

end /* undo_column_delete */

/* .page */

void undo_cmd ()

/****************************************************************************
 * VE's undo command. This command examines the undo item at the front of   *
 * the undo queue, and calls the appropriate undo function, based on the    *
 * undo type stored in the undo item.                                       *
 ****************************************************************************/

begin
   register int cmd_ix;
   register struct str_undo_item *curr_item;

   /* First check if the undo queue is empty. If it is, stop any further    */
   /* repititions. If the undo queue is empty and this is the first rep,    */
   /* tell the user that the queue is empty. Only do this if this is the    */
   /* first rep, since if it is not, we have already done some undoing, and */
   /* the message would be misleading.                                      */

   if ( undo_queue->nodes == 0 ) 
   begin
      if ( requested_reps > 1 ) 
         requested_reps = 1;                    /* Stop further repetitions */

      if ( curr_rep == 1 )
         write_ve_msg ( "No undo actions available" );

      return;
   end

   undo_state = US_undoing;  /* Set the global undo state                   */

   /* Get the item at the head of the undo queue, look up its index for the */
   /* undo proc dispatch table, and then run the undo procedure.            */

   curr_item = (struct str_undo_item *) undo_queue->head;
   cmd_ix = findb ( &undo_types, (char) curr_item->data_type, NUM_UNDO_CMDS );

   if ( cmd_ix == -1 ) 
      write_ve_msg ( "Unknown Undo Type!" );

   else
      (*undo_procs[cmd_ix])( curr_item );

   /* Reset the global undo state and we are done */

   undo_state = US_monitoring;
   restore_cursor ();

end /* undo_cmd */

