/***************************************************************************
 *                                                                         *
 *   FSD.C                                                                 *
 *                                                                         *
 *   Copyright (c) 1992-1997 Galacticomm, Inc.      All Rights Reserved.   *
 *                                                                         *
 *   Full-Screen Data Entry                                                *
 *                                                                         *
 *   With operating-system-specific functions removed (I/O, memory)        *
 *   the pure display/entry algorithm can be isolated.                     *
 *                                                                         *
 *                                             - R. Stein  11/9/91         *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "fsd.h"

#define FILREV "$Revision: 7 $"

/*--- GLOBAL VARIABLES ---*/
struct fsdscb *fsdscb;                     /* Global ptr to current FSD SCB */
                   /* Note, if fsdinc() or fsdqoe() are interrupt routines, */
     /* then you must PRESERVE fsdscb before setting it for their purposes. */
INT fsdbln;                 /* length of fsdbuf-referenced temporary buffer */
                      /* fsdbuf[] is used only within each call to fsdppc() */
                                                    /* (output by fsdini()) */
CHAR fsdemg[MAXHLP];          /* error message for fsdppc(), fsdprc(), etc. */
CHAR chkemg[MAXHLP];                    /* error message for chkmin,max,alt */
CHAR *xannam;                            /* global return value of fsdxan() */
                  /* if fsdxan() found an answer, xannam points to its name */
                 /* otherwise, xannam points to final '\0' of answer string */

/*--- OPTION VARIABLES ---*/
INT almmmx=1;                        /* sound alarm for min/max violations? */
INT udwrap=1;                /* should cursor up/down arrow wrap at limits? */
CHAR secchr='*';            /* character to echo when typing "secret" field */
INT maxfld=1000;    /* max number of fields allowed (use to limit fsdppc()) */
                               /* (initial value here is arbitrarily large) */


/*--- Table of template character indexes ---*/

CHAR tmpspc[]={
  /* 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F */
  /* --------------------------------------------------------------- */
     0,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     1,4,0,6,5,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,7,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

#define TMPJNK 0                        /* Template characters: who cares   */
#define TMPWHT 1                        /*                      white space */
#define TMPEQU 2                        /*                      =           */
#define TMPSLS 3                        /*                      /           */
#define TMPEXC 4                        /*                      !           */
#define TMPDOL 5                        /*                      $           */
#define TMPSFD TMPPND                   /* lowest code for subfield capabil */
#define TMPPND 6                        /*                      #           */
#define TMPQST 7                        /*                      ?           */


/*--- Global pointers ---*/

static CHAR *ppctmp;  /* template string (local to fsdppc() & subordinates) */
struct fsdfld *fldptr;                      /* global pointer to field info */
                      /* used locally by many routines & their subordinates */
                  /* Note, fsdinc() and fsdqoe() will preserve and restore, */
                                    /* in case they are interrupt routines. */
static CHAR pbuff[10+14+ANSLEN+1]; /* answer buffer, for to fsdprc() & subs */
static CHAR qbuff[10+14+ANSLEN+1]; /* answer buffer, for to fsdinc() & subs */
static CHAR *bufptr=pbuff;                   /* pointer to one of the above */

/* possible contents of bufptr:  candidate answer
                                 goto command + attribute setting + answer
                                 "ALT=" plus alternate value                */

static CHAR *altptr;                    /* global return value for chkalt() */
                                          /* also used by altntr() hdlalt() */
static INT altcnt;                      /* global return value for chkalt() */
static INT fmtend;         /* global output of dspans(): offset after entry */
static INT fmtbeg;   /* global output of dspans(): offset past initial punc */
static INT ansiscn;               /* global between fsdppc() & subordinates */
static CHAR *bdans;                        /* used by fsdbd1() and fsdbdn() */
static CHAR spcblk[ANSLEN+1+1]="\
                                        \
                                         ";          /* for use by spaces() */
#if ANSLEN != 80
#error spcblk[] array should have exactly ANSLEN+1 spaces, plus a NUL
#endif

VOID
fsdini(VOID)          /* Initialization for Full Screen Data Entry software */
{
     fsdbln=ANSILN*ANSIWD*2;
}

static CHAR *
nxttkn(                                        /* search for the NEXT token */
CHAR *fldopt,                    /* somewhere in the field spec option list */
CHAR *token,                                                  /* token name */
INT tokend) /* tokenize end of token? (expect white space or ')' following) */
{                                         /* return ptr to text AFTER token */
     CHAR *cp;
     CHAR c,t,e;       /* fld option char, token 1st char, char after token */
     INT i,n;

     cp=fldopt;
     t=token[0];
     n=strlen(token);
     for (i=0 ; (c=*cp) != '\0' && c != ')' ; cp++,i++) {
          if (c == t && sameto(token,cp)
           && (i == 0 || isspace(cp[-1]))
           && (!tokend || (e=cp[n]) == '\0' || isspace(e) || e == ')')) {
               return(cp+n);
          }
     }
     return(NULL);
}

static CHAR *
foptkn(             /* search for a specific option in field specifications */
CHAR *token,                                                  /* token name */
INT tokend)              /* tokenize end of token? (search for white space) */
                      /* returns NULL if not found, else ptr to AFTER token */
                                      /* fsdscb, fldptr are implicit inputs */
{
     CHAR *cp;                                  /* scan field specification */

     if (fldptr->fldtyp == 'Y') {
          cp="(ALT=NO ALT=YES)";
     }
     else {
          cp=fsdscb->fldspc+fldptr->fspoff;
     }
     for ( ; *cp != '(' ; cp++) {
          if (*cp == '\0' || isspace(*cp)) {
               return(NULL);
          }
     }
     return(nxttkn(cp+1,token,tokend));
}

static CHAR *
endtkn(                                        /* find end of token's value */
CHAR *token,                      /* token's value, as returned by foptkn() */
INT embwht)                             /* 1=value can have embedded blanks */
{                              /* returns pointer to token value terminator */
     INT i;

     for (i=0 ; i < ANSLEN && *token != '\0' && *token != ')' ; i++,token++) {
          if (isspace(*token) && !embwht) {
               break;
          }
     }
     return(token);
}

static VOID
ppcerr(                  /* fsdppc() or fsdprc() reports some kind of error */
CHAR *error,
...)
{
     va_list ap;

     va_start(ap,error);
     if (fsdemg[0] == '\0') {
          vsprintf(fsdemg,error,ap);
     }
     va_end(ap);
}

static INT
fspscn(VOID)               /* scans field specifications & updates fspoff's */
                                                  /* returns 0=ok, !0=error */
{
     struct fsdfld *ffptr;                            /* \ scans field data */
     INT fldi;                                        /* / array together   */
     CHAR *cp;                                           /* \ scan field    */
     INT i;                                              /* / spec together */
     INT errors=0;
     INT fnmlen;                                       /* field name length */

     cp=fsdscb->fldspc;
     i=0;
     ffptr=fsdscb->flddat;
     fsdscb->maxans=0;
     for (fldi=0 ; fldi < maxfld && *cp != '\0' ; ffptr++,fldi++) {
          setmem(ffptr,sizeof(struct fsdfld),0);
          while (isspace(*cp)) {
               cp++;
               i++;
          }
          if (*cp == '\0') {
               break;
          }
          ffptr->fspoff=i;
          while (*cp != '\0' && !isspace(*cp) && *cp != '(') {
               cp++;
               i++;
               fsdscb->maxans++;
          }
          fsdscb->maxans+=3;              /* room for '=' a NUL and 1 extra */
          if ((fnmlen=i-ffptr->fspoff) > FLDNAM) {
               errors++;
               ppcerr("Field name \"%0.*s\" too long",fnmlen,cp-fnmlen);
          }
          if (*cp == '(') {
               while (*cp != '\0' && *cp != ')') {
                    cp++;
                    i++;
               }
               if (*cp == ')') {
                    cp++;
                    i++;
               }
               else {
                    errors++;
                    ppcerr("Field spec \"%0.*s\" missing ')'",fnmlen,
                           fsdscb->fldspc+ffptr->fspoff);
               }
          }
     }
     fsdscb->numfld=fldi;
     return(errors);
}

static INT
chkops(VOID)                  /* check for each field's options & set flags */
{                                   /* returns number of errors encountered */
     INT fldi;                                        /* / array together   */
     CHAR *cp;

     fldi=0;
     for (fldptr=fsdscb->flddat ; fldi < fsdscb->numfld ; fldi++,fldptr++) {
          fldptr->flags=0;
          fldptr->fldtyp='\0';
          if ((cp=foptkn("MIN=",0)) != NULL) {
               fldptr->flags|=FFFMMX;
               if (*cp != '-') {
                    fldptr->flags|=FFFNNG;
               }
          }
          if (foptkn("MAX=",0) != NULL) {
               fldptr->flags|=FFFMMX;
          }
          if (foptkn("MULTICHOICE",1) != NULL) {
               fldptr->flags|=FFFMCH;
          }
          if (foptkn("ALT=",0) != NULL) {
               fldptr->flags|=FFFALT;
          }
          if (foptkn("NOSPACES",1) != NULL) {
               fldptr->flags|=FFFNSP;
          }
          if (foptkn("SECRET",1) != NULL) {
               fldptr->flags|=FFFSEC;
          }
     }
     return(0);
}

static VOID
tmpfld(                            /* log field info, used only by tmpscn() */
INT tmpoff,
CHAR width)
{
     CHAR x,y;

     if (ansiscn) {
          if ((y=curcury()) > 98) {
               y=98;
          }
          if ((x=curcurx()) > 98) {
               x=98;
          }
          if (curcury() > fsdscb->maxy) {
               fsdscb->maxy=curcury();
          }
          sprintf(fldptr->ansgto,"\x1B[%d;%df",y+1,x+1);
          fldptr->attr=curatr.attrib;
     }
     else {
          fldptr->ansgto[0]='\0';
          fldptr->attr=0x07;
     }
     fldptr->tmpoff=tmpoff;
     if (width > ANSLEN) {
          width=ANSLEN;
     }
     fldptr->xwidth=width;
     fldptr->width=width;
     fldptr->mbpoff=-1;
     fsdscb->maxans+=width;
}

static INT
tmpscn(VOID)  /* preprocess template's field information, prep for fsdent() */
{
     struct curatr ansave;                                /* save ANSI info */
     INT fldi;                                              /* field number */
     CHAR fldc,lstfldc=0;          /* current and last field id character */
     CHAR tmpidx;                             /* output from tmpspc[] table */
     CHAR subctn=0;       /* could subfield be continuing (ie no white sp)? */
     INT errors=0;
     CHAR *cp;
     INT i,j;
     CHAR *supsrt;                /* start of sequence of supporting string */

     if (ansiscn) {
          movmem(&curatr,&ansave,sizeof(struct curatr));
          setwin(fsdbuf,0,0,ANSIWD-1,ANSILN-1,0);
          cursiz(GVIDNOCURS);
          locate(0,0);
          setatr(0x07);
          ansion(1);
          supsrt=ppctmp;
     }
     fsdscb->hlplen=0;
     fldi=0;
     fldptr=fsdscb->flddat;
     fsdscb->maxy=0;
     for (cp=ppctmp,i=0 ; *cp != '\0' ; ) {
          switch(tmpidx=tmpspc[(SHORT)(fldc=*cp)]) {
          case TMPWHT:
               subctn=0;
          default:
               cp++;
               i++;
               break;

          case TMPDOL:
          case TMPEXC:
          case TMPPND:
          case TMPQST:
               if (cp[1] != fldc) {
                    cp++;
                    i++;
                    break;
               }
               if (ansiscn) {
                    *cp='\0';
                    printf("%s",supsrt);
                    *cp=fldc;
                    supsrt=cp;
               }
               for (j=0 ; *cp == fldc ; cp++,j++) {
               }
               if (fldi > 0
                && tmpidx >= TMPSFD
                && subctn
                && lstfldc == fldc) {         /* concatenate add'l subfield */
                    fldptr--;
                    fldptr->xwidth=i+j-fldptr->tmpoff;
                    fldptr->mbpoff=0;
                    fldptr->width+=j;
                    fsdscb->maxans+=j;
                    if (fldptr->xwidth > ANSLEN) {
                         fldptr->xwidth=ANSLEN;
                         fldptr->width=ANSLEN;
                    }
                    fldptr++;
               }
               else if (tmpidx == TMPEXC) {                   /* help field */
                    if (ansiscn) {
                         sprintf(fsdscb->hlpgto,"\x1B[%d;%df",curcury()+1,
                                                              curcurx()+1);
                         fsdscb->hlpatr=curatr.attrib;
                    }
                    else {
                         fsdscb->hlpgto[0]='\0';
                         fsdscb->hlpatr=0x07;
                    }
                    fsdscb->hlplen=min(j,MAXHLP);
                    fsdscb->hlpoff=i;
               }
               else if (fldi < fsdscb->numfld) {       /* regular new field */
                    tmpfld(i,j);
                    fldi++;
                    fldptr++;
                    subctn=1;
               }
               i+=j;
               lstfldc=fldc;
               break;

          case TMPSLS:
               if (i > 0
                && toupper(cp[-1]) == 'Y'
                && toupper(cp[1]) == 'N'
                && fldi < fsdscb->numfld) {
                    if (ansiscn) {
                         cp[-1]='\0';
                         printf("%s",supsrt);
                         cp[-1]='Y';
                         supsrt=cp-1;
                    }
                    tmpfld(i-1,3);
                    lstfldc=fldc;
                    if (fldptr->flags&(FFFMCH+FFFALT+FFFMMX+FFFNNG
                                      +FFFNSP+FFFSEC)) {
                         errors++;
                         ppcerr(
                             "Field %s, Y/N field cannot have options (%02X)",
                             fldnmi(fldi),fldptr->flags);
                    }
                    fldptr->flags|=FFFMCH+FFFALT;
                    fldi++;
                    fldptr++;
               }
               cp++;
               i++;
               break;
          }
     }
     fsdscb->numtpl=fldi;
     if (ansiscn) {
          printf("%s",supsrt);
          if (curcury() > fsdscb->maxy) {
               fsdscb->maxy=curcury();
          }
          rstloc();
          rstwin();
          movmem(&ansave,&curatr,sizeof(struct curatr));
          rstcur();
     }
     return(errors);
}

static INT
embscn(VOID)    /* construct individual answer-templates for embedded punct */
{
     struct fsdfld *ffptr;                            /* \ scans field data */
     INT fldi;                                        /* / array together   */
     CHAR *tp;                                       /* input scan template */
     CHAR *bp;                         /* output scan fsdscb->mbpunc buffer */
     CHAR fldc;                                     /* field type character */
     INT i,n;                      /* count format characters (###-###-###) */

     bp=fsdscb->mbpunc;
     fldi=0;
     for (ffptr=fsdscb->flddat ; fldi < fsdscb->numtpl ; fldi++,ffptr++) {
          tp=ppctmp+ffptr->tmpoff;
          fldc=*tp;
          ffptr->fldtyp=fldc;
          if (ffptr->mbpoff >= 0) {
               tp=ppctmp+ffptr->tmpoff;
               n=ffptr->xwidth;
               ffptr->mbpoff=(INT)(bp-fsdscb->mbpunc);
               for (i=0 ; i < n ; i++,bp++,tp++) {
                    *bp=(*tp == fldc || fldc == 'Y') ? ' ' : *tp;
               }
               *bp++='\0';
          }
     }
     fsdscb->mbleng=(INT)(bp-fsdscb->mbpunc);
     return(0);
}

INT
fsdppc(               /* preprocess field information, in prep for fsdent() */
CHAR *templt,                                            /* template string */
INT ascn)              /* 1=scan in preparation for ANSI session (fsdent()) */
                                 /* 0=only fsddsp() or fsdlin() will follow */
                /* implicit input:  fsdscb  points to session control block */
       /* Must call at least once for each new scb & template & field spec. */
              /* You may call immediately before calling fsdent()/fsdlin(), */
             /* or for slightly better compute efficiency, you may call way */
                  /* ahead of time for a given scb & template & field spec, */
                           /* then call fldent() multiple times on the same */
             /* fsdscb, fsdscb->fldspc, fsdscb->flddat, and fsdscb->mbpunc. */
        /* note: fsdppc() will need to write to templt, but it will recover */
  /* any changes that it makes (sticking in '\0's for efficient printf()'s) */
                                    /* see also implicit inputs to fsdent() */
            /* only output:  fsdscb->flddat, fsdscb->mbpunc, fsdscb->numfld */
   /* YOU MUST CALL fsdppc() AT SOME POINT B4 YOU CALL fsddsp() or fsdent() */
                                    /* returns number of errors encountered */
{
     INT errors;

     ppctmp=templt;
     ansiscn=ascn;
     fsdemg[0]='\0';
     errors=fspscn();       /* scan fields specs, compute fspoff's & numfld */
     errors+=chkops();                    /* check field options & set flags */
     errors+=tmpscn();                                      /* scan template */
     errors+=embscn();          /* scan for fields with embedded punctuation */
     return(errors);
}

VOID
fsdans(             /* Install answer string & compute ansoff,anslen fields */
CHAR *oldans)                         /* answer string, or "" for all blank */
                           /* call in prep for fsddsp() or fsdent() session */
                        /* if duplicate answer, earlier answer will prevail */
                                                   /* fsdscb implicit input */
{
     struct fsdfld *ffptr;                            /* \ scans field data */
     INT fldi;                                        /* / array together   */
     INT i;                 /* count characters output in new answer string */
     CHAR *cp;                          /* output initial new answer string */
     CHAR *fp;                             /* transfer name from field spec */
     CHAR *np;                   /* temp point to name in new answer string */
     INT nval;                                           /* length of value */

     cp=fsdscb->newans;
     i=0;
     fldi=0;
     for (ffptr=fsdscb->flddat ; fldi < fsdscb->numfld ; fldi++,ffptr++) {
          fp=&fsdscb->fldspc[ffptr->fspoff];
          np=cp;
          while (*fp != '\0' && !isspace(*fp) && *fp != '(') {
               *cp++=*fp++;
               i++;
          }
          *cp='\0';
          stzcpy(cp+1,fsdxan(oldans,np),ffptr->width+1);
          *cp++='=';
          i++;
          ffptr->ansoff=i;
          nval=strlen(cp);
          cp+=nval+1;
          i+=nval+1;
          ffptr->anslen=nval;
     }
     *cp='\0';
     fsdscb->allans=i+1;
}

static VOID
spaces(                                                  /* output n spaces */
INT n)                                           /* n must be 0 to ANSLEN+1 */
{
     if (n < 0) {
          n=0;
     }
     else if (n > ANSLEN+1) {
          n=ANSLEN+1;
     }
     spcblk[n]='\0';
     fsdous(spcblk);
     spcblk[n]=' ';
}

static VOID
secchs(                                     /* output n "secret" characters */
INT n)
{
     while (n-- > 0) {
          fsdouc(secchr);
     }
}

static VOID
dspans(                   /* format and display answer (& handle subfields) */
CHAR *answer,           /* answer to display (does not assume 0-terminated) */
INT justfy)  /* 1=justify right/left, 0=show minimum of field, -1=left just */

                         /* also outputs fmtend=position of first blank pad */
                         /* also outputs fmtbeg=position after initial punc */
{
     CHAR *tp;                              /* input scan template (if any) */
     CHAR *bp;                                        /* output scan buffer */
     CHAR i,n;                  /* count formatted characters (###-###-###) */
     CHAR j;               /* count meaningful field characters (#########) */
     CHAR npad;                           /* number of padding blanks req'd */
     CHAR sec;
     INT anslen;

     anslen=strlen(answer);
     npad=fldptr->width-anslen;
     n=fldptr->xwidth;
     sec=(fldptr->flags&FFFSEC) != 0;
     if (fldptr->mbpoff >= 0) {
          bp=bufptr;
          tp=fsdscb->mbpunc+fldptr->mbpoff;
          fmtend=-1;
          fmtbeg=-1;
          for (i=0,j=0 ; i < n ; i++,tp++) {
               if (*tp == ' ') {
                    if (fmtbeg == -1) {
                         fmtbeg=i;
                    }
                    if (j >= anslen) {
                         if (fmtend == -1) {
                              fmtend=i;
                         }
                         if (justfy == 0) {
                              break;
                         }
                         *bp++=' ';
                    }
                    else {
                         *bp++=sec ? secchr : *answer++;
                    }
                    j++;
               }
               else {
                    *bp++=*tp;
               }
          }
          *bp='\0';
          if (fmtend == -1) {
               fmtend=n;
          }
          if (fmtbeg == -1) {
               fmtbeg=n;
          }
          fsdous(bufptr);
     }
     else {
          if (justfy == 0 || npad == 0) {
               sec ? secchs(anslen) : fsdous(answer);
               fmtend=anslen;
          }
          else if (fldptr->fldtyp == '$' && justfy != -1) {
               spaces(npad);
               sec ? secchs(anslen) : fsdous(answer);
               fmtend=n;
          }
          else {
               sec ? secchs(anslen) : fsdous(answer);
               fmtend=anslen;
               spaces(npad);
          }
          fmtbeg=0;
     }
}

VOID
shofld(            /* show this field (goto it, attr, contents) (ANSI only) */
INT attr,                        /* implicit inputs: fsdscb, fldptr, bufptr */
INT justfy)                                          /* see dspans(), above */
{
     fsdous(fldptr->ansgto);
     fsdous(ibm2ans(attr,bufptr));
     dspans(fsdscb->newans+fldptr->ansoff,justfy);
}

static VOID
cursat(                                                      /* show cursor */
INT fldi)                                           /* on this field number */
{                                    /* also sets fsdscb->crsfld and fldptr */
     fsdscb->crsfld=fldi;
     fsdscb->shffld=fldi;
     fldptr=fsdscb->flddat+fldi;
     shofld(fsdscb->crsatr,1);
}

static VOID
shoabf(                     /* show answer buffer, set subfield pointer too */
INT justfy)                                                 /* see dspans() */
{
     dspans(fsdscb->ansbuf,justfy);
     if (fldptr->mbpoff >= 0) {
          fsdscb->ftmptr=fsdscb->mbpunc+fldptr->mbpoff+fmtend;
     }
}

static INT
vldfld(                                    /* consider a new field position */
INT fldn)                                                      /* new field */
{                               /* returns actual new field or -1=can't use */
     if (fldn < 0) {
          fldn=(udwrap && fsdscb->flags&FSDANS ? fsdscb->numtpl-1 : -1);
     }
     else if (fldn >= fsdscb->numtpl) {
          fldn=(udwrap && fsdscb->flags&FSDANS ? 0 : -1);
     }
     return(fldn);
}

static INT
movfld(       /* find a field beyond a given field (aVOIDing FFFAVD fields) */
INT fldn,                         /* proposed new field, but if can't find, */
INT finc,                                       /* moves +1=higher -1=lower */
INT resort)                                /* last resort if all else fails */
                /* returns new field, or (resort) if couldn't find anything */
{
     INT fldi,flast;

     if ((fldi=vldfld(fldn)) == -1) {
          return(resort);
     }
     flast=fldi;
     while (fsdscb->flddat[fldi].flags&FFFAVD) {
          if ((fldi=vldfld(fldi+finc)) == -1
                               || fldi == fldn
                               || fldi == flast) {
               return(resort);
          }
          flast=fldi;
     }
     return(fldi);
}

static VOID
unentr(VOID)                         /* undo entry of a field in ASCII mode */
{
     INT n;

     if (fldptr->mbpoff >= 0) {
          n=(INT)(fsdscb->ftmptr-(fsdscb->mbpunc+fldptr->mbpoff));
     }
     else {
          n=fsdscb->anslen;
     }
     while (n-- > 0) {
          fsdous("\b \b");
     }
}

static CHAR *
ck4alt(        /* check validated answer for exact match among ALT= options */
INT tokend)                     /* 1=requires exact match 0=accepts partial */
{              /* returns pointer to ALT= option value, or NULL if no match */
     CHAR *ap;

     if (fsdscb->anslen == 0) {
          return(NULL);                            /* (won't find a "ALT=") */
     }
     strcpy(bufptr,"ALT=");
     strcpy(bufptr+4,fsdscb->ansbuf);
     if ((ap=foptkn(bufptr,tokend)) != NULL) {
          return(ap-fsdscb->anslen);
     }
     return(NULL);
}

static VOID
gtohlp(VOID)
{
     CHAR buffer[14+1];

     fsdous(fsdscb->hlpgto);
     fsdous(ibm2ans(fsdscb->hlpatr,buffer));
}

VOID
fsddsp(                                /* Display screen & filled-in fields */
CHAR *templt)                       /* original template passed to fsdppc() */
                      /* expects fsdscb->flddat to contain field info, etc. */
     /* basically, requires that fsdppc() & fsdans() have been done already */
                                /* expects no intervening calls to fsdinc() */
                    /* may corrupt template (wiping out help field symbols) */
{
     INT fldi;
     CHAR *sp;            /* supporting text in template surrounding fields */
     CHAR *np;                             /* template for a specific field */
     CHAR fldc;

     if (fsdscb->hlplen > 0) {
          setmem(templt+fsdscb->hlpoff,fsdscb->hlplen,' ');
     }
     sp=templt;
     fldi=0;
     for (fldptr=fsdscb->flddat ; fldi < fsdscb->numtpl ; fldi++,fldptr++) {
          np=templt+fldptr->tmpoff;
          fldc=*np;
          *np='\0';
          fsdous(sp);
          *np=fldc;
          if (fldptr->flags&FFFAVD) {
               spaces(fldptr->xwidth);
          }
          else {
               dspans(fsdscb->newans+fldptr->ansoff,1);
          }
          sp=np+fldptr->xwidth;
     }
     fsdous(sp);
}

static VOID
entprp(VOID)              /* prepare for entry mode (go to the point state) */
{                                                  /* fsdscb implicit input */
     fsdscb->ansbuf[0]='\0';
     fsdscb->anslen=0;
     fsdscb->ansptr=0;
     fsdscb->altptr=NULL;
     fsdscb->flags&=~FSDANT;
}

static VOID
defntr(VOID)                       /* pick default value into answer buffer */
{                                       /* sets ansbuf,anslen,altptr fields */
     strcpy(fsdscb->ansbuf,fsdscb->newans+fldptr->ansoff);
     fsdscb->anslen=fldptr->anslen;
     fsdscb->altptr=ck4alt(1);
     fsdscb->flags&=~FSDANT;
}

static VOID
pmtfld(VOID)                                   /* non-ANSI prompt for field */
{                                         /* fsdscb, fldptr implicit inputs */
     CHAR *tp,tc;
     INT tn,ti;

     tp=fsdrft();
     if (fldptr > fsdscb->flddat) {
          ti=fldptr[-1].tmpoff+fldptr[-1].xwidth;
     }
     else {
          ti=0;
          fsdous("\r\n");
     }
     tc=tp[tn=fldptr->tmpoff];
     tp[tn]='\0';
     fsdous(tp+ti);
     tp[tn]=tc;
}

VOID
fsdent(                 /* begin Full Screen Data Entry session (ANSI only) */
INT inifld)                                      /* initial field, 0 to N-1 */
                     /* Implicit input:  fsdscb  global var tells it all... */
         /* Set fsdscb->fldspc to point to the field specifications string. */
         /* set fsdscb->flddat and fsdscb->mbpunc to point to the data that */
                                                       /* fsdppc() prepared */
    /* YOU MUST HAVE CALLED fsdppc() AT SOME POINT BEFORE YOU CALL fsdent() */
  /* Also b4 fsdent(): set fsdscb->newans to point to a [BIG ENOUGH] buffer */
             /* for the output answer string & call fsdans() with defaults. */
       /* Set fsdscb->fldvfy to NULL or to point to a field verify routine. */
       /* Set fsdscb->crsatr to attribute for a field when cursor is on it. */
{
     fsdscb->flags=FSDANS;
     cursat(movfld(inifld,1,inifld));
     fsdous(fldptr->ansgto);
     entprp();
     fsdscb->state=FSDAPT;
     fsdscb->chgcnt=0;
}

VOID
fsdlin(VOID)            /* begin data entry for non-ANSI (ASCII-only) users */
{                            /* basically same inputs & outputs as fsdent() */
     fsdscb->flags=0;
     fsdscb->crsfld=movfld(0,1,0);
     fldptr=fsdscb->flddat+fsdscb->crsfld;
     pmtfld();
     defntr();
     fsdscb->ansptr=fsdscb->anslen;
     shoabf(0);
     fsdscb->state=FSDNPT;
     fsdscb->chgcnt=0;
}

static VOID
fsdpci(VOID)                           /* initialization for fsdpch() calls */
{
}

static VOID
fsdpch(CHAR c)        /* process characters received after entry of a field */
{
     (VOID)c;
}

static INT
chktyp(VOID)           /* check that field type and contents are consistent */
{                                             /* candidate answer in bufptr */
     INT rc=1;
     CHAR *cp;

     switch(fldptr->fldtyp) {
     case 'Y':
     case '?':
          rc=(fldptr->flags&FFFMCH) == 0
         && ((fldptr->flags&FFFNSP) == 0 || strchr(bufptr,' ') == NULL);
          break;
     case '#':
          rc=alldgs(bufptr);
          break;
     case '$':
          cp=bufptr;
          if (*cp == '-') {
               cp++;
          }
          rc=strlen(cp) > 0 && alldgs(cp);
          break;
     }
     return(rc);
}

static INT
chkmin(VOID)    /* check that answer (in bufptr) and minimum are consistent */
{                         /* returns 1=yes or 0=no with error msg in chkmsg */
     CHAR *tp,*ep,endc;
     INT rc=1;
     LONG anum,tnum;
     INT ml;

     if (!(fldptr->flags&FFFMMX) || (tp=foptkn("MIN=",0)) == NULL) {
          return(1);
     }
     ep=endtkn(tp,0);
     endc=*ep;
     *ep='\0';
     switch(fldptr->fldtyp) {
     case '?':
          if (alldgs(tp)                          /* MIN=<minimum length> ? */
           && strlen(tp) <= 2
           && (ml=atoi(tp)) <= fldptr->width) {
               rc=strlen(bufptr) >= ml;
               if (rc == 0) {
                    sprintf(chkemg,"Enter at least %d character(s)",ml);
               }
               break;
          }                                     /* or MIN=<minimum value> ? */
     case '#':
          rc=strcmpi(tp,bufptr) <= 0;
          if (rc == 0) {
               sprintf(chkemg,"Enter at least \"%.*s\"",MAXHLP-17,tp);
          }
          break;
     case '$':
          sscanf(tp,"%ld",&tnum);
          sscanf(bufptr,"%ld",&anum);
          rc=tnum <= anum;
          if (rc == 0) {
               sprintf(chkemg,"Enter at least %ld",tnum);
          }
          break;
     }
     *ep=endc;
     return(rc);
}

static INT
chkmax(VOID)    /* check that answer (in bufptr) and maximum are consistent */
{                         /* returns 1=yes or 0=no with error msg in chkmsg */
     CHAR *tp,*ep,endc;
     INT rc=1;
     LONG anum,tnum;

     if (!(fldptr->flags&FFFMMX) || (tp=foptkn("MAX=",0)) == NULL) {
          return(1);
     }
     ep=endtkn(tp,0);
     endc=*ep;
     *ep='\0';
     switch(fldptr->fldtyp) {
     case '?':
     case '#':
          rc=strcmpi(tp,bufptr) >= 0;
          if (rc == 0) {
               sprintf(chkemg,"Enter no higher than \"%.*s\"",MAXHLP-23,tp);
          }
          break;
     case '$':
          sscanf(tp,"%ld",&tnum);
          sscanf(bufptr,"%ld",&anum);
          rc=anum <= tnum;
          if (rc == 0) {
               sprintf(chkemg,"Enter no higher than %ld",tnum);
          }
          break;
     }
     *ep=endc;
     return(rc);
}

static INT
chkalt(              /* check field answer (in bufptr) for alternate values */
INT rpt)                                /* 1=make report in chkemg, 0=don't */
                                /* returns number of possible matches found */
    /* if that was 1, copies that answer into bufptr, else bufptr undefined */
 /* if finds anything, points altptr to 1st one in the field's option list, */
          /* else NULL, and altcnt is ordinal count of that option, else -1 */
{
     CHAR *tp,*ep,endc;
     INT rc=0;
     CHAR anstmp[ANSLEN+1];
     CHAR bc;
     INT i;
     INT felen,femax;
     CHAR fsdtmp[MAXHLP+1];
     INT full;

     if (!(fldptr->flags&FFFALT) || (tp=foptkn("ALT=",0)) == NULL) {
          return(0);
     }
     rmvwht(bufptr);
     bc=toupper(bufptr[0]);
     altcnt=-1;
     altptr=NULL;
     i=0;
     if (rpt) {
          strcpy(fsdtmp,"Choices:  ");
          felen=strlen(fsdtmp);
          femax=fsdscb->flags&FSDANS ? fsdscb->hlplen : MAXHLP;
     }
     full=0;
     do {
          ep=endtkn(tp,0);
          endc=*ep;
          *ep='\0';
          if (sameto(bufptr,tp)) {
               if (bc == toupper(*tp)) {
                    if (rc == 0) {
                         altptr=tp;
                         altcnt=i;
                    }
                    strcpy(anstmp,tp);
                    rc++;
               }
               if (rpt && !full) {
                    if ((felen+=strlen(tp)+2) <= femax-3) {
                         strcat(fsdtmp,tp);
                         strcat(fsdtmp,"  ");
                    }
                    else {
                         strcat(fsdtmp,"...");
                         full=1;
                    }
               }
          }
          *ep=endc;
          i++;
     } while ((tp=nxttkn(ep,"ALT=",0)) != NULL);
     if (rc == 1) {
          strcpy(bufptr,anstmp);
          if (rpt) {
               chkemg[0]='\0';
          }
     }
     else if (rpt && fldptr->flags&FFFMCH) {
          strcpy(chkemg,fsdtmp);
     }
     return(rc);
}


static INT
stfans(VOID)   /* stuff 1 new answer into the new answer string at ->newans */
                 /* implicit inputs: fsdscb, fldptr, bufptr contains answer */
{                           /* returns true if stuffed answer was different */
     INT nl,nr;                /* # bytes to left & right of current answer */
     INT m;                                         /* length of old answer */
     struct fsdfld *efptr;        /* to adjust all subsequent ansoff fields */
     INT anslen;
     INT rc;

     anslen=strlen(bufptr);
     m=fldptr->anslen;
     nl=fldptr->ansoff;
     nr=fsdscb->allans-nl-m;
     rc=(anslen != fldptr->anslen || strcmp(bufptr,fsdscb->newans+nl) != 0);
     movmem(fsdscb->newans+nl+m,fsdscb->newans+nl+anslen,nr);
     movmem(bufptr,fsdscb->newans+nl,anslen);
     fldptr->anslen=anslen;
     efptr=fsdscb->flddat+fsdscb->numfld-1;
     while (efptr != fldptr) {
          efptr->ansoff+=anslen-m;
          efptr--;
     }
     fsdscb->allans+=anslen-m;
     return(rc);
}

static VOID
gcalarm(                                   /* tranmsit ASCII BEL if allowed */
INT beep)                                     /* 1=beep if allowed, 0=don't */
{
     if (almmmx && beep) {
          fsdouc('\7');
     }
}

static VOID
announce(                            /* Display message on help area if any */
INT beep)                                     /* 1=beep if allowed, 0=don't */
{
     if (fsdscb->flags&FSDANS) {
          if (fsdscb->hlplen > 0) {
               gtohlp();
               if (strlen(fsdemg) > fsdscb->hlplen) {
                    fsdemg[(SHORT)fsdscb->hlplen]='\0';
               }
               fsdous(fsdemg);
               gcalarm(beep);
               fsdscb->state=FSDKHP;
          }
          else {
               gcalarm(beep);
          }
     }
     else {
          fsdous("\r\n\r\n");
          fsdous(fsdemg);
          gcalarm(beep);
          fsdous("\r\n");
     }
}

static VOID
xitfsd(
CHAR state)
{
     if (!(fsdscb->flags&FSDANS)) {
          fsdous("\r\n");
     }
     fsdscb->state=state;
     fsdnfy();
}

INT
fsdprc(VOID)       /* Process (entry or display) until done (ANSI or ASCII) */
                    /* returns 0=continue processing 1=save & exit -1=abort */
   /* note, if fsdprc() does not call fsdnfy(), it does no output that pass */
                              /* also, non-zero return codes have no output */
{
     INT n,c;
     INT fldi;                                       /* index of NEXT field */
     INT vc;                                  /* field verifier return code */
     CHAR delta;
     INT dir;
     CHAR sover;                                     /* entry session over? */
     CHAR reprmt;                   /* reprompt (used only in linear entry) */

     switch (fsdscb->state) {
     case FSDBUF:                                /* Entered & checking mode */
          fldptr=fsdscb->flddat+fsdscb->entfld;
          strcpy(bufptr,fsdscb->ansbuf);
          depad(bufptr);
          if (fldptr->fldtyp == '#' || fldptr->fldtyp == '$') {
               strcpy(bufptr,skpwht(bufptr));
          }
          fsdemg[0]='\0';
          if (fsdscb->flags&FSDIGA) {
               fsdscb->flags&=~FSDIGA;
               vc=VFYDEF;
          }
          else if (fsdscb->fldvfy != NULL) {
               vc=fsdscb->fldvfy(fsdscb->entfld,bufptr);
          }
          else {
               vc=VFYCHK;
          }
          fldi=fsdscb->crsfld;
          fldptr=fsdscb->flddat+fsdscb->entfld;
          sover=(fsdscb->state != FSDBUF);
          reprmt=(fldi != fsdscb->entfld);
          delta=0;
          switch(vc) {
          case VFYOK:
               delta=stfans();
               break;
          case VFYCHK:
               chkemg[0]='\0';
               if ((chktyp() && chkmin() && chkmax()) || chkalt(1) == 1) {
                    delta=stfans();
                    break;
               }
               if (chkemg[0] != '\0') {
                    strcpy(fsdemg,chkemg);
               }
               vc=VFYREJ;
          case VFYREJ:
               fldi=fsdscb->entfld;
               if (fsdemg[0] != '\0') {
                    announce(1);
                    reprmt=1;
               }
               else {
                    gcalarm(1);
                    if (!(fsdscb->flags&FSDANS)) {
                         unentr();
                    }
               }
               break;
          }
          if (delta) {
               fldptr->flags|=FFFCHG;
          }
          if (!sover && delta && fsdscb->chgcnt < 255) {
               fsdscb->chgcnt++;
          }
          if (vc != VFYREJ) {
               if (fsdemg[0] != '\0') {
                    announce(0);
                    reprmt=1;
               }
               switch(fsdscb->xitkey) {
#ifdef UNIX
               case 'C'-64:
#else
               case 'O'-64:
#endif
               case ESC:
                    xitfsd(FSDQIT);
                    sover=1;
                    break;
               case 'G'-64:
                    xitfsd(FSDSAV);
                    sover=1;
                    break;
               }
          }
          dir=(0 < fldi && fldi < fsdscb->entfld) ? -1 : 1;
          fldi=movfld(fldi,dir,fsdscb->entfld);
          if (fsdscb->flags&FSDANS) {
               if (fldi != fsdscb->entfld || sover) {
                    shofld(fldptr->attr,1);     /* turn off old entry field */
               }
               if (!sover) {
                    cursat(fldi);                        /* show new cursor */
                    fsdous(fldptr->ansgto);                  /* & sit there */
                    entprp();
               }
          }
          else {
               if (!sover) {
                    if (0 <= fldi && fldi < fsdscb->numtpl) {
                         fsdscb->crsfld=fldi;
                         fldptr=fsdscb->flddat+fsdscb->crsfld;
                         if (reprmt) {
                              pmtfld();
                         }
                         defntr();
                         fsdscb->ansptr=fsdscb->anslen;
                         shoabf(0);
                    }
                    else {
                         xitfsd(FSDSAV);             /* (non-ANSI, no wrap) */
                    }
               }
          }
          if (fsdscb->state == FSDBUF) {
               fsdscb->hdlahd=0;
               fsdscb->state=FSDSTB;
               fsdnfy();
          }
          break;
     case FSDSTB:                    /* Entered, checked, now catch-up mode */
          if (fsdscb->ahdptr > (n=fsdscb->hdlahd)) {
               fsdpci();
               while (fsdscb->ahdptr > n) {
                    if ((c=fsdscb->typahd[n]) == '\0') {
                         c=(fsdscb->typahd[n+1])<<8;
                         n+=2;
                    }
                    else {
                         n++;
                    }
                    fsdpch(c);
              }
               fsdscb->hdlahd=n;
               fsdnfy();
          }
          else {
               dsairp();
               if (fsdscb->hdlahd >= fsdscb->ahdptr && fsdoba() == fsdomt) {
                    fsdscb->state=(fsdscb->flags&FSDANS) ? FSDAPT : FSDNPT;
                    enairp();
               }
               else {
                    enairp();
                    fsdnfy();
               }
          }
          break;
     case FSDSAV:
          return(1);
     case FSDQIT:
          return(-1);
     }
     return(0);
}

static VOID
ansmov(                                      /* output an ANSI move command */
INT val,                                                            /* 0-99 */
CHAR cmd)                                     /* A=up B=down C=right D=left */
{
     if (val > 0 && val < 100) {
          fsdous("\x1B[");
          if (val > 1) {
               if (val > 9) {
                    fsdouc((val/10)+'0');
               }
               fsdouc((val%10)+'0');
          }
          fsdouc(cmd);
     }
}

static VOID
skppnc(VOID)                      /* skip beginning punctuation in template */
{
     CHAR c;

     if (fldptr->mbpoff >= 0) {
          fsdscb->ftmptr=fsdscb->mbpunc+fldptr->mbpoff;
          while ((c=*fsdscb->ftmptr) != ' ' && c != '\0') {
               fsdouc(c);
               fsdscb->ftmptr++;
          }                          /* (needed when punc can prefix field) */
     }
}

static VOID
skcpnc(VOID)             /* skip character and more punctuation in template */
{
     CHAR c;

     fsdouc(fldptr->flags&FFFSEC ? secchr
                              : fsdscb->ansbuf[(SHORT)fsdscb->ansptr]);
     fsdscb->ansptr++;
     if (fldptr->mbpoff >= 0) {
          while ((c=*++fsdscb->ftmptr) != ' ' && c != '\0') {
               fsdouc(c);
          }
     }
}

static VOID
skepnc(VOID)                                       /* skip to end of answer */
{
     CHAR ch;
     INT j,k;

     if (fldptr->mbpoff >= 0) {
          for (k=0,j=fsdscb->ansptr ; (ch=*fsdscb->ftmptr) != '\0' ; k++) {
               if (ch == ' ' && j++ >= fsdscb->anslen) {
                    break;
               }
               fsdscb->ftmptr++;
          }
     }
     else {
          k=fsdscb->anslen-fsdscb->ansptr;
     }
     ansmov(k,'C');
     fsdscb->ansptr=fsdscb->anslen;
}

static VOID
xitfld(                        /* Exit the entry state & move the cursor... */
INT finc)                                      /* +1=higher -1=lower 0=stay */
{
     INT fldn;

     fsdscb->ansbuf[(SHORT)fsdscb->anslen]='\0';
     fsdscb->entfld=fsdscb->crsfld;
     if ((fldn=movfld(fsdscb->crsfld+finc,finc,-1)) != -1) {
          fsdscb->crsfld=fldn;
          if (fsdscb->flags&FSDANS) {
               fsdous(fsdscb->flddat[fldn].ansgto);
          }
     }
     fsdscb->ahdptr=0;
     fsdscb->state=FSDBUF;
     fsdnfy();
}

static VOID
hopfld(                             /* Hop cursor from the current field... */
INT fldn,                             /* ...to a new field (index 0 to N-1) */
INT finc)      /* or if that don't fly, then +1=higher -1=lower 0=only fldn */
                   /* implicit input:  fldptr refer to current cursor field */
{
     INT fldi;

     if ((fldi=movfld(fldn,finc,-1)) == -1) {
          return;
     }
     if (fsdscb->flags&FSDQOT) {         /* Big output already in progress, */
          if (fldptr->width > 40 || fsdscb->flddat[fldi].width > 40) {
               return;    /* really big fields?  ignore cursor keys for now */
          }
          fldptr=fsdscb->flddat+fldi;
          fsdscb->crsfld=fldi;
          fsdous(fldptr->ansgto);                      /* just move cursor. */
          fsdscb->flags|=FSDSHN;
     }
     else {                                      /* No big output underway, */
          shofld(fldptr->attr,1);
          cursat(fldi);                                      /* shuffle now */
          fsdous(fldptr->ansgto);
          fsdscb->flags|=FSDQOT;
     }
}

static VOID
bgnter(VOID)                                 /* begin entry/edit of a field */
{                                       /* implicit inputs:  fsdscb, fldptr */
                                      /* fsdscb->ansbuf is initial contents */
                             /* computes ansptr & anslen fields from ansbuf */
                                /* shows blank field, doesn't show contents */
     CHAR buffer[14+1];

     if (fsdscb->flags&FSDANS) {
          fsdous(fldptr->ansgto);
          fsdous(ibm2ans(fsdscb->crsatr,buffer));
          if (fldptr->mbpoff >= 0) {
               fsdous(fsdscb->mbpunc+fldptr->mbpoff);
          }
          else {
               spaces(fldptr->width);
          }
          fsdous(fldptr->ansgto);
     }
     else {
          unentr();
     }
     fsdscb->ansptr=fsdscb->anslen=strlen(fsdscb->ansbuf);
     skppnc();
}

static VOID
altntr(VOID)                      /* begin entry/edit of an alternate value */
           /* value in bufptr, altptr points to corrsp ALT= in options list */
{
     fsdscb->altptr=altptr;
     strcpy(fsdscb->ansbuf,bufptr);
     bgnter();
     if (fsdscb->flags&FSDANS) {
          shoabf(-1);
          ansmov(fldptr->xwidth-fmtend,'D');
     }
     else {
          shoabf(0);
     }
     fsdscb->flags&=~FSDANT;
     fsdscb->flags|=FSDQOT;
}

static INT
hdlalt(CHAR c)                                    /* handle alternate value */
{                          /* returns 1=literal 0=bad -1=complete & handled */
                                        /* implicit inputs:  fsdscb, fldptr */
     CHAR *ep,endc;

     if (!(fldptr->flags&FFFALT)) {
          return(0);
     }
     if (c == ' ' && fldptr->flags&FFFMCH && !(fsdscb->flags&FSDQOT)) {
          if ((altptr=fsdscb->altptr) != NULL) {
               altptr=nxttkn(altptr,"ALT=",0);
          }
          else if (fsdscb->anslen > 0) {
               altptr=ck4alt(0);
          }
          if (altptr == NULL) {
               altptr=foptkn("ALT=",0);
          }
          if (altptr == NULL || altptr == fsdscb->altptr) {
               return(0);
          }
          ep=endtkn(altptr,0);
          endc=*ep;
          *ep='\0';
          strcpy(bufptr,altptr);
          *ep=endc;
          altntr();
          return(-1);
     }
     else if (c != ' ') {
          ep=bufptr;
          if (fsdscb->flags&FSDANT) {
               strcpy(bufptr,fsdscb->ansbuf);
               ep+=fsdscb->anslen;
          }
          *ep++=c;
          *ep='\0';
          switch (chkalt(0)) {
          case 0:
               break;
          case 1:
               altntr();
               return(-1);
          default:
               if (!(fsdscb->flags&FSDANT)) {
                    fsdscb->flags|=FSDANT;
                    fsdscb->altptr=NULL;
                    if (fsdscb->anslen > 0) {
                         fsdscb->ansbuf[0]='\0';
                         bgnter();
                    }
               }
               return(1);
          }
     }
     return(0);
}

static INT
hdlprt(CHAR c)                                /* handle printable character */
                  /* (fsdscb is input, may muck with fsdscb->ansbuf[] etc.) */
               /* expects fsdscb->anslen to be # characters entered, if any */
{                  /* returns 0=ignore, 1=enter the character, -1=we did it */
     switch (fldptr->fldtyp) {
     case 'Y':
     case '?':
          if (fldptr->flags&FFFMCH) {
               return(hdlalt(c));
          }
          return(1);
     case '#':
     case '$':
          if ((!(fsdscb->flags&FSDANT) || fsdscb->anslen == 0)
              && (isdigit(c)
                  || (c == '-'
                      && fldptr->fldtyp == '$'
                      && fsdscb->ansptr == 0
                      && !(fldptr->flags&FFFNNG)))) {
               if (fsdscb->flags&FSDANT || fsdscb->altptr != NULL) {
                    fsdscb->ansbuf[0]='\0';
                    bgnter();
                    fsdscb->flags&=~FSDANT;
                    fsdscb->altptr=NULL;
               }
               return(1);
          }
          return(hdlalt(c));
     default:
          return(1);
     }
}

static VOID
addprt(CHAR c)     /* try to add printable character to end of entry buffer */
{
     INT n;

     if ((SHORT)fsdscb->ansptr < fldptr->width
      && (!(fldptr->flags&FFFNSP) || c != ' ')) {
          fsdscb->ansbuf[(SHORT)(fsdscb->ansptr++)]=c;
          if ((n=fsdscb->ansptr) > fsdscb->anslen) {
               fsdscb->ansbuf[(SHORT)(fsdscb->anslen=n)]='\0';
          }
          fsdouc(fldptr->flags&FFFSEC ? secchr : c);   /* echo entered char */
          if (fldptr->mbpoff >= 0) {
               while ((c=*++fsdscb->ftmptr) != ' ' && c != '\0') {
                    fsdouc(c);
               }                             /* advance over embedded punct */
          }
     }
}

static INT
hdlnbs(VOID)               /* handle backspace in ANSI mode -- move cursors */
{                              /* returns 1=has embedded punctuation, 0=not */
     INT j;

     fsdscb->ansptr--;
     if (fldptr->mbpoff >= 0) {
          for (j=1 ; *--fsdscb->ftmptr != ' ' ; j++) {
          }
          ansmov(j,'D');
          return(1);
     }
     else {
          fsdouc('\b');
          return(0);
     }
}

static INT
bstalt(VOID)                    /* can we bust through the alternate value? */
{
     if (fsdscb->altptr != NULL) {
          if (fldptr->flags&FFFMCH      /* no, this is a multi choice field */
           || fldptr->fldtyp != '?') {       /* no, this is a numeric field */
               return(0);
          }
          fsdscb->altptr=NULL;                        /* yes, starting over */
     }
     return(1);                             /* yes, there wasn't one anyway */
}

static VOID
hdlcbs(VOID)   /* handle backsp in ASCII mode -- move cursors destructively */
{
     if (fsdscb->ansptr > 0 && bstalt()) {
          fsdscb->ansptr--;
          fsdscb->anslen--;
          if (fldptr->mbpoff >= 0) {
               while (*--fsdscb->ftmptr != ' ') {
                    fsdous("\b \b");
               }
          }
          fsdous("\b \b");
          fsdscb->ansbuf[(SHORT)fsdscb->anslen]='\0';
     }
}

static VOID
dprest(VOID)                                 /* display rest of entry field */
{
     CHAR sec;
     CHAR i,j,k,n;
     CHAR *cp;

     n=fsdscb->anslen;
     i=fsdscb->ansptr;
     sec=(fldptr->flags&FFFSEC) != 0;
     if (fldptr->mbpoff >= 0) {
          for (cp=fsdscb->ftmptr,j=i,k=0 ; j < n ; k++,cp++) {
               if (*cp == ' ') {
                    fsdouc(sec ? secchr : fsdscb->ansbuf[(SHORT)j]);
                    j++;
               }
               else {
                    fsdouc(*cp);
               }
          }
     }
     else {
          k=n-i;
          if (sec) {
               secchs(k);
          }
          else {
               fsdous(fsdscb->ansbuf+i);
          }
     }
     if (sec && n >= 1 && fsdscb->ansbuf[n-1] == ' ') {
          fsdous("\b ");
     }
     ansmov(k,'D');
}

VOID
fsdinc(                            /* Handle Incoming character (ANSI-only) */
INT c)                                       /* keystroke code, ala GCOMM.H */
{                                            /* may be called via interrupt */
     INT i,j,n;
     CHAR ch;
     struct fsdfld *savfld;
     CHAR longot=0;

     savfld=fldptr;
     fldptr=fsdscb->flddat+fsdscb->crsfld;
     bufptr=qbuff;
     if (fsdscb->state == FSDKHP) {
          fsdscb->state=FSDAPT;
          gtohlp();
          spaces(fsdscb->hlplen);
          fsdous(fldptr->ansgto);
          longot=1;
     }
     switch(fsdscb->state) {
     case FSDAPT:                              /* Cursor pointing ANSI mode */
          switch(c) {
          case CRSRUP:
          case BAKTAB:
          case 'U'-64:
               hopfld(fsdscb->crsfld-1,-1);
               break;
          case 'L'-64:
               fsdqdp();
               break;
          case '\r':
               defntr();
               fsdscb->xitkey=c;
               xitfld(1);
               break;
          case CRSRDN:
          case TAB:
               hopfld(fsdscb->crsfld+1,1);
               break;
          case CTRLPGUP:
          case CTRLHOME:
               hopfld(0,1);
               break;
          case CTRLPGDN:
          case CTRLEND:
               hopfld(fsdscb->numtpl-1,-1);
               break;
          case CRSRRT:             /* rt/lf arrow/etc initiated field entry */
          case CRSRLF:
          case HOME:
          case END:
          case '\b':
               if (fsdscb->flags&FSDQOT || fldptr->flags&FFFMCH) {
                    break;
               }
               defntr();
               if (!bstalt()) {
                    break;
               }
               shofld(fsdscb->crsatr,-1);
               fsdous(fldptr->ansgto);
               skppnc();
               if (fsdscb->anslen > 0) {
                    if (c == CRSRRT) {
                         skcpnc();
                    }
                    else if (c == END) {
                         skepnc();
                    }
               }
               fsdscb->state=FSDAEN;
               break;
          case 'F'-64:
               if (fsdscb->flags&FSDQOT
                || fldptr->flags&FFFMCH
                || fldptr->anslen == fldptr->width) {
                    break;
               }
               defntr();
               if (!bstalt()) {
                    break;
               }
               memmove(fsdscb->ansbuf+1,fsdscb->ansbuf,++fsdscb->anslen);
               fsdscb->ansbuf[0]=' ';
               fsdous(fldptr->ansgto);
               fsdous(ibm2ans(fsdscb->crsatr,bufptr));
               shoabf(-1);
               fsdous(fldptr->ansgto);
               skppnc();
               fsdscb->state=FSDAEN;
               break;
          case '\x7F':
          case DEL:
               if (fsdscb->flags&FSDQOT
                || fldptr->flags&FFFMCH
                || fldptr->anslen == 0) {
                    break;
               }
               defntr();
               if (!bstalt()) {
                    break;
               }
               memmove(fsdscb->ansbuf,fsdscb->ansbuf+1,fsdscb->anslen--);
               fsdous(fldptr->ansgto);
               fsdous(ibm2ans(fsdscb->crsatr,bufptr));
               shoabf(-1);
               fsdous(fldptr->ansgto);
               skppnc();
               fsdscb->state=FSDAEN;
               break;
          default:
               if (' ' <= c && c < 256 && !(fsdscb->flags&FSDQOT)) {
                    defntr();                            /* (to get altptr) */
                    fsdscb->ansptr=0;                   /* (allows - entry) */
                    switch(hdlprt(c)) {
                    case 1:
                         fsdscb->ansbuf[0]='\0';
                         bgnter();
                         addprt(c);
                    case -1:
                         fsdscb->state=FSDAEN;
                         break;
                    }
               }
               break;
#ifdef UNIX
          case 'C'-64:
#else
          case 'O'-64:
#endif
          case 'G'-64:
          case ESC:
               defntr();
               fsdscb->xitkey=c;
               xitfld(0);
               break;
          }
          break;
     case FSDAEN:                                  /* Field entry ANSI mode */
          switch(c) {
          case CRSRUP:
          case BAKTAB:
          case 'U'-64:
               fsdscb->xitkey=c;
               xitfld(-1);
               break;
          case 'L'-64:
               fsdqdp();
               break;
          case '\r':
          case TAB:
          case CRSRDN:
               fsdscb->xitkey=c;
               xitfld(1);
               break;
          case CRSRRT:
               if (fsdscb->ansptr < fsdscb->anslen
                && bstalt()
                && !(fldptr->flags&FFFMCH)) {
                    fsdscb->ansptr++;
                    if (fldptr->mbpoff >= 0) {
                         for (j=1 ; (ch=*++fsdscb->ftmptr) != ' ' && ch != '\0'
                                  ; j++) {
                         }
                         ansmov(j,'C');
                    }
                    else {
                         fsdous("\x1B[C");
                    }
               }
               break;
          case CRSRLF:
               if (fsdscb->ansptr > 0
                && bstalt()
                && !(fldptr->flags&FFFMCH)) {
                    fsdscb->ansptr--;
                    if (fldptr->mbpoff >= 0) {
                         for (j=1 ; *--fsdscb->ftmptr != ' ' ; j++) {
                         }
                         ansmov(j,'D');
                    }
                    else {
                         fsdous("\x1B[D");
                    }
               }
               break;
          case HOME:
               if (!(fldptr->flags&FFFMCH)
                && bstalt()) {
                    fsdscb->ansptr=0;
                    if (fldptr->mbpoff >= 0) {
                         fsdscb->ftmptr=fsdscb->mbpunc+fldptr->mbpoff;
                    }
                    fsdous(fldptr->ansgto);
               }
               break;
          case END:
               if (!(fldptr->flags&FFFMCH)
                && bstalt()) {
                    skepnc();
               }
               break;
          default:
               if (' ' <= c && c < 256) {
                    if (hdlprt(c) == 1) {
                         addprt(c);
                    }
               }
               break;
          case 'F'-64:
               if ((SHORT)fsdscb->anslen >= fldptr->width || !bstalt()) {
                    break;
               }
               n=++fsdscb->anslen;
               i=fsdscb->ansptr;
               memmove(fsdscb->ansbuf+i+1,fsdscb->ansbuf+i,n-i);
               fsdscb->ansbuf[i]=' ';
               dprest();
               break;
          case '\x7F':
          case DEL:
               if ((i=fsdscb->ansptr) == (n=fsdscb->anslen) || !bstalt()) {
                    break;
               }
               if (i == n-1) {
                    fsdous(" \b");
               }
               else {
                    memmove(fsdscb->ansbuf+i,fsdscb->ansbuf+i+1,n-i-1);
                    fsdscb->ansbuf[n-1]=' ';
                    dprest();
               }
               fsdscb->ansbuf[(SHORT)--fsdscb->anslen]='\0';
               break;
          case '\b':
               if ((i=fsdscb->ansptr) == 0 || !bstalt()) {
                    break;
               }
               if ((n=fsdscb->anslen) == i) {
                    hdlnbs();
                    fsdous(" \b");
               }
               else {
                    memmove(fsdscb->ansbuf+i-1,fsdscb->ansbuf+i,n-i);
                    fsdscb->ansbuf[n-1]=' ';
                    hdlnbs();
                    dprest();
               }
               fsdscb->ansbuf[(SHORT)--fsdscb->anslen]='\0';
               break;
#ifdef UNIX
          case 'C'-64:
#else
          case 'O'-64:
#endif
          case 'G'-64:
          case ESC:
               fsdscb->xitkey=c;
               xitfld(0);
               break;
          }
          break;
     case FSDNPT:                                    /* Non-ANSI Point mode */
          if ('!' <= c && c < 256) {   /* (note, CTRL char always appended) */
               fsdscb->state=FSDNEN;
               fsdscb->ansptr=0;                        /* (allows - entry) */
               if (hdlprt(c) == 1) {
                    fsdscb->ansbuf[0]='\0';
                    bgnter();
                    addprt(c);
               }
               fsdscb->ansptr=fsdscb->anslen;
               break;
          }
     case FSDNEN:                                    /* Non-ANSI Entry mode */
          switch(c) {
          case CRSRDN:
          case '\r':
          case TAB:
               fsdscb->xitkey=c;
               if (fsdscb->crsfld < fsdscb->numtpl-1) {
                    xitfld(1);
               }
               else {
                    xitfld(0);
                    fsdscb->crsfld++;
               }
               break;
          case CRSRUP:
          case BAKTAB:
          case 'U'-64:
               if (movfld(fsdscb->crsfld-1,-1,-1) == -1) {
                    break;
               }
               if (fsdscb->state == FSDNEN) {
                    unentr();
               }
               fsdscb->xitkey=c;
               xitfld(-1);
               fsdscb->flags|=FSDIGA;
               break;
          case 'L'-64:
               fsdqdp();
               break;
          case '\b':
          case '\x7F':
          case DEL:
               fsdscb->state=FSDNEN;
               hdlcbs();
               break;
          default:
               if (' ' <= c && c < 256) {
                    fsdscb->state=FSDNEN;
                    if (hdlprt(c) == 1) {
                         addprt(c);
                    }
               }
               break;
#ifdef UNIX
          case 'C'-64:
#else
          case 'O'-64:
#endif
          case 'G'-64:
          case ESC:
               fsdscb->xitkey=c;
               xitfld(0);
               break;
          }
          break;
     case FSDBUF:                                /* Entered & checking mode */
     case FSDSTB:                        /* Entered checked & catch-up mode */
          if (fsdscb->ahdptr <= EXTHED-2) {
               if ((c&0xFF00) != 0) {
                    fsdscb->typahd[(SHORT)fsdscb->ahdptr++]='\0';
                    c>>=8;              /* code special keystroke in ansbuf */
               }
               fsdscb->typahd[(SHORT)fsdscb->ahdptr++]=(CHAR)c;
          }
          break;
     }
     if (longot) {
          fsdscb->flags|=FSDQOT;
     }
     bufptr=pbuff;
     fldptr=savfld;
}

VOID
fsdqoe(VOID)          /* Report that the quick output buffer has gone empty */
            /* Called by application after fsdinc() initiated output when   */
            /* that output is truly out, and (importantly) the quick output */
            /* buffer is completely empty                                   */
{
     struct fsdfld *savfld;

     fsdscb->flags&=~FSDQOT;
     if (fsdscb->flags&FSDSHN) {
          fsdscb->flags&=~FSDSHN;
          savfld=fldptr;
          bufptr=qbuff;
          switch (fsdscb->state) {
          case FSDAPT:                          /* delayed cursor shuffling */
          case FSDKHP:
               fldptr=fsdscb->flddat+fsdscb->shffld;
               shofld(fldptr->attr,1);
               cursat(fsdscb->crsfld);
               fsdous(fldptr->ansgto);
               break;
          case FSDAEN:        /* 1/2 shuffle (now entering at new position) */

               /*
               Actually, we don't ever get to this case because the
               FSDAEN state never begins when FSDSHN.  That's due to the
               triple echo-buffer burden of uncursor-cursor-retype.
               Uncursor:  turn off cursor attribute for old field.
               Cursor:  turn on for new field.  Retype:  redisplay blank
               new field in preparation for entry.  It only takes two
               keystrokes to induce this sequence (e.g. CRSRDN 'A').  If they
               come too fast, the 2nd keystroke will be ignored.

               fsdous("\x1B[s");
               fldptr=fsdscb->flddat+fsdscb->shffld;
               shofld(fldptr->attr,1);                   / * turn off old. * /
               fsdous("\x1B[u");
               fsdous(ibm2ans(fsdscb->crsatr,buffer));
               */

               break;
          }
          bufptr=pbuff;
          fldptr=savfld;
     }
}

/*--- VERIFY ROUTINE ---*/

INT
vfyadn(   /* factory-issue field verify routine, for ask-done-at-end scheme */
INT fldno,          /* field number, (fldptr is redundantly also available) */
CHAR *answer)                                            /* proposed answer */
{
     if (fldno == fsdscb->numtpl-1) {
          switch (fsdscb->xitkey) {
          case TAB:
          case CRSRDN:
               if (!(fsdscb->flags&FSDANS) || !udwrap) {
                    break;
               }
          case CRSRUP:
          case BAKTAB:
          case 'U'-64:
          case 'G'-64:
#ifdef UNIX
          case 'C'-64:
#else
          case 'O'-64:
#endif
          case ESC:
               return(VFYDEF);
          }
          switch(toupper(answer[0])) {
          case 'S':                                                 /* Save */
          case 'Y':                                    /* Yes=done and save */
          case 'D':                                                 /* Done */
               xitfsd(FSDSAV);
               return(VFYOK);
          case 'Q':                                                 /* Quit */
          case 'X':                                                 /* eXit */
          case 'A':                                     /* Abort or Abandon */
               xitfsd(FSDQIT);
               return(VFYOK);
          case 'E':                                                 /* Edit */
          case 'N':                                    /* No=edit some more */
               fsdscb->crsfld=0;
               return(VFYDEF);
          }
     }
     else {
#ifdef UNIX
          if ((fsdscb->xitkey == 'C'-64
#else
          if ((fsdscb->xitkey == 'O'-64
#endif // UNIX
            || fsdscb->xitkey == ESC)
           && (fsdscb->chgcnt > 0
            || strcmp(answer,fsdscb->newans+fldptr->ansoff) != 0)) {
#ifdef UNIX
               strcpy(fsdemg,"Are you sure?  Enter QUIT or ^C to quit now.");
#else
               strcpy(fsdemg,"Are you sure?  Enter QUIT or ^O to quit now.");
#endif // UNIX
               fsdscb->crsfld=fsdscb->numtpl-1;
               fsdscb->xitkey=0;
          }
     }
     return(VFYCHK);
}

/*--- ANSWER HANDLING UTILITIES -- Call on any unprocessed answer string ---*/

INT
stranslen(              /* find length of answer string (incl final "\0\0") */
CHAR *anstg)                      /* multi-string answer string (see FSD.H) */
{                   /* returns length including final double-NUL terminator */
     INT n,len;

     for (len=0 ; *anstg != '\0' ; anstg+=n,len+=n) {
          n=strlen(anstg)+1;
     }
     return(len+1);
}

CHAR *
fsdxan(                /* no-preparation- extract answer from answer string */
CHAR *answer,                                              /* answer string */
CHAR *name)                               /* field name (all caps required) */
                      /* returns extracted string, '\0' terminated if found */
        /* (and sets global xannam to point to field name in answer string) */
  /* otherwise return value and xannam point to final '\0' of answer string */
{
     INT n;

     n=strlen(name);
     for (xannam=answer ; *xannam != '\0' ; xannam+=strlen(xannam)+1) {
          if (sameto(name,xannam) && xannam[n] == '=') {
               return(xannam+n+1);
          }
     }
     return(xannam);
}

VOID
fsdpan(                                    /* put answer into answer string */
CHAR *answer,               /* answer string (must have room for new value) */
CHAR *name,                               /* field name (all caps required) */
CHAR *value)                                                   /* new value */
{
     CHAR *vp;
     INT nold,nnew,nstr;

     vp=fsdxan(answer,name);
     if (*xannam == '\0') {
          sprintf(xannam,"%s=%s%c",name,value,'\0');
     }
     else {
          nold=strlen(vp);
          nnew=strlen(value);
          nstr=stranslen(vp+nold+1);
          if (nstr > 0 && nold != nnew) {
               movmem(vp+nold+1,vp+nnew+1,nstr);
          }
          strcpy(vp,value);
     }
}

VOID
fsddan(VOID)   /* delete answer just found by fsdxan() fm its answer string */
           /* (this routine has no effect if fsdxan() didn't find anything) */
{
     INT n;

     if ((n=strlen(xannam)) > 0) {
          movmem(xannam+n+1,xannam,stranslen(xannam+n+1));
     }
}

VOID
fsdbd1(                            /* 1st step to building an answer string */
CHAR *answers)                                           /* where to put it */
{
     bdans=answers;
     *bdans='\0';
}

VOID
fsdbdn(                           /* next step to building an answer string */
CHAR *name,
CHAR *value)
{
     if (value[0] != '\0') {
          sprintf(bdans,"%s=%s",name,value);
          bdans+=strlen(bdans)+1;
          *bdans='\0';
     }
}

VOID
fsdbdf(INT fldno)   /* build answer string from field # from recent session */
{
     fsdbdn(fldnmi(fldno),fsdnan(fldno));
}

/*--- ANSWER HANDLING UTILITIES -- Call only after fsdans() ---*/

CHAR *
fldnmi(                         /* extract field name based on field number */
INT fldi)                        /* expects fsdscb as input, after fsdppc() */
               /* call only after fsdans(), or an entry session is complete */
{
     static CHAR fldnam[FLDNAM+1];
     CHAR *cp,*np;
     INT i;

     np=fldnam;
     i=0;
     cp=fsdscb->fldspc+fsdscb->flddat[fldi].fspoff;
     while (*cp != '\0' && !isspace(*cp) && *cp != '(' && i++ < FLDNAM) {
          *np++=*cp++;
     }
     *np='\0';
     return(fldnam);
}

CHAR *
fsdfxt(                      /* extract answer from answer string by number */
INT fldi,                             /* field number 0 to fsdscb->numfld-1 */
CHAR *buffer,                                            /* where to put it */
INT length)                             /* bytes in buffer, incl final '\0' */
               /* call only after fsdans(), or an entry session is complete */
{
     if (0 <= fldi && fldi < fsdscb->numfld) {
          stzcpy(buffer,fsdnan(fldi),length);
     }
     else {
          buffer[0]='\0';
     }
     return(buffer);
}

CHAR *
fsdnan(                      /* extract answer from answer string by number */
INT fldi)                             /* field number 0 to fsdscb->numfld-1 */
               /* call only after fsdans(), or an entry session is complete */
{
     return(fsdscb->newans+fsdscb->flddat[fldi].ansoff);
}

INT
fsdsan(                                          /* set answer to new value */
INT fldi,                                            /* answer field number */
CHAR *value)                                                   /* new value */
                                           /* returns 1=changed 0=no change */
               /* call only after fsdans(), or an entry session is complete */
{
     fldptr=fsdscb->flddat+fldi;
     if (strlen(value) > ANSLEN) {
          stzcpy(bufptr,value,ANSLEN+1);
     }
     else {
          strcpy(bufptr,value);
     }
     return(stfans());
}

VOID
fsdman(                          /* stuff more answers into current session */
CHAR *ansstg)                              /* answer string full of answers */
               /* call only after fsdans(), or an entry session is complete */
{
     INT len,fldi;
     CHAR c,*fp,*np;
     CHAR same;

     for ( ; (len=strlen(ansstg)) != 0 ; ansstg+=len+1) {
          fldptr=fsdscb->flddat;
          for (fldi=0 ; fldi < fsdscb->numfld ; fldi++,fldptr++) {
               fp=fsdscb->fldspc+fldptr->fspoff;
               np=ansstg;
               same=1;
               while ((c=*fp++) != '\0' && !isspace(c) && c != '(') {
                    if (*np++ != c) {
                         same=0;
                         break;
                    }
               }
               if (same && *np == '=') {
                    fsdsan(fldi,np+1);
                    break;
               }
          }
     }
}

INT
fsdord(  /* returns ordinal value of multiple-choice answer, per field spec */
INT fldi)                             /* field number 0 to fsdscb->numfld-1 */
                 /* if N alternate values, returns 0..N-1 or -1 if no match */
               /* call only after fsdans(), or an entry session is complete */
                                /* only returns 0..N-1 if unequivocal match */
                      /* in that case, answer is available via fsdnan(fldi) */
{
     fldptr=fsdscb->flddat+fldi;
     strcpy(bufptr,fsdscb->newans+fldptr->ansoff);
     if (chkalt(0) == 1) {
          stfans();
          return(altcnt);
     }
     else {
          return(-1);
     }
}

/*--- OTHER UTILITIES ---*/

static VOID
whiteout(                                 /* whiteout non-ANSI non-EOL text */
CHAR *stg,                                                 /* starting here */
INT n)                                    /* and up to this many characters */
{
     INT state;
     CHAR c;

     for (state=0 ; n > 0 && (c=*stg) != '\0' ; stg++,n--) {
          switch (state) {
          case 0:
               switch(c) {
               case '\x1B':
                    state=1;
                    break;
               case '\r':
               case '\n':
               case '\t':
               case '\f':
               case '\b':
                    break;
               default:
                    *stg=' ';
                    break;
               }
               break;
          case 1:
               if (c == '[') {
                    state=2;
               }
               else {
                    state=0;
                    stg[-1]=' ';
                    stg[0]=' ';
               }
               break;
          case 2:
               if (!isdigit(c) && c != ';') {
                    state=0;
               }
               break;
          }
     }
}

VOID
tpwipe(           /* wipe out supporting text surrounding field in template */
CHAR *templt,                                              /* template text */
INT fldi,                                                   /* field number */
INT preitems,       /* 0=ignore 1=blank to preceding EOL/ANSI 2=to next ... */
INT postitems)     /* 0=ignore 1=blank to subsequent EOL/ANSI 2=to next ... */
                                  /* expects fsdscb as prepared by fsdppc() */
{
     INT n,nlimit,cnt;
     CHAR *cp,*sp;

     if (fldi < 0 || fsdscb->numtpl <= fldi) {
          return;
     }
     fldptr=fsdscb->flddat+fldi;
     nlimit=(fldi == 0 ? 0 : fldptr[-1].tmpoff+fldptr[-1].xwidth);
     cnt=0;
     for (cp=templt+(n=fldptr->tmpoff) ; n > nlimit && preitems > 0 ; n--) {
          cp--;
          switch (*cp) {
          case '\r':
          case '\x1B':
               preitems--;
               break;
          }
          cnt++;
     }
     whiteout(cp,cnt);
     nlimit=(fldi < fsdscb->numtpl-1 ? fldptr[1].tmpoff : strlen(templt));
     n=fldptr->tmpoff+fldptr->xwidth;
     cnt=0;
     for (sp=cp=templt+n ; n < nlimit && postitems > 0 ; n++,cp++) {
          switch (*cp) {
          case '\r':
          case '\x1B':
               postitems--;
               break;
          }
          cnt++;
     }
     whiteout(sp,cnt-1);
}
