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

#include  <stdio.h>
#include  <exec/types.h>
#include  <clib/dos_protos.h>
#include  <clib/dos_pragmas.h>
#include  "ed.h"

int   tabsize;      /* Tab size (0: use real tabs)  */

/*
 * Toggle autoindent
 */
toggleautoindent(f, n)
{
 if( autoindent ) {
  autoindent = FALSE;
 }
 else {
  autoindent = TRUE;
 }
 setoption(0,autoindent);
 return(TRUE);
}

/*
 * Toggle CASE sensitive search/replace
 */

togglecasesens(f, n)
{
 if( casesens ) {
  casesens = FALSE;
 }
 else {
  casesens = TRUE;
 }
 setoption(1,casesens);
 return(TRUE);
}

toggleicon(f, n)
{
 if( icon ) {
  icon = FALSE;
 }
 else {
  icon = TRUE;
 }
 setoption(2,icon);
 return(TRUE);
}

/*
 * Toggle keepbackup
 */

togglebackup(f, n)
{
 if( keepBackup ) {
  keepBackup = FALSE;
 }
 else {
  keepBackup = TRUE;
 }
 setoption(3,keepBackup);
 return(TRUE);
}

togglestripline(f, n)
{
 if( stripline ) {
  stripline = FALSE;
 }
 else {
  stripline = TRUE;
 }
 setoption(4,stripline);
 return(TRUE);
}

togglerequester(f, n)
{
  if( requester )
    requester = FALSE;
  else
    requester = TRUE;

  setoption(5,requester);
  return(TRUE);
}

toggledisplaytab(f, n)
{
 if( displaytab ) {
  displaytab = FALSE;
 }
 else {
  displaytab = TRUE;
 }
 sgarbf = TRUE;
 setoption(6,displaytab);
 return(TRUE);
}

toggleinterlace(f, n)
{
 if( interlace ) {
  interlace = FALSE;
 }
 else {
  interlace = TRUE;
 }
 vtrethink();
 setoption(1,casesens);
 setoption(2,icon);
 setoption(3,keepBackup);
 setoption(4,stripline);
 setoption(5,requester);
 setoption(6,displaytab);
 setoption(7,interlace);
 return(TRUE);
}

/*
 * Display the current position of the cursor, in origin 1 X-Y coordinates,
 * the character that is under the cursor (in octal), 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 =".
 */
showcpos(f, n)
{
  int   dotChar;
  int   dotLine;
  LINE  *clp;
  long  nch;
  long  nbc;
  int   ratio;
  int   cln;
  short col;

  clp = lforw(curbp->b_linep);    /* Grovel the data.   */
  nch = 0;
  cln = 0;
  for (;;) {
    if (clp == curwp->w_dotp) {
      nbc = (long)nch + curwp->w_doto;
     /*
      * 7.10.90/ms
      * Inkonsitenz mit den Liniennummern bei INFO und GOTO.
      */
      dotLine = (cln + 1);
      if (curwp->w_doto == llength(clp))
        dotChar = '\n';
      else
        dotChar = lgetc(clp, curwp->w_doto);
    }
    nch += llength(clp);

    if (clp == curbp->b_linep)
      break;

    clp = lforw(clp);
    ++cln;
  }
  col = getccol(FALSE);       /* Get real column.   */

  ratio = 0;        /* Ratio before dot.  */
  if (nch != 0)
    ratio = (100L*nbc) / nch;

  mlwrite(
#ifdef DEUTSCH
    "Linien=%d  Linie=%d  Zeile=%d  Spalte=%d  CH=0x%x  .=%D  (%d%% von %D)",
#else
    "lines=%d  line=%d  row=%d  col=%d  CH=0x%x  .=%D  (%d%% of %D)",
#endif
    cln, dotLine, currow, col, dotChar, nbc, ratio, nch
  );
  return (TRUE);
}

/*
 * Return current column.  Stop at first non-blank given TRUE argument.
 */
getccol(bflg)
int bflg;
{
register short c, i, col;

col = 0;
for (i=0; i<curwp->w_doto; ++i)
 {
 c = lgetc(curwp->w_dotp, i);
 if (bflg && c != ' ' && c != '\t')
  break;
 if (c == '\t')
  col |= 0x07;
 else if (c<0x20 || c==0x7F)
  ++col;
 ++col;
  }
return((int)col);
}

/*
 * 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.
 */
twiddle(f, n)
{
 register LINE   *dotp;
 register int  doto;
 register int  cl;
 register int  cr;

 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 the next character, and insert it into the buffer. All the characters
 * are taken literally, with the exception of the newline, which always has
 * its line splitting meaning. The character is always read, even if it is
 * inserted 0 times, for regularity. Bound to "M-Q" (for me) and "C-Q" (for
 * Rich, and only on terminals that don't need XON-XOFF).
 */
quote(f, n)
{
 register int  s;
 register int  c;

 c = GetChar();
 if (n < 0)
  return (FALSE);
 if (n == 0)
  return (TRUE);
 if (c == '\n') {
  do {
   s = lnewline();
  }
  while (s==TRUE && --n);
  return (s);
 }
 return (linsert(n, c));
}

/*
 * Set tab size if given non-default argument (n <> 1).  Otherwise, insert a
 * tab into file.  If given argument, n, of zero, change to true 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".
 */
tab(f, n)
{
 if (n < 0)
  return (FALSE);
 if (n != 1) {
  tabsize = n;
  return(TRUE);
 }
 else {
  if (! tabsize) {
   return(linsert(1, '\t'));
  }
  return(linsert(tabsize - (getccol(FALSE) % tabsize), ' '));
 }
}

/*
 * 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".
 */
openline(f, n)
{
 register int  i;
 register int  s;

 if (n < 0)
  return (FALSE);
 if (n == 0)
  return (TRUE);
 i = n;          /* Insert newlines.   */
 do {
  gotobol(f,1);  /* start of line */
  s = lnewline();
 }
 while (s==TRUE && --i);
 if (s)        /* Then back up overtop */
  s = backchar(f, n);     /* of them all.   */
 return (s);
}

/*
 * Insert a newline. Bound to "C-M". If you are at the end of the line and the
 * next line is a blank line, just move into the blank line. This makes "C-O"
 * and "C-X C-O" work nicely, and reduces the ammount of screen update that
 * has to be done. This would not be as critical if screen update were a lot
 * more efficient.
 *
 * newline(f, n)
 * {
 *  register LINE   *lp;
 *  register int  s;
 *
 *  if (n < 0)
 *   return (FALSE);
 *  while (n--) {
 *   lp = curwp->w_dotp;
 *   if (llength(lp) == curwp->w_doto
 *       && lp != curbp->b_linep
 *       && llength(lforw(lp)) == 0) {
 *    if ((s=forwchar(FALSE, 1)) != TRUE)
 *     return (s);
 *   }
 *   else if ((s=lnewline()) != TRUE)
 *    return (s);
 *  }
 *  return (TRUE);
 * }
 */

/*
 * Insert a newline, then enough tabs and spaces to duplicate the indentation
 * of the previous line. Assumes tabs are every eight characters. Quite simple.
 * Figure out the indentation of the current line. Insert a newline by calling
 * the standard routine. Insert the indentation by inserting the right number
 * of tabs and spaces. Return TRUE if all ok. Return FALSE if one of the
 * subcomands failed. Normally bound to "C-J".
 */
indent(f, n)
{
 register int  c;
 register int  i;
 register LINE *prevLine;

 if (n < 0)
  return (FALSE);

 while (n--) {
  if ( lnewline() == FALSE ) return (FALSE);
  prevLine = lback(curwp->w_dotp);
  if ( autoindent ) {
   for (i=0; (i<llength(prevLine)); ++i) {
    c = lgetc(prevLine, i);
    if ((c != ' ') && (c != '\t') ) break;
    if ( linsert(1,c) == FALSE ) return (FALSE);
   }
  }
 /*
  * 29.7.89/ms
  *   stripLine() eingefügt. Die vorhergehende Zeile wird aller
  *   überflüssigen Leerzeichen entledigt, sobald die neue Zeile die Ein-
  *   rückung übernommen hat. Dies ist wichtig, falls leere Zeilen in den
  *   Programmfluss eingefügt werden.
  */
  stripLine(prevLine);
 }
 return (TRUE);
}

/*
 * Delete entire line.
 */
delline(f, n)
{
 gotobol(f, n);
 if (curwp->w_dotp->l_used != 0) {
  kill(FALSE);
  lastflag = thisflag;
 }
 return (kill(FALSE));
}

/*
 * join line with next
 */
join(f,n)
{
 register short doto;

 while (n--) {
  doto = curwp->w_doto;
  gotoeol(f,1);
  forwdel(f,1);
  curwp->w_doto = doto;
 }
 return (TRUE);
}

/*
 * 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".
 */
forwdel(f, n)
{
 if (n < 0)
  return (backdel(f, -n));
 if (f != FALSE) {       /* Really a kill.   */
  if ((lastflag&CFKILL) == 0)
   kdelete();
  thisflag |= CFKILL;
 }
 return (ldelete(n, f));
}

/*
 * 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".
 */
backdel(f, n)
{
 register int  s;

 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)))
  s = ldelete(n, f);
 return (s);
}

/*
 * 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
 * newlines. Normally bound to "C-K".
 */
kill(f, n)
{
 register int  chunk;
 register LINE   *nextp;

 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;
  curwp->w_doto = 0;
 }
 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 {
  mlwrite("neg kill");
  return (FALSE);
 }
 return (ldelete(chunk, TRUE));
}

/*
 * seems useful to ms and red, bound to C-X C-K
 */
kill0(f,n)
{
 return (kill(TRUE,0));
}

/*
 * Yank text back from the kill buffer. This is really easy. All of the work
 * is done by the standard insert routines. All you do is run the loop, and
 * check for errors. Bound to "C-Y". The blank lines are inserted with a call
 * to "newline" instead of a call to "lnewline" so that the magic stuff that
 * happens when you type a carriage return also happens when a carriage return
 * is yanked back from the kill buffer.
 */
yank(f, n)
{
 register int  c;
 register int  i;
 extern   int  kused;

 if (n < 0)
  return (FALSE);
 while (n--) {
  i = 0;
  while ((c=kremove(i)) >= 0) {
   if (c == '\n') {
    if (/*newline(FALSE, 1)*/ lnewline() == FALSE)
     return (FALSE);
   }
   else {
    if (linsert(1, c) == FALSE)
     return (FALSE);
   }
   ++i;
  }
 }
 return (TRUE);
}

/*
 * Yank text back from the kill buffer and into a file. This is really easy.
 * its just yank() that goes to a file
 */

#define NAMELEN   30
fileyank(f, n)
{
 register long   len;
 register short i;
 register short c;
 unsigned char filename[NAMELEN];
 extern  FILE *ffp;

 if (n < 0)
  return (FALSE);
 filename[0]='\0';

#ifdef DEUTSCH
 if ( mlreply( "Kopiere nach: ", filename, NAMELEN) != TRUE)
#else
 if ( mlreply( "Yank to file: ", filename, NAMELEN) != TRUE)
#endif
  return (FALSE);
 if ( ffwopen( filename ) == FIOERR )
  return (FALSE);

 len = 0L;
 do {
  for ( i = 0; ( i < 512 ) && ( (c=kremove(len+i)) >= 0 ); i++)
   fputc( (unsigned char) c, ffp);
  len += i;
  if (ferror(ffp)) {
#ifdef DEUTSCH
   mlwrite("Schreibfehler!");
#else
   mlwrite("Write I/O error");
#endif
   i = 0;   /* abort by short circuit */
  }
 }
 while ( i == 512 );

 ffclose();
 if (icon)
   makeicon(filename);
 return (TRUE);
}

extern long Lock(), CurrentDir();

changedir()
{
#define DIRLEN 32
 static unsigned char dir[DIRLEN];
 long lock;

#ifdef DEUTSCH
 if ( mlreply( "Wechsle nach: ", dir, DIRLEN) != TRUE) {
#else
 if ( mlreply( "Change Directory: ", dir, DIRLEN) != TRUE) {
#endif
  return (FALSE);
 }
 if (lock = Lock(dir, -2L)) {
  lock = CurrentDir(lock);
  if (doCD) {
   UnLock(lock);
  }
  else {
   origDir = lock;
   doCD = TRUE;
  }
  mlwrite("ok");
 }
 else
#ifdef DEUTSCH
  mlwrite ("Verzeichnis %s nicht gefunden", dir);
#else
  mlwrite ("CD: cannot lock %s", dir);
#endif
}

/*
 * 29.7.89/ms
 * Diese Prozedur löscht alle überflüssigen Leerzeichen am Ende einer Zeile.
 *
 * 20.2.92/ms
 * Falls die Zeile gerade die Cursor-Zeile ist, muss sichergestellt werden,
 * dass der Cursor noch auf einem gültigen Zeichen steht.
 */
void stripLine(lp)
LINE *lp;
{
  WINDOW *wp;
  int     ogoal;

  if (stripline) {
    ogoal = curgoal;
    if (lp == curwp->w_dotp) {
      curgoal = getccol(FALSE);
    }
    for ( ;
      ((lp->l_used > 0)
      && (lp->l_text[lp->l_used-1] == ' ' || lp->l_text[lp->l_used-1] == '\t'));
      lp->l_used--
    );
    if (lp == curwp->w_dotp) {
      curwp->w_doto = getgoal(lp);
      curwp->w_flag |= WFMOVE;
    }
   /*
    * 20.2.92/ms
    * Für alle Fenster auf diesem Buffer muss die Position der
    * Marke überprüft werden. Sobald eine Marke ausserhalb dem
    * gültigen Bereich liegt, muss diese Korrigiert werden.
    */
    for (wp = wheadp; (wp != NULL); wp = wp->w_wndp) {
      if (lp == wp->w_markp && (wp->w_marko > llength(wp->w_markp)))
        wp->w_marko = llength(wp->w_markp);
    }
    curgoal = ogoal;
  }
}

