/*
** This file contains the command processing functions for a number of random
** commands. There is no functional grouping here, for sure.
**
 *****************************************************************************/

#include "Include.h"

// Local functions

REG makelit(char *);


/// setfillcol()
/*
** Set fill column to n.
 ***********************************************************************/
STD setfillcol(int f, int n)
{
        fillcol = n;
   mlwrite(TEXT59,n);
/*              "[Fill column is %d]" */
        return(TRUE);
}
//-

/// getlinenum()
/*
** Display the current position of the cursor, in origin 1 X-Y coordinates,
** the character that is under the cursor (in hex), and the fraction of the
** text that is before the cursor. The displayed column is not the current
** column, but the column that would be used on an infinite width display.
** Normally this is bound to "C-X =".
**
** BUFFER *bp;    buffer to get current line from
** LINE *sline;   line to search for
**
 ***********************************************************************/
STD getlinenum(BUFFER *bp, LINE *sline)
{
        register LINE   *lp;     /* current line */
        register int numlines;   /* # of lines before point */

   /* starting at the beginning of the buffer */
        lp = lforw(bp->b_linep);

   /* start counting lines */
        numlines = 0;
        while (lp != bp->b_linep) {
      /* if we are on the current line, record it */
      if (lp == sline)
         break;
      ++numlines;
      lp = lforw(lp);
        }

   /* and return the resulting count */
   return(numlines + 1);
}
//-

/// getccol()
/*
** Return current column.  Stop at first non-blank given TRUE argument.
 ***********************************************************************/
STD getccol(int bflg)
{
        register int c, i, col;
        col = 0;
        for (i=0; i<curwp->w_doto; ++i) {
                c = lgetc(curwp->w_dotp, i);
                if (c!=' ' && c!='\t' && bflg)
                        break;
                if (c == '\t')
                        col += -(col % tabsize) + (tabsize - 1);
                else if (c<0x20 || c==0x7F)
                        ++col;
                ++col;
        }
        return(col);
}
//-

/// twiddle()
/*
** Twiddle the two characters on either side of dot. If dot is at the end of
** the line twiddle the two characters before it. Return with an error if dot
** is at the beginning of line; it seems to be a bit pointless to make this
** work. This fixes up a very common typo with a single stroke. Normally bound
** to "C-T". This always works within a line, so "WFEDIT" is good enough.
 ***********************************************************************/
STD twiddle(int f, int n)
{
        register LINE   *dotp;
        register int    doto;
        register int    cl;
        register int    cr;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        dotp = curwp->w_dotp;
        doto = curwp->w_doto;
        if (doto==llength(dotp) && --doto<0)
                return(FALSE);
        cr = lgetc(dotp, doto);
        if (--doto < 0)
                return(FALSE);
        cl = lgetc(dotp, doto);
        lputc(dotp, doto+0, cr);
        lputc(dotp, doto+1, cl);
        lchange(WFEDIT);
        return(TRUE);
}
//-

/// quote()
/*
** Quote the next character, and insert it into the buffer. All the characters
** are taken literally, including the newline, which does not then have
** its line splitting meaning. The character is always read, even if it is
** inserted 0 times, for regularity. Bound to "C-Q"
 ***********************************************************************/
STD quote(f, n)
{
        register int c;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        c = tgetc();
        if (n < 0)
                return(FALSE);
        if (n == 0)
                return(TRUE);
        return(linsert(n, c));
}
//-

/// tab()
/*
** Set tab size if given non-default argument (n <> 1).  Otherwise, insert a
** tab into file.  If given argument, n, of zero, change to hard tabs.
** If n > 1, simulate tab stop every n-characters using spaces. This has to be
** done in this slightly funny way because the tab (in ASCII) has been turned
** into "C-I" (in 10 bit code) already. Bound to "C-I".
 ***********************************************************************/
STD tab(int f, int n)
{
        if (n < 0)
                return(FALSE);
        if (n == 0 || n > 1) {
                stabsize = n;
                return(TRUE);
        }
        if (!stabsize)
                return(linsert(1, '\t'));
        return(linsert(stabsize - (getccol(FALSE) % stabsize), ' '));
}
//-

/// trim()
/*
** trim: trim trailing whitespace from the point to eol
** with no arguments, it trims the current region
 ***********************************************************************/
STD trim(int f, int n)
{
   register LINE *lp;   /* current line pointer */
   register int offset; /* original line offset position */
   register int length; /* current length */
   register int inc; /* increment to next line [sgn(n)] */

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

   if (f == FALSE)
      n = reglines();

   /* loop thru trimming n lines */
   inc = ((n > 0) ? 1 : -1);
   while (n) {
      lp = curwp->w_dotp;     /* find current line text */
      offset = curwp->w_doto;    /* save original offset */
      length = lp->l_used;    /* find current length */

      /* trim the current line */
      while (length > offset) {
         if (lgetc(lp, length-1) != ' ' &&
             lgetc(lp, length-1) != '\t')
               break;
         length--;
      }
      lp->l_used = length;

      /* advance/or back to the next line */
      forwline(TRUE, inc);
      n -= inc;
   }
   lchange(WFEDIT);
   thisflag &= ~CFCPCN; /* flag that this resets the goal column */
   return(TRUE);
}
//-

/// openline()
/*
** Open up some blank space. The basic plan is to insert a bunch of newlines,
** and then back up over them. Everything is done by the subcommand
** procerssors. They even handle the looping. Normally this is bound to "C-O".
 ***********************************************************************/
STD openline(int f, int n)
{
        register int    i;
        register int    s;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        if (n < 0)
                return(FALSE);
        if (n == 0)
                return(TRUE);
        i = n;                                  /* Insert newlines.     */
        do {
                s = lnewline();
        } while (s==TRUE && --i);
        if (s == TRUE)                          /* Then back up overtop */
                s = backchar(f, n);             /* of them all.         */
        return(s);
}
//-

/// newline()
/*
** Insert a newline. Bound to "C-M". If we are in CMODE, do automatic
** indentation as specified.
 ***********************************************************************/
STD newline(int f, int n)
{
   register int    s;

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


        /*
         * If a newline was typed, fill column is defined, the argument is non-
         * negative, wrap mode is enabled, and we are now past fill column,
    * and we are not read-only, perform word wrap.
         */
        if ((curwp->w_bufp->b_mode & MDWRAP) && fillcol > 0 &&
       getccol(FALSE) > fillcol &&
       (curwp->w_bufp->b_mode & MDVIEW) == FALSE)
      execkey(&wraphook, FALSE, 1);

   /* insert some lines */
   while (n--) {
      if ((s=lnewline()) != TRUE)
         return(s);
   }
   return(TRUE);
}
//-

/// forwdel()
/*
** Delete forward. This is real easy, because the basic delete routine does
** all of the work. Watches for negative arguments, and does the right thing.
** If any argument is present, it kills rather than deletes, to prevent loss
** of text if typed with a big argument. Normally bound to "C-D".
 ***********************************************************************/
STD forwdel(int f, int n)
{
   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        if (n < 0)
                return(backdel(f, -n));
        if (f != FALSE) {                       /* Really a kill.       */
                if ((lastflag&CFKILL) == 0)
                        kdelete();
                thisflag |= CFKILL;
        }
        return(ldelete((long)n, f));
}
//-

/// killtext()
/*
** Kill text. If called without an argument, it kills from dot to the end of
** the line, unless it is at the end of the line, when it kills the newline.
** If called with an argument of 0, it kills from the start of the line to dot.
** If called with a positive argument, it kills from dot forward over that
** number of newlines. If called with a negative argument it kills backwards
** that number of newlines. Normally bound to "C-K".
 ***********************************************************************/
STD killtext(int f, int n)
{
        register LINE   *nextp;
        long chunk;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        if ((lastflag&CFKILL) == 0)             /* Clear kill buffer if */
                kdelete();                      /* last wasn't a kill.  */
        thisflag |= CFKILL;

        if (f == FALSE) {
                chunk = llength(curwp->w_dotp)-curwp->w_doto;
                if (chunk == 0)
                        chunk = 1;
   } else if (n == 0) {
                chunk = -curwp->w_doto;
        } else if (n > 0) {
                chunk = llength(curwp->w_dotp)-curwp->w_doto+1;
                nextp = lforw(curwp->w_dotp);
                while (--n) {
                        if (nextp == curbp->b_linep)
                                return(FALSE);
                        chunk += llength(nextp)+1;
                        nextp = lforw(nextp);
                }
        } else if (n < 0) {
                chunk = -curwp->w_doto;
                nextp = lback(curwp->w_dotp);
                while (n++) {
                        if (nextp == curbp->b_linep)
                                return(FALSE);
                        chunk -= llength(nextp)+1;
                        nextp = lback(nextp);
                }
        }
        return(ldelete(chunk, TRUE));
}
//-

/// clermes()
/* This function simply clears the message line,
**      mainly for macro usage
 ************************************************/
STD clrmes(int f, int n)
{
   mlforce("");
   return(TRUE);
}
//-

/// writemsg()
/*
** This function writes a string on the message line
** mainly for macro usage
 ***********************************************************************/
STD writemsg(int f, int n)
{
   register int status;
   char buf[NPAT];      /* buffer to recieve message into */

   if ((status = mlreply(TEXT67, buf, NPAT - 1)) != TRUE)
/*                            "Message to write: " */
      return(status);

   /* expand all '%' to "%%" so mlwrite won't expect arguments */
   makelit(buf);

   /* write the message out */
   mlforce(buf);
   return(TRUE);
}
//-

/// istring()
/* ask for and insert a string into the current
** buffer at the current point
 ***********************************************************************/
STD istring(f, n)
{
   register int status; /* status return code */
   char tstring[NPAT+1];   /* string to add */

   /* ask for string to insert */
   status = mltreply(TEXT68, tstring, NPAT, sterm);
/*                        "String to insert<META>: " */
   if (status != TRUE)
      return(status);

   if (f == FALSE)
      n = 1;

   if (n < 0)
      n = - n;

   /* insert it */
   while (n-- && (status = linstr(tstring)))
      ;
   return(status);
}
//-

/// ovstring()
/*
** ask for and overwite a string into the current
** buffer at the current point
 ***********************************************************************/
STD ovstring(f, n)
{
   register int status; /* status return code */
   char tstring[NPAT+1];   /* string to add */

   /* ask for string to insert */
   status = mltreply(TEXT69, tstring, NPAT, sterm);
/*                        "String to overwrite<META>: " */
   if (status != TRUE)
      return(status);

   if (f == FALSE)
      n = 1;

   if (n < 0)
      n = - n;

   /* insert it */
   while (n-- && (status = lover(tstring)))
      ;
   return(status);
}
//-

/// lookup_color()
int STD lookup_color(char *sp)
{
   register int i;      /* index into color list */

   /* test it against the colors we know */
   for (i = 0; i < NCOLORS; i++) {
      if (strcmp(sp, cname[i]) == 0)
         return(i);
   }
   return(-1);
}
//-

/// backdel()
/*
** Delete backwards. This is quite easy too, because it's all done with other
** functions. Just move the cursor back, and delete forwards. Like delete
** forward, this actually does a kill if presented with an argument. Bound to
** both "RUBOUT" and "C-H".
 *****************************************************************************/
STD backdel(int f, int n)
{
        register int    s;

   if (curbp->b_mode&MDVIEW)  /* don't allow this command if   */
      return(rdonly()); /* we are in read only mode   */
        if (n < 0)
                return(forwdel(f, -n));
        if (f != FALSE) {                       /* Really a kill.       */
                if ((lastflag&CFKILL) == 0)
                        kdelete();
                thisflag |= CFKILL;
        }
        if ((s=backchar(f, n)) == TRUE)
                s = ldelete((long)n, f);
        return(s);
}
//-

// Local functions

/// makelit()
/* expand all "%" to "%%"
**
** char *s;    string to expand
 ***********************************************************************/
REG makelit(char *s)
{
   register char *sp;   /* temp for expanding string */
   register char *ep;   /* ptr to end of string to expand */

   sp = s;
   while (*sp)
   if (*sp++ == '%') {
      /* advance to the end */
      ep = --sp;
      while (*ep++)
         ;
      /* null terminate the string one out */
      *(ep + 1) = 0;
      /* copy backwards */
      while(ep-- > sp)
         *(ep + 1) = *ep;

      /* and advance sp past the new % */
      sp += 2;
   }

   return(1);
}
//-


