#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  : wordcmd.c
 
                       VERSION : v3.2
 
                       REVISED : 27 Apr 2005
 
                       AUTHOR  : Michael A. Campbell
 
 
 
 
 
 


                          FUNCTIONAL DESCRIPTION
                          ----------------------
 
               This module contains a number of "word" and "word processing"
 
               type commands, such as word_right, word_left, delete_word,
 
               paragraph_up, paragraph_down, and wrap_paragraph.        
 






*/

/*  */

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

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

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

/* Local parse state indicators for get_next_word line parser */ 

#define  PS_INITD             1
#define  PS_END_OF_BUF        2
#define  PS_NO_DELIM_FOUND    3

#define  NOT_A_DELIMITER      -1
#define  IS_A_DELIMITER       0

#define  MAX_WRAP_LINES       256  /* Random selection: could be higher */


#include "../str/vestruct.str"

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

#include "../ext/strprim.ext"
#include "../ext/termif.ext"
#include "../ext/kbdif.ext"
#include "../ext/veutils.ext"
#include "../ext/lnklst.ext"
#include "../ext/cursor.ext"
#include "../ext/modeproc.ext"
#include "../ext/undo.ext"

/*  */

/*************************
 *  LOCAL VARIABLES      *
 *************************/

/* Define the set of characters that defines the delimeters between words  */

static char word_delims[] = { ' ', '(', ')', '.', ',', ';', ':' };

static char wrap_bufr  [2 * MAX_LINE_SIZE];  /* For word wrapping two lines */
static char wrap_bufr2 [2 * MAX_LINE_SIZE];  /* For word wrapping two lines */

/*  */

/*************************
 *  LOCAL PROCEDURES     *
 *************************/

static void get_next_word ( struct node *line, struct node *next_word, 
                            int *idx )

/****************************************************************************
 * Starting at offset idx, find the next word in the current line, and      *
 * return it in next_word.text. The length is returned in next_word.length. *
 * If the line is empty, or exhausted, a nil (length = 0) next word is      *
 * returned.                                                                *
 *                                                                          *
 * NOTE: *idx should be init'd to 0 prior to the first call to this func    *
 * with a new line.                                                         *
 *                                                                          *
 * This routine is loosely based on the UDI_EMULATOR dq$get$argument        *
 * procedure in DBG68K.                                                     *
 ****************************************************************************/
{
   char next_char;     /* The next char to be processed                    */
   BOOL done;          /* Have we found the end of the next word?          */

   next_word->length = 0;
   done              = FALSE;
   
   while ( *idx < line->length && !done ) 
   {
      next_char = line->text[(*idx)++];

      /* If this is an ASCII blank, skip it, unless we already have a word */
      /* in the buffer, in which case, this will be the terminating delim. */

      if ( ( next_char != ' ' ) || ( next_word->length != 0 ) )
      {
         if ( next_char != ' ' )
            next_word->text[next_word->length++] = next_char;
         else
            done = TRUE;
      }

   } /* while *idx < line->length */

   next_word->text[next_word->length] = '\0';

} /* get_next_word */

/*  */

static int compress_line ( struct node *line )

/****************************************************************************
 * In place, compress all of the redundant spaces out of the supplied line. *
 * Return the number of space delimited words.                              *
 ****************************************************************************/
{
   int i = 0;               /* Loop counter                                 */
   int j = 0;               /* Loop counter                                 */
   int word_count = 0;      /* Number of space delimited words              */

   struct node compressed_line;     /* Compressed version of "line"         */

   for ( i = 0; i < line->length; i++ )
   {
      if ( line->text[i] != ' ' )
      {
         /* This character is not a blank, so copy it over. In addition,   */
         /* if its predecessor was a blank (or this is the first char in   */
         /* the output line, increment the word counter.                   */

         compressed_line.text[j++] = line->text[i];

         if ( j == 1 )
            word_count++;
         else
            if ( compressed_line.text[j - 2] == ' ' )
               word_count++;

      } /* if this char not a blank */
      else
      {
         /* This char is a blank, so only copy it over if its predecessor */
         /* was not a blank. If we have not output any chars yet, do      */
         /* nothing. This strips all leading spaces off of the input line */

         if ( j != 0 )
            if ( compressed_line.text[j - 1] != ' ' )
               compressed_line.text[j++] = line->text[i];

      } /* this char is a blank */

   } /* for i */

   /* Now, copy the compressed line over the original, and we are done */

   compressed_line.text[j] = '\0';
   strcpy ( line->text, compressed_line.text );
   line->length = j;

   return ( word_count );

} /* compress_line */

/*  */

static void list_item_adjust ( struct node *src_line, struct node *dst_line,
                               int *src_idx,          int *dst_idx, 
                               int para_indent,       BOOL shift_left )

/****************************************************************************
 * This function implements VE's text wrapping list item support. A         *
 * graph that is a list item looks something like:                          *
 *                                                                          *
 *        xy) para line 1                                                   *
 *            para line 2                                                   *
 *            para line 3                                                   *
 *                                                                          *
 * The key feature is that all lines except the first have a consistent     *
 * indent, and that the second word of the first line begins at that        *
 * indent. This function detects this situation, and if found, copies the   *
 * the source line up to the beginning of the "para line 1" text over to    *
 * the destination line. It then adjusts the indices into both lines. This  *
 * makes the source line appear to have been processed up to that point,    *
 * and thus causes the usual processing (compaction of spacing) to be       *
 * skipped. This preserves the visual structure of the list item.           *
 ****************************************************************************/
{
   /* Start by finding the index of the start of the second word. Do this   */
   /* by finding the start of the first word, the end of the first word,    */
   /* and then the start of the second word.                                */

   int idx1 = 0;     /* Index of first char of first word on line           */
   int idx2 = 0;     /* Index of first blank after first word               */
   int idx3 = 0;     /* Index of first char of second word on line          */
   int i;            /* Loop counter                                        */

   idx1 = findb_not ( src_line->text,       ' ', src_line->length       );
   if ( idx1 >= 0 )
      idx2 = findb     (&src_line->text[idx1], ' ', src_line->length - idx1);
   if ( idx2 >= 0 )
   {
      idx2 += idx1;
      idx3 = findb_not (&src_line->text[idx2], ' ', src_line->length - idx2);
   }
   if ( idx3 >=0 )
      idx3 += idx2;

   /* Check for problems */

   if ( idx1 == -1 || idx2 == -1 || idx3 == -1 )
      return;

   /* Now we check to see if we have a list item "situation" */

   if ( para_indent == idx3 )
   {
      /* This sentence is the first sentence of a list item. Copy the item */
      /* preamble over to the destination buffer.                          */

      for ( i = 0; i < para_indent; i++ )
         dst_line->text[i] = src_line->text[i];

      dst_line->length            = para_indent;
      dst_line->text[para_indent] = '\0';
      
      *src_idx = *dst_idx = para_indent;

      if ( shift_left )
      {
         src_line->length -= para_indent;
         *src_idx          = 0;

     for ( i = 0; i < src_line->length; i++ )
            src_line->text[i] = src_line->text[i + para_indent];
      }
   }

} /* list_item_adjust */


/*  */
 
static int get_indent ( struct node *line )

/****************************************************************************
 * This function scans the supplied line, and returns the indent level of   *
 * the line, defined as the index (zero based) of the first non blank char  *
 * on the line. If there is nothing but spaces on the line, 0 is returned.  *
 ****************************************************************************/
{
   int i      = 0;   /* General loop counter                                */
   int indent = 0;   /* The return value                                    */
   
   for ( i = 0; i < line->length; i++ )
      if ( line->text[i] != ' ' )
      {
         indent = i;
         break;
      }

   return ( indent );

} /* get_indent */

/*  */

static void right_justify_line ( struct node *line, int para_indent )

/****************************************************************************
 * This function right justifies the supplied line to the column indicated  *
 * by options.right_margin.                                                 *
 *                                                                          *
 * WARNING: because the justification happens "in place", the supplied line *
 *          MUST be as long as, or longer than, the current right margin.   *
 *          If this is not the case, a memory overwrite will occur!!        *
 ****************************************************************************/
{
   int word_count;               /* The number of words on the line         */
   int words_done = 0;           /* Number of words processed               */
   int indent;                   /* Indent of this line                     */

   int spaces_needed;            /* Total spaces needed                     */
   int spaces_per_word;          /* Number of spaces per word needed        */
   int spaces_added;             /* Number of spaces added over all words   */
   int extra_spaces;             /* Number of spaces still left after above */

   int    src_ix = 0;            /* Working index into source line          */
   int    jst_ix = 0;            /* Working index into output line          */
   int    i;                     /* General loop counter                    */

   struct node *next_word;       /* Temp buffer for the next word           */
   struct node *jst_line;        /* The destination line                    */

   /* Perform some basic sanity checking before proceeding. If the line is  */
   /* already as long as, or longer than, the right margin, nothing can be  */
   /* done. If the line is blank, there is nothing that can be done.        */

   if ( ( line->length >= options.right_margin ) ||
        ( line->length == 0                    )    )
      return;

   next_word = (struct node *) ve_alloc ( sizeof(struct node) );
   setb ( 0, next_word, sizeof(struct node) );

   jst_line  = (struct node *) ve_alloc ( sizeof(struct node) );
   setb ( ' ', jst_line->text, sizeof(jst_line->text) );

   /* Set up the output line with the same indentation as the input line  */

   indent = get_indent ( line );

   jst_line->text[indent] = '\0';
   jst_line->length       = indent;

   list_item_adjust ( line, jst_line, &src_ix, &jst_ix, para_indent, TRUE );

   /* Eliminate all redundant spaces in the line, prior to starting. Then  */
   /* figure out how many spaces need to be added per word, to make        */
   /* the line reach the right margin. Since this is not likely to be an   */
   /* integer value, figure out how many extra spaces must be added in     */
   /* addition to this to achieve the desired result.                      */

   word_count    = compress_line ( line );
   spaces_needed = options.right_margin - (line->length + jst_line->length);

   spaces_per_word = spaces_needed / (word_count - 1);
   spaces_added    = (word_count - 1) * spaces_per_word++;
   extra_spaces    = spaces_needed - spaces_added;

   while ( words_done++ <= word_count )
   {
      get_next_word ( line, next_word, &src_ix );

      /* The next check is "idiot proofing" - should never hit it */

      if ( next_word->length == 0 )
         break;

      /* Got a word. Add it to the output line */

      strcat ( jst_line->text, next_word->text );

      jst_line->length += next_word->length;

      if ( spaces_per_word > 0 && words_done < word_count )
      {
         /* Need to add some additional spaces, to achieve justification. */
         /* Add as many as needed for each word.                          */

         for ( i = 0; i < spaces_per_word; i++ )
            jst_line->text[jst_line->length++] = ' ';
      }

      if ( extra_spaces > 0 && words_done < word_count )
      {
         /* If we need to add extra spaces, over and above the fixed       */
         /* number per word, add 1 after each word, starting from the left */
         /* and working to the right. We are guaranteed to not need more   */
         /* than one such space per word, due to the way spaces_per_word   */
         /* is calculated. The check for para_indent is support of HP-UX   */
         /* "adjust" like bulletted list justification.                    */

         jst_line->text[jst_line->length++] = ' ';
         extra_spaces--;
      }

      jst_line->text[jst_line->length] = '\0';

   } /* while words_done < word_count */

   /* Now that jst_line contains the right justified line, copy it over the */
   /* supplied line. WARNING!! If the supplied line is not as large, this   */
   /* will copy right off the end of it, corrupting whatever is next in     */
   /* memory. User beware!!                                                 */

   line->length = jst_line->length;
   strcpy ( line->text, jst_line->text );

   free ( jst_line  );
   free ( next_word );

} /* right_justify_line */

/*  */

static void wrap_range ( struct node *range_start, struct node *range_end )

/****************************************************************************
 *  Wrap all paragraphs found between the starting and ending lines. This   *
 *  involves compressing all unneeded spaces, and then reformatting the     *
 *  lines so that the maximum number of words is placed on each line.       *
 *  Optionally, the lines are right justified (if options.right_justify is  *
 *  TRUE.                                                                   *
 *                                                                          *
 *  This text wrapper has special support for ASCII bulletted lists, as per *
 *  the HP-UX "adjust" command.                                             *
 ****************************************************************************/
{
   int curr_indent;                /* Indent of current line                */
   int indent_1st;                 /* Indent of the first line              */
   int indent_2nd;                 /* Indent of the second (and more) lines */
   int num_2nd_indents;            /* Number of indent levels in para       */
   int para_indent;                /* The indent level to be used for para  */

   int  first_blank;               /* Index of first blank on line          */
   BOOL line_too_dense = FALSE;    /* Is the line too dense to wrap?        */

   struct node *src_line;          /* Line we are reading text from         */
   struct node *dst_line;          /* Line we are writing wrapped text to   */
   struct node *new_word;          /* Temporary word parsing buffer         */

   struct node *pnode;             /* Line just prior to wrap range         */
   struct node *nnode;             /* Next line after wrap range            */
   struct node *cnode;             /* Current line in linked list           */

   struct file_ctl wrap_block;     /* The list "anchor" for the wrapped txt */

   int  src_ix = 0;                /* The index into the source line        */
   int  dst_ix = 0;                /* The index into the destination line   */
   int  range_lines = 1;           /* The number of lines in the range      */
   int  line_size;                 /* Allocation size of wrapped line       */

   /* First, some simplicity checking. If the supplied range is only one    */
   /* line long, and it is shorter than the right margin, we are done.      */

   if ( ( range_start == range_end                    ) &&
        ( range_start->length <= options.right_margin )    )
      return;

   /* Save the indent of the first line (in case this is a "list item"),    */
   /* then examine the indents of all other lines. If all other lines have  */
   /* a consistent indent, but one which is different from the first line,  */
   /* use the first lines indent for the first line of wrapped text, and    */
   /* the indent from all other lines for all remaining text.               */

   indent_1st = get_indent ( range_start );

   indent_2nd = indent_1st;                        /* For 1 line ranges     */
   num_2nd_indents  = 0;

   cnode = range_start->fwd_link;

   while ( cnode != range_end->fwd_link )
   {
      curr_indent = get_indent ( cnode );

      if ( curr_indent != indent_2nd )
      {
         indent_2nd = curr_indent;
         num_2nd_indents++;
      }

      /* Since we are passing over the whole range to be wrapped, lets     */
      /* examine each line and ensure it is wrappable. If any line exists  */
      /* which has an unbroken string of chars up to the right margin, we  */
      /* can't wrap that trash - might as well quit.                       */

      if ( cnode->length >= options.right_margin )
      {
         first_blank = findb ( cnode->text, ' ', cnode->length );
         if ( ( first_blank > options.right_margin ) || ( first_blank == -1 ) )
            line_too_dense = TRUE;
      }

      range_lines += 1;
      cnode        = cnode->fwd_link;

   } /* while cnode ... */

   if ( line_too_dense )
   begin
      write_ve_msg ( "Paragraph too dense to wrap" );
      return;
   end

   para_indent = (num_2nd_indents == 1) ? indent_2nd : indent_1st;

   if ( options.left_margin >= 0 ) 
   begin
      /* The user has defined a left margin, so use this as the left edge   */
      /* vs. the automatically determined values above. Adjust the values   */
      /* relative to the left margin                                        */

      if ( para_indent > indent_1st )
      begin
         /* Bullet'd list style. The second and succeeding lines are        */
         /* indented further than the first line. Set the first line's      */
         /* indent to the left margin and the others relative to that.      */

         para_indent = options.left_margin + para_indent - indent_1st;
         indent_1st  = options.left_margin;
      end

      else
      begin
         /* Letter paragraph style. The first line is indented more than    */
         /* the remaining lines. Set the second and succeeding lines to be  */
         /* indented to the left margin, and the first line relative to it  */

         indent_1st  = options.left_margin + indent_1st - para_indent;
         para_indent = options.left_margin;
      end

   end

   /* OK, now that we've got the indent thing figured out, lets set up the  */
   /* wrap block anchor, and init some working vars.                        */

   init_list ( &wrap_block );

   new_word = (struct node *) ve_alloc ( sizeof(struct node) );
   setb ( 0, new_word, sizeof(struct node) );

   /* Save the position of the wrapped text within the file */

   pnode = range_start->bwd_link;
   nnode = range_end->fwd_link;

   /* Set up the initial source and destination lines */

   line_size = options.right_margin + options.left_margin + DES_SIZE + 32;

   src_line = range_start;
   dst_line = (struct node *) ve_alloc ( line_size );

   setb ( ' ', (char *) dst_line, line_size );
   dst_line->text[indent_1st] = '\0';
   dst_line->length           = indent_1st;

   dst_ix = indent_1st;

   list_item_adjust (src_line, dst_line, &src_ix, &dst_ix, para_indent, FALSE);

   do
   {
      /* For each line, parse out each word, and add it to the destination  */
      /* line, until that line is full. Then, optionally right justify the  */
      /* line. Finally, add it to the wrap block.                           */

      get_next_word ( src_line, new_word, &src_ix );

      while ( new_word->length > 0 )
      {
         if ( ( dst_ix + new_word->length >= options.right_margin ) &&
              ( dst_ix > para_indent                              )    )
         {
            /* The word will not fit on the current line. We are done with */
            /* this line. Release it to the wrap block, and create a new   */
            /* working line.                                               */

            dst_line->length = --dst_ix;     /* Subtract trailing space    */
            dst_line->text[dst_ix] = '\0';
            
            if ( options.right_justify )
               right_justify_line ( dst_line, para_indent );

            send_node ( &wrap_block, dst_line );
            
            /* Allocate and prepare a new destination line                  */
            
            dst_line = (struct node *) ve_alloc ( line_size );

            setb ( ' ', (char *) dst_line, line_size );
            dst_line->length            = para_indent;
            dst_line->text[para_indent] = '\0';

            dst_ix = para_indent;

         } /* The word will not fit */
         
         /* Add the word to the output buffer, and append a space to it */

         strcat ( dst_line->text, new_word->text );
         strcat ( dst_line->text, " " );

         dst_ix += ( new_word->length + 1 );

         get_next_word ( src_line, new_word, &src_ix );

      } /* while new_word->length > 0 */
      
      /* Advance to the next line of text to be wrapped, and release the    */
      /* memory held by the last unwrapped line.                            */
      
      cnode    = src_line;
      src_line = src_line->fwd_link;
      src_ix   = 0;
      
      free ( cnode );
      
   }
   while ( src_line != nnode );
   
   /* Now that we have completely processed the unwrapped range, take care  */
   /* of the last wrap line buffer.                                         */
   
   if ( dst_ix > para_indent ) 
   {
      /* We do have some trailing text in this buffer */
      
      dst_line->length       = dst_ix;
      dst_line->text[dst_ix] = '\0';

      send_node ( &wrap_block, dst_line );
   }
   else
      free ( dst_line );
      
   /* Now, link the wrap block into the file, in place of the old set of    */
   /* unwrapped text lines.                                                 */
   
   cnode           = wrap_block.head;         /* Link in the start          */
   pnode->fwd_link = cnode;
   cnode->bwd_link = pnode;
   if ( pnode == (struct node *) &curr_file )
      curr_file.head = cnode;
   
   cnode           = wrap_block.tail;         /* Link in the end            */
   cnode->fwd_link = nnode;
   if ( nnode != NULL )
      nnode->bwd_link = cnode;
   else
      curr_file.tail = cnode;

   curr_file.nodes = curr_file.nodes - range_lines + wrap_block.nodes;
         
   /* Do some housekeeping, and we are done!                                */
   
   free ( new_word );
   
} /* wrap_range */
   
/*  */

static int find_next_word_end ( struct node *line, int line_cx )

/****************************************************************************
 *  Starting at offset line_cx, search the current line for the end of the  *
 *  next (or current) word. Return the offset into the line of that end. If *
 *  no end is found, the current line_cx is returned.                       *
 ****************************************************************************/
{
   register int  cx;         /* Local copy of line_cx used in the search    */
   register int  delim_ix;   /* Index of current char in set of word delims */
   register char this_char;  /* The char currently being examined           */
   BOOL     found = FALSE;   /* Loop control boolean                        */
   
   cx = line_cx;
   
   if ( cx >= line->length )
   {
      /* If the cursor is beyond the end of the line, set it to the last   */
      /* position on the line, and the rest will fall through.             */
      
      cx = line->length - 1;
   }

   /* Find the beginning of a word, defined as the first non-delimeter   */
   /* character encountered.                                             */
   
   while ( !found && cx < line->length )
   {
      this_char = line->text[cx++];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix == NOT_A_DELIMITER )
         found = TRUE;
   }
   
   if ( found )
   {
      cx--;
      found = !found;
   }
   
   /* Find the end of this word, defined as the first delimeter char we  */
   /* encounter, and then back up by one.                                */
   
   while ( !found && cx < line->length )
   {
      this_char = line->text[cx++];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix >= IS_A_DELIMITER )
      {
         /* Found the end of the word or ran off the end of the line. In    */
         /* either case, back up by one, so that we are on the last char    */
         /* and call it quits.                                              */
         
         cx -= 2;             /* 2 because you are one past the end and     */
         found = TRUE;        /* post incremented                           */
      }
      else if ( cx == line->length )
      {
         cx--;
         found = TRUE;
      }
   }

   return ( found ? cx : line_cx );
   
} /* find_next_word_end */

/*  */

static int find_next_word_start ( struct node *line, int line_cx )

/******************************************************************************
 *  Starting at offset line_cx, search the current line for the start of the  *
 *  next word. Return the offset into the line of that end. If no start is    *
 *  found, the current line_cx is returned.                                   *
 ******************************************************************************/
{
   register int  cx;           /* Local copy of line_cx used in the search    */
   register int  delim_ix;     /* Index of current char in set of word delims */
   register char this_char;    /* The char currently being examined           */
   BOOL found = FALSE;         /* Loop control boolean                        */
   
   cx = line_cx;
   
   if ( cx >= line->length )
   {
      /* If the cursor is beyond the end of the line, set it to the last   */
      /* position on the line, and the rest will fall through.             */
      
      cx = line->length - 1;
   }

   /* Find an interword space, defined as the first delimeter character    */
   /* encountered.                                                         */
   
   while ( !found && cx < line->length )
   {
      this_char = line->text[cx++];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix >= IS_A_DELIMITER )
         found = TRUE;
   }
   
   if ( found )
   {
     cx--;
     found = !found;
   }
   
   /* Find the start of the next word, defined as the first non delimeter   */
   /* char we encounter, and then back up by one.                           */
   
   while ( !found && cx < line->length )
   {
      this_char = line->text[cx++];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix == NOT_A_DELIMITER )
      {
         /* Found the end of the word or ran off the end of the line. In    */
         /* either case, back up by one, so that we are on the last char    */
         /* and call it quits.                                              */
         
         found = TRUE;
         cx--;
      }
   }

   return ( found ? cx : line_cx );
   
} /* find_next_word_start */

/*  */

static int find_prev_word_start ( struct node *line, int line_cx )

/*****************************************************************************
 *  Starting at offset line_cx, search backwards in the current line for the *
 *  the start of the previous or current word. Return the offset into the    *
 *  line of that start. If no previous found, return index of start of line. *
 *****************************************************************************/
{
   register int  cx;          /* Local copy of line_cx used in the search    */
   register int  delim_ix;    /* Index of current char in set of word delims */
   register char this_char;   /* The char currently being examined           */
   BOOL found = FALSE;        /* Loop control boolean                        */

   cx = line_cx;
   
   if ( cx >= line->length )
   {
      /* If the cursor is beyond the end of the line, move it to the end    */
      /* before starting the search.                                        */
      
      cx = line->length - 1;
   }

   /* Find the last character of the previous word, or any part of a current */
   /* word that the cursor is positioned in. This is defined as the first    */
   /* non-delim character encountered. Stop if a non delimiter found, or the */
   /* start of the line is reached.                                          */
   
   while ( !found && cx >= 0 )
   {
      this_char = line->text[cx--];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix == NOT_A_DELIMITER )
         found = TRUE;
   }
   
   if ( found )
   {
      cx++;
      found = !found;
   }
 
   /* Find the beginning of this word, defined as the first delimeter    */
   /* char we encounter, and then go forward by one.                     */
   
   while ( !found && cx >= 0 )
   {
      this_char = line->text[cx--];
      delim_ix  = findb ( word_delims, this_char, sizeof(word_delims) );
      
      if ( delim_ix >= IS_A_DELIMITER )
      {
         /* Found the start of the word. Advance by one, so that we are on */
         /* the first character, and call it quits.                        */

         found = TRUE;
         cx = cx + 2;        /* plus two because of the predecrement       */
      }
      else if ( cx == -1 )
      {
         /* Have hit the start of the line, and not found a delimiter. By  */
         /* definition, the current character is the start of the word!    */

         found = TRUE;
         cx = 0;
      }
   }
   
   return ( found ? cx : line_cx );
   
} /* find_prev_word_start */

/*  */

static struct node *find_para_start ( struct node *line )

/****************************************************************************
 * This function finds the start of the paragraph containing the line that  *
 * is supplied, and returns the pointer to the first line of the para.      *
 ****************************************************************************/
{
   register struct node *cnode;     /* Current node in the linked list      */

   /* To start, search backwards to find a valid line, so that we are       */
   /* inside a paragraph. If we are in a valid paragraph, this has no       */
   /* impact on cnode's value.                                              */

   cnode = line;

   while (cnode->bwd_link != (struct node *) &curr_file && cnode->length == 0)
      cnode = cnode->bwd_link;

   /* A paragraph is delineated by one or more lines with no text on them, */
   /* so hunt for such a line. When found, or the head of the file is      */
   /* encountered, we are done.                                            */
   
   while ( cnode->bwd_link != (struct node *) &curr_file && cnode->length > 0 )
      cnode = cnode->bwd_link;

   if ( cnode->length == 0 && cnode->fwd_link != NULL )
     cnode = cnode->fwd_link;

   return ( cnode );
   
} /* find_para_start */

/*  */

static struct node *find_para_end ( struct node *line )

/****************************************************************************
 * This functions finds the end of the paragraph containing the supplied    *
 * line, and returns a pointer to the last line of the paragraph.           *
 ****************************************************************************/
{
   register struct node *cnode;   /* Current node in the linked list        */
   register struct node *pnode;   /* Previous node in the linked list       */

   /* To start, search forward to find a valid line, so that we are         */
   /* inside a paragraph. If we are in a valid paragraph, this has no       */
   /* impact on cnode's value.                                              */

   cnode = pnode = line;
   
   while (cnode->fwd_link != EOL && cnode->length == 0)
   {
      pnode = cnode;
      cnode = cnode->fwd_link;
   }

   /* A paragraph is delineated by one or more lines with no text on them, */
   /* so hunt for such a line. When found, or the end of the file is       */
   /* encountered, we are done.                                            */
   
   while ( cnode->fwd_link != EOL && cnode->length > 0 )
   {
      pnode = cnode;
      cnode = cnode->fwd_link;
   }

   if ( cnode->length == 0 )
      cnode = pnode;

   return ( cnode );

} /* find_para_end */

/*  */

void para_up_cmd ()

/***************************************************************************
 * This command moves the cursor up one paragraph, or until the top of the *
 * file is reached.                                                        *
 ***************************************************************************/
{
   struct node *cnode;   /* Current node in the linked list                */

   BOOL this_page;       /* Is the current line on the displayed page?     */
   int  y;               /* The y co-ordinate of current line in page      */

   update_curr_line ();

   /* In case we are presently on the first line of a paragraph, move up   */
   /* by one, so that we will advance upward one para, rather than refind  */
   /* the start of this paragraph.                                         */

   if ( curr_line->bwd_link != (struct node *) &curr_file )
      cnode = curr_line->bwd_link;
   else
      cnode = curr_line;
      
   curr_line = find_para_start ( cnode );
   
   curr_screen.cx = 0;

   /* If the start of the paragraph is on the displayed text page, just    */
   /* move to it. Else, redraw the new page.                               */
      
   this_page = displayed ( curr_line, &y );
   
   if ( this_page )
      ti_move_cursor ( curr_screen.cx, (curr_screen.cy = y) );
   else
   {
      curr_screen.cy = update_curr_screen ( 11 );
      draw_curr_screen ();
   }
   
} /* para_up_cmd */

/*  */

void para_down_cmd ()

/***************************************************************************
 * This command moves the cursor down one paragraph, or until the end of   *
 * the file is reached.                                                    *
 ***************************************************************************/
{
   BOOL this_page;       /* Is the current line on the displayed page?     */
   int  y;               /* The y co-ordinate of current line in page      */
   
   update_curr_line ();
   
   curr_line = find_para_end ( curr_line );

   /* Having found a paragraph end, advance to the next line, so that       */
   /* repetitive para_down_cmd calls will move forward one paragraph at a   */
   /* time.                                                                 */

   if ( curr_line->fwd_link == EOL )
      curr_screen.cx = curr_line->length;  /* Last line - move to end of it */
   else
   {
      curr_line = curr_line->fwd_link;     /* Else move to next line        */
      curr_screen.cx = 0;
   }

   /* If the end of the paragraph is on the displayed text page, just move */
   /* to it. Else, redraw the new page.                                    */
      
   this_page = displayed ( curr_line, &y );
   
   if ( this_page )
      ti_move_cursor ( curr_screen.cx, (curr_screen.cy = y) );
   else
   {
      curr_screen.cy = update_curr_screen ( 11 );
      draw_curr_screen ();
   }
   
} /* para_down_cmd */

/*  */

void wrap_paragraph_cmd ()

/****************************************************************************
 * This command wraps the text in the paragraph containing the current line.*
 * If the current line is a blank, it wraps the text in the paragraph       *
 * below the current line.                                                  *
 ****************************************************************************/
{
   struct node *para_start;    /* The start of the paragraph               */
   struct node *para_end;      /* The end of the paragraph                 */
   struct node *prev_line;     /* The line before the start of the para    */
   struct node *next_line;     /* The line after the end of the para       */
   struct node *this_line;     /* The current line for counting lines      */
   int para_start_line_num;    /* The line number of the paragraph start   */

   BOOL prev_was_displayed;    /* Was the previous line on screen?         */
   BOOL start_was_top;         /* Was the starting line at top of screen?  */
   int  prev_cy;               /* cy of the previous line                  */

   register int i;             /* Loop counter                             */
   register int start_ix;      /* Loop start index                         */
   register int end_ix;        /* Loop end index                           */

   register int  wrapped_lines = 0;   /* The number of wrapped lines       */

   update_curr_line ();

   /* Pick up the start and end of the paragraph to be wrapped             */

   para_end   = find_para_end   ( curr_line  );
   para_start = find_para_start ( para_end   );

   curr_line = para_start;
   para_start_line_num = get_curr_line_num ();

   prev_line = para_start->bwd_link;
   next_line = para_end->fwd_link;

   prev_was_displayed = displayed ( prev_line, &prev_cy );
   start_was_top      = (para_start == curr_screen.top);

   push_undo_wrap_para ( para_start, para_end, wrapped_lines, 
                         para_start_line_num, curr_screen.cx, curr_screen.cy );

   /* Now, do the wrap */

   wrap_range ( para_start, para_end );

   /* While inserting the left marging spaces, for undo purposes, figure   */
   /* out how many lines resulted from the wrap and update the undo action */
   /* with this information                                                */

   this_line = prev_line->fwd_link;

   while ( (this_line != next_line) && (this_line != NULL) )
   begin
      wrapped_lines += 1;
      this_line = this_line->fwd_link;
   end

   update_undo_wrap_para ( wrapped_lines );

   /* Finally, update the screen with the result of all of this. If the    */
   /* line before the wrapped paragraph is still on screen, restart the    */
   /* display from there. Else, redraw the whole screen.                   */

   after_delete_screen_update ( prev_line, prev_was_displayed, 
                                start_was_top, prev_cy             );
   para_down_cmd ();

} /* wrap_paragraph_cmd */

/*  */

void block_wrap ()

/****************************************************************************
 * This command will wrap all paragraphs enclosed within the current block. *
 * Rightfully, this function belongs in blockcmd.c, but is here to be with  *
 * all of the rest of the text wrapping commands.                           *
 ****************************************************************************/
{
   struct node *start_marker;      /* Line prior to start of block          */
   struct node *end_marker;        /* Next line after the block             */
   
   struct node *wrap_line;         /* Line to start next wrap operation at  */
   struct node *next_line;         /* The line after the current paragraph  */
   
   struct node *para_start;        /* Start of the current paragraph        */
   struct node *para_end;          /* End of the current paragraph          */

   update_curr_line ();

   curr_line  = block[0];

   end_marker = find_para_start ( block[1]   );
   end_marker = find_para_end   ( end_marker );
   end_marker = end_marker->fwd_link;
  
   do
   {
      wrap_paragraph_cmd ();
      
   }
   while ( curr_line != end_marker && curr_line->fwd_link != EOL );
   
} /* block_wrap */
   
/*  */

void delete_word_cmd ()

/***************************************************************************
 * This command deletes the word that the cursor is on, or the next word,  *
 * if the cursor is not presently on a word.                               *
 ***************************************************************************/
{
   int end_cx;       /* Index of the end of the current (or next) word     */
   int num_pos;      /* The number of char positions to the end_cx         */

   /* If the cursor is beyond the end of line, deleting a word makes no    */
   /* sense, so do something non-sensical - BEEP!                          */

   if ( curr_screen.cx >= curr_line->length - 1 )
      writechar ( BEL );

   else
   {
      update_curr_line ();

      /* Find the x co-ordinate of the end of the next word, and then      */
      /* compute the number of character positions between the current cx  */
      /* and the end x co-ord.                                             */

      end_cx  = find_next_word_end ( curr_line, curr_screen.cx );
      num_pos = end_cx - curr_screen.cx + 1;

      /* If the number of positions is > 0, build a string of wdc commands */
      /* (one per char to delete) to delete the word, and send it.         */

      if ( num_pos > 0 )
      begin
         push_undo_del_word ( curr_line, num_pos, get_curr_line_num(), 
                              curr_screen.cx, curr_screen.cy           );
         while ( num_pos-- > 0 )
            delete_char_cmd ();
      end

   } /* cursor not beyond end of line */

} /* delete_word_cmd */

/*  */

void word_left_cmd ()

/***************************************************************************
 * This command moves the cursor one word to the left, and leaves it       *
 * positioned on the first character of that word.                         *
 ***************************************************************************/
{
   int delim_ix;

   /* If we are not at the start of the line, proceed. In case we are      */
   /* presently on the first char of a word, move over by one               */

   if ( curr_screen.cx > 0 ) 
   {
      update_curr_line ();

      curr_screen.cx = find_prev_word_start ( curr_line, --curr_screen.cx );

      /* Check for the case that the only motion that occured was because   */
      /* the initial predecrement. If so, reverse the effect.               */

      delim_ix = findb ( word_delims, curr_line->text[curr_screen.cx], sizeof(word_delims) );
      if ( delim_ix >= IS_A_DELIMITER )
         curr_screen.cx++;

      restore_cursor ();
   }

} /* word_left_cmd */

/*  */

void word_right_cmd ()

/***************************************************************************
 * This command moves the cursor one word to the right, and leaves it      *
 * positioned on the first character of that word.                         *
 ***************************************************************************/
{
   int on_delim;  /* Is current character a delim? == -1 if not.           */

   /* If we are not at the end of the line, proceed                        */

   if ( curr_screen.cx < curr_line->length - 1 ) 
   {
      update_curr_line ();

      curr_screen.cx = find_next_word_start ( curr_line, curr_screen.cx );

      restore_cursor ();
   }

} /* word_left_cmd */

/* .page */

void wrap_line ( force_wrap )

/****************************************************************************
 * This function implements VE's on-the-fly text wrapping by calling the    *
 * after-the-fact text wrapper in real time as characters are entered into  *
 * the file.                                                                *
 ****************************************************************************/

BOOL force_wrap;  /* Wrap even if line doesn't exceed right margin?         */

begin
   register int i, ix;           /* Loop counter and non-blank index       */
   register BOOL done = FALSE;   /* Set TRUE when marker is found          */
   char curr_char;               /* Char at cursor position                */

   update_curr_line ();

   if ( curr_line->length > 0 )
   begin

      if ( (options.wrap_text && curr_line->length > options.right_margin - 1) || 
           (force_wrap                                                       ) )
      begin
         /* Position the cursor to start on-the-fly wrapping. In general, we   */
         /* want to be inside the text portion of the line as we start.        */

         if ( curr_screen.cx >= curr_line->length )
            curr_screen.cx = curr_line->length;
/*          curr_screen.cx = (curr_line->length > 0) ? curr_line->length - 1 : 0;  */
         else
         begin
            ix = findb_not ( curr_line->text, ' ', curr_line->length );
            curr_screen.cx = (curr_screen.cx < ix) ? ix : curr_screen.cx;
         end

         /* Mark current cursor position by saving the character at the cursor */
         /* position and then overwriting it with a marker. If we are at the   */
         /* very end of the line, increase its length by one so that the       */
         /* wrapper will see and wrap our marker - else we won't be able to    */
         /* recover it later.                                                  */

         if ( curr_screen.cx == curr_line->length )
            curr_line->text[curr_line->length++] = ' ';

         curr_char = curr_line->text[curr_screen.cx];
         curr_line->text[curr_screen.cx] = WRAP_MARKER;

         /* Wrap the paragraph that we are in */

         wrap_paragraph_cmd ();

         /* Recover the cursor position by searching for our marker,        */
         /* restoring the original character, and positioning the cursor.   */

         para_up_cmd ();  /* positions at col 0 of first line of paragraph  */

         while ( curr_line != NULL && !done )
         begin

            for ( i = 0; i < curr_line->length; i++ )
            begin
               if ( curr_line->text[i] == WRAP_MARKER )
               begin
                  /* We have found our marker. Restore the original content */
                  /* and reposition the cursor.                             */

                  curr_line->text[i] = curr_char;

                  display_new_screen ( i );  /* sets curr_screen.cx/.cy     */
               
                  /* Update the display with the correct character at our   */
                  /* marker's position and we are done.                     */

                  writechar ( curr_char );
                  ti_move_cursor ( curr_screen.cx, curr_screen.cy );

                  done    = TRUE;

                  break;
               end
            end

            if ( !done )
               curr_line = curr_line->fwd_link;

         end /* while not done */

      end /* wrap text is ON and line > right margin */

   end /* line length > 0 */

   load_line_buf ();

end /* wrap_line */

