/*
** The routines in this file implement commands that work word or a
** paragraph at a time.  There are all sorts of word mode commands.  If I
** do any sentence mode commands, they are likely to be put in this file.
 ***************************************************************************/

#include "Include.h"

// Local functions

STD i_fillpara(int, int, int);
STD reform(char *);


/// wrapword()
/* Word wrap on n-spaces. Back-over whatever precedes the point on the current
** line and stop on the first word-break or the beginning of the line. If we
** reach the beginning of the line, jump back to the end of the word and start
** a new line. Otherwise, break the line at the word-break, eat it, and jump
** back to the end of the word. Make sure we force the display back to the
** left edge of the current window
(* Returns TRUE on success, FALSE on errors.
 ***********************************************************************/
STD wrapword(int f, int n)
{
   register int cnt; /* size of word wrapped to next line */
   register int c;      /* charector temporary */

   /* backup from the <NL> 1 char */
   if (!backchar(FALSE, 1))
      return(FALSE);

   /* back up until we aren't in a word,
      make sure there is a break in the line */
   cnt = 0;
   while (((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ')
            && (c != '\t')) {
      cnt++;
      if (!backchar(FALSE, 1))
         return(FALSE);
      /* if we make it to the beginning, start a new line */
      if (curwp->w_doto == 0) {
         gotoeol(FALSE, 0);
         return(lsoftline());
      }
   }

   /* delete the forward white space */
   if (!forwdel(0, 1))
      return(FALSE);

   /* put in a end of line */
   if (!lsoftline())
      return(FALSE);

   /* and past the first word */
   while (cnt-- > 0) {
      if (forwchar(FALSE, 1) == FALSE)
         return(FALSE);
   }

   /* make sure the display is not horizontally scrolled */
   if (curwp->w_fcol != 0) {
      curwp->w_fcol = 0;
      curwp->w_flag |= WFHARD | WFMOVE | WFMODE;
   }

   return(TRUE);
}
//-

/// forwword()
/*
** Move the cursor forward by the specified number of words. All of the motion
** is done by "forwchar". Error if you try and move beyond the buffer's end.
 ***********************************************************************/
STD forwword(int f, int n)
{
   if (n < 0)
      return(backword(f, -n));
   while (n--) {
      /* scan through the current word */
      while (inword() == TRUE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }

      /* scan through the intervening white space */
      while (inword() == FALSE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
   }
   return(TRUE);
}
//-

/// endword()
/*
** Move forward to the end of the nth next word. Error if you move past
** the end of the buffer.
 ***********************************************************************/
STD endword(int f, int n)
{
   if (n < 0)
      return(backword(f, -n));
   while (n--) {
      /* scan through the intervening white space */
      while (inword() == FALSE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }

      /* scan through the current word */
      while (inword() == TRUE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
   }
   return(TRUE);
}
//-

/// upperword()
/*
** Move the cursor forward by the specified number of words. As you move,
** convert any characters to upper case. Error if you try and move beyond the
** end of the buffer. Bound to "M-U".
 ***********************************************************************/
STD upperword(int f, int n)
{
   int c;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
   if (n < 0)
      return(FALSE);
   while (n--) {
      while (inword() == FALSE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
      while (inword() != FALSE) {
         c = lgetc(curwp->w_dotp, curwp->w_doto);
         if (islower(c)) {
            c = upperc(c);
            lputc(curwp->w_dotp, curwp->w_doto, c);
            lchange(WFHARD);
         }
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
   }
   return(TRUE);
}
//-

/// lowerword()
/*
** Move the cursor forward by the specified number of words. As you move
** convert characters to lower case. Error if you try and move over the end of
** the buffer. Bound to "M-L".
 ***********************************************************************/
STD lowerword(int f, int n)
{
   int c;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
   if (n < 0)
      return(FALSE);
   while (n--) {
      while (inword() == FALSE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
      while (inword() != FALSE) {
         c = lgetc(curwp->w_dotp, curwp->w_doto);
         if (isupper(c)) {
            c = lowerc(c);
            lputc(curwp->w_dotp, curwp->w_doto, c);
            lchange(WFHARD);
         }
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
   }
   return(TRUE);
}
//-

/// capword()
/*
** Move the cursor forward by the specified number of words. As you move
** convert the first character of the word to upper case, and subsequent
** characters to lower case. Error if you try and move past the end of the
** buffer. Bound to "M-C".
 ***************************************************************************/
STD capword(int f, int n)  // f = prefix flag, n = argument
{
   int c;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
   if (n < 0)
      return(FALSE);
   while (n--) {
      while (inword() == FALSE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
      if (inword() != FALSE) {
         c = lgetc(curwp->w_dotp, curwp->w_doto);
         if (islower(c)) {
            c = upperc(c);
            lputc(curwp->w_dotp, curwp->w_doto, c);
            lchange(WFHARD);
         }
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
         while (inword() != FALSE) {
            c = lgetc(curwp->w_dotp, curwp->w_doto);
            if (isupper(c)) {
               c = lowerc(c);
               lputc(curwp->w_dotp, curwp->w_doto, c);
               lchange(WFHARD);
            }
            if (forwchar(FALSE, 1) == FALSE)
               return(FALSE);
         }
      }
   }
   return(TRUE);
}
//-

/// delfword()
/*
** Kill forward by "n" words. Remember the location of dot. Move forward by
** the right number of words. Put dot back where it was and issue the kill
** command for the right number of characters. With a zero argument, just
** kill one word and no whitespace. Bound to "M-D".
 *************************************************************************/
STD delfword(int f, int n)
{
   register LINE  *dotp;   /* original cursor line */
   register int   doto; /* and row */
   register int c;      /* temp char */
   long size;     /* # of chars to delete */

   /* don't allow this command if we are in read only mode */
   if (curbp->b_mode&MDVIEW)
      return(rdonly());

   /* ignore the command if there is a negative argument */
   if (n < 0)
      return(FALSE);

   /* Clear the kill buffer if last command wasn't a kill */
   if ((lastflag&CFKILL) == 0)
      kdelete();
   thisflag |= CFKILL;  /* this command is a kill */

   /* save the current cursor position */
   dotp = curwp->w_dotp;
   doto = curwp->w_doto;

   /* figure out how many characters to give the axe */
   size = 0;

   /* get us into a word.... */
   while (inword() == FALSE) {
      if (forwchar(FALSE, 1) == FALSE)
         return(FALSE);
      ++size;
   }

   if (n == 0) {
      /* skip one word, no whitespace! */
      while (inword() == TRUE) {
         if (forwchar(FALSE, 1) == FALSE)
            return(FALSE);
         ++size;
      }
   } else {
      /* skip n words.... */
      while (n--) {
   
         /* if we are at EOL; skip to the beginning of the next */
         while (curwp->w_doto == llength(curwp->w_dotp)) {
            if (forwchar(FALSE, 1) == FALSE)
               return(FALSE);
            ++size;
         }
   
         /* move forward till we are at the end of the word */
         while (inword() == TRUE) {
            if (forwchar(FALSE, 1) == FALSE)
               return(FALSE);
            ++size;
         }
   
         /* if there are more words, skip the interword stuff */
         if (n != 0)
            while (inword() == FALSE) {
               if (forwchar(FALSE, 1) == FALSE)
                  return(FALSE);
               ++size;
            }
      }
   
      /* skip whitespace and newlines */
      while ((curwp->w_doto == llength(curwp->w_dotp)) ||
         ((c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' ') ||
         (c == '\t')) {
            if (forwchar(FALSE, 1) == FALSE)
               break;
            ++size;
      }
   }

   /* restore the original position and delete the words */
   curwp->w_dotp = dotp;
   curwp->w_doto = doto;
   return(ldelete(size, TRUE));
}
//-

/// delbword()
/*
** Kill backwards by "n" words. Move backwards by the desired number of words,
** counting the characters. When dot is finally moved to its resting place,
** fire off the kill command. Bound to "M-Rubout" and to "M-Backspace".
 *******************************************************************************/
STD delbword(int f, int n)
{
   long size;

   /* don't allow this command if we are in read only mode */
   if (curbp->b_mode&MDVIEW)
      return(rdonly());

   /* ignore the command if there is a nonpositive argument */
   if (n <= 0)
      return(FALSE);

   /* Clear the kill buffer if last command wasn't a kill */
   if ((lastflag&CFKILL) == 0)
      kdelete();
   thisflag |= CFKILL;  /* this command is a kill */

   if (backchar(FALSE, 1) == FALSE)
      return(FALSE);
   size = 0;
   while (n--) {
      while (inword() == FALSE) {
         if (backchar(FALSE, 1) == FALSE)
            return(FALSE);
         ++size;
      }
      while (inword() != FALSE) {
         ++size;
         if (backchar(FALSE, 1) == FALSE)
            goto bckdel;
      }
   }
   if (forwchar(FALSE, 1) == FALSE)
      return(FALSE);
bckdel:  if (forwchar(FALSE, size) == FALSE)
      return(FALSE);
   return(ldelete(-size, TRUE));
}
//-

/// inword()
/*
** Return TRUE if the character at dot is a character that is considered to be
** part of a word. The default word character list is hard coded. If $wchars
** has been set by the user, use that instead
 ***********************************************************************/
STD inword(void)
{
   register int c;

   /* the end of a line is never in a word */
   if (curwp->w_doto == llength(curwp->w_dotp))
      return(FALSE);

   /* grab the word to check */
   c = lgetc(curwp->w_dotp, curwp->w_doto);


   /* else use the default hard coded check */
   if (isletter(c))
      return(TRUE);
   if (c>='0' && c<='9')
      return(TRUE);
   if (c == '_')
      return(TRUE);
   // treat contractions as one word for the spelling checker
   if (c == '\'')
      return(TRUE);
   return(FALSE);
}
//-

/* XENO_ added autofillpara and i_fillpara stuff */

/// fillpara()
/*
** Fill the current paragraph according to the current fill column
 ***********************************************************************/
STD fillpara(int f, int n)
{
   i_fillpara(f, n, 0);
   return(1);
}
//-

/// autofillpara()
STD autofillpara(int f, int n)
{
   i_fillpara(f, n, 1);
   return(1);
}
//-

/// backword()
/*
** Move the cursor backward by "n" words. All of the details of motion are
** performed by the "backchar" and "forwchar" routines. Error if you try to
** move beyond the buffers.
 ****************************************************************************/
STD backword(int f, int n)
{
   if (n < 0)
      return(forwword(f, -n));
   if (backchar(FALSE, 1) == FALSE)
      return(FALSE);
   while (n--) {
      while (inword() == FALSE) {
         if (backchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
      while (inword() != FALSE) {
         if (backchar(FALSE, 1) == FALSE)
            return(FALSE);
      }
   }
   return(forwchar(FALSE, 1));
}
//-

// Local functions

/// i_fillpara()
/*
** int f, n;      Default flag and Numeric argument
** int which;     0 = normal fill, 1 = auto fill while inserting
 *****************************************************************/
STD i_fillpara(int f, int n, int which)
{
   register char *pp;   /* ptr into paragraph being reformed */
   register char *para; /* malloced buffer for paragraph */
   register LINE *lp;   /* ptr to current line */
   register int lsize;  /* bytes in current line */
   register char *txtptr;  /* ptr into current line */
   LINE *ptline;     /* line the point started on */
   int ptoff;     /* offset of original point */
   int back;      /* # of characters from origin point to eop */
   int status;    /* return status from linstr() */
   int psize;     /* byte size of paragraph */
   LINE *bop;     /* ptr to beg of paragraph */
   LINE *eop;     /* pointer to line just past EOP */

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
   if (fillcol == 0) {  /* no fill column set */
      mlwrite(TEXT98);
/*                      "No fill column set" */
      return(FALSE);
   }

   /* save the original point */
   ptline = curwp->w_dotp;
   ptoff = curwp->w_doto;

/* XENO really modified this logic for autofillpara */
   if (which) {
      gotobol(FALSE, 1);
      bop = lp = curwp->w_dotp;
   }

   /* record the pointer to the line just past the EOP */
   gotoeop(FALSE, 1);
   eop = lforw(curwp->w_dotp);

   /* and back to the beginning of the paragraph */
   if (!which) {
      gotobop(FALSE, 1);
      bop = lp = curwp->w_dotp;
   }

   /* ok, how big is this paragraph? */
   psize = 0;
   while (lp != eop) {
      psize += lp->l_used + 1;
      lp = lp->l_fp;
   }

   /* create a buffer to hold this stuff */
   para = malloc(psize + 100);   /***** THIS IS TEMP *****/
   if (para == NULL) {
      mlwrite(TEXT94);
/*                      "%%Out of memory" */
      return(FALSE);
   }

   /* now, grab all the text into a string */
   back = 0;   /* counting the distance to backup when done */
   lp = bop;
   pp = para;

   while (lp != eop) {
      lsize = lp->l_used;
      if (back == 0) {
         if (lp == ptline)
            back = lsize - ptoff + 1;
      } else
         back += lsize + 1;
      txtptr = lp->l_text;
      while (lsize--)         /* copy a line */
         *pp++ = *txtptr++;
      *pp++ = ' ';         /* turn the NL to a space */
      lp = lp->l_fp;
      lfree(lp->l_bp);     /* free the old line */
   }
   *(--pp) = 0;   /* truncate the last space */

   /* reformat the paragraph in the buffer */
   reform(para);

   /* insert the reformatted paragraph back into the current buffer */
   status = linstr(para);
   lnewline();    /* add the last newline to our paragraph */
   if (status == TRUE)  /* reposition us to the same place */
      status = backchar(FALSE, back);

   /* make sure the display is not horizontally scrolled */
   if (curwp->w_fcol != 0) {
      curwp->w_fcol = 0;
      curwp->w_flag |= WFHARD | WFMOVE | WFMODE;
   }

   /* free the buffer and return */
   free(para);
   return(status);
}
//-

/// reform()
/*
** reformat a paragraph as stored in a string
**
** char *para;    string buffer containing paragraph
 ***********************************************************************/
STD reform(char *para)
{
   register char *sp;      /* string scan pointer */
   register int col;    /* current colomn position */
   register char *lastword;   /* ptr to end of last word */

   /* don't format if first line of paragraph is a quote */
   if (isaquote(para)) {
      return TRUE;
   }
   /* scan string, replacing some whitespace with newlines */
   sp = para;
   lastword = para;
   col = 0;
   while (*sp) {
      /* if we are at white space.... */
      if ((*sp == ' ') || (*sp == '\t')) {
         if (*sp == '\t')
            col = (col + 8) & (~7);
         else
            col++;

         /* break on whitespace? */
         /* but don't create a line beginning with a blank */
         if (col > fillcol && *(sp+1) != ' ') {
            *sp = '\r';
            col = 0;
         }

         /* onward, resetting the most recent begin of word */
         ++sp;
         lastword = sp;
      } else { /* a non-blank to process */

         ++sp;
         ++col;
         if (col > fillcol) {
            /* line break here! */
            if ((lastword > para) &&
               (*(lastword - 1) != '\r')) {
                  *(lastword - 1) = '\r';
                  sp = lastword;
                  col = 0;
            }
         }
      }
   }
}
//-


