/*
 * prompter.c -- simple prompting editor front-end
 *
 * $Id: prompter.c,v 1.5 2003/09/30 16:58:43 gbburkhardt Exp $
 *
 * This code is Copyright (c) 2002, by the authors of nmh.  See the
 * COPYRIGHT file in the root directory of the nmh distribution for
 * complete copyright information.
 */

#include <h/mh.h>
#include <fcntl.h>
#include <h/signals.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>

#ifdef HAVE_TERMIOS_H
# include <termios.h>
#else
# ifdef HAVE_TERMIO_H
#  include <termio.h>
# else
#  include <sgtty.h>
# endif
#endif

#define	QUOTE '\\'

#ifndef	CKILL
# define CKILL '@'
#endif

#ifndef	CERASE
# define CERASE '#'
#endif

static struct swit switches[] = {
#define	ERASESW	0
    { "erase chr", 0 },
#define	KILLSW	1
    { "kill chr", 0 },
#define	PREPSW	2
    { "prepend", 0 },
#define	NPREPSW	3
    { "noprepend", 0 },	
#define	RAPDSW	4
    { "rapid", 0 },
#define	NRAPDSW	5
    { "norapid", 0 },
#define	BODYSW	6
    { "body", -4 },
#define	NBODYSW	7
    { "nobody", -6 },
#define	DOTSW	8
    { "doteof", 0 },
#define	NDOTSW	9
    { "nodoteof", 0 },
#define VERSIONSW 10
    { "version", 0 },
#define	HELPSW	11
    { "help", 0 },
    { NULL, 0 }
};


#ifdef HAVE_TERMIOS_H
static struct termios tio;
# define ERASE tio.c_cc[VERASE]
# define KILL  tio.c_cc[VKILL]
# define INTR  tio.c_cc[VINTR]
#else
# ifdef HAVE_TERMIO_H
static struct termio tio;
#  define ERASE tio.c_cc[VERASE]
#  define KILL  tio.c_cc[VKILL]
#  define INTR  tio.c_cc[VINTR]
# else
static struct sgttyb tio;
static struct tchars tc;
#  define ERASE tio.sg_erase
#  define KILL  tio.sg_kill
#  define INTR  tc.t_intrc
# endif
#endif

static int wtuser = 0;
static int sigint = 0;
static jmp_buf sigenv;

/*
 * prototypes
 */
int getln (char *, int);
static int chrcnv (char *);
static void chrdsp (char *, char);
static RETSIGTYPE intrser (int);


int
main (int argc, char **argv)
{
    int body = 1, prepend = 1, rapid = 0;
    int doteof = 0, fdi, fdo, i, state;
    char *cp, *drft = NULL, *erasep = NULL;
    char *killp = NULL, name[NAMESZ], field[BUFSIZ];
    char buffer[BUFSIZ], tmpfil[BUFSIZ];
    char **arguments, **argp;
    FILE *in, *out;

#ifdef LOCALE
    setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');

    /* read user profile/context */
    context_read();

    arguments = getarguments (invo_name, argc, argv, 1);
    argp = arguments;

    while ((cp = *argp++))
	if (*cp == '-') {
	    switch (smatch (++cp, switches)) {
		case AMBIGSW: 
		    ambigsw (cp, switches);
		    done (1);
		case UNKWNSW: 
		    adios (NULL, "-%s unknown", cp);

		case HELPSW: 
		    snprintf (buffer, sizeof(buffer), "%s [switches] file",
			invo_name);
		    print_help (buffer, switches, 1);
		    done (1);
		case VERSIONSW:
		    print_version(invo_name);
		    done (1);

		case ERASESW: 
		    if (!(erasep = *argp++) || *erasep == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;
		case KILLSW: 
		    if (!(killp = *argp++) || *killp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    continue;

		case PREPSW: 
		    prepend++;
		    continue;
		case NPREPSW: 
		    prepend = 0;
		    continue;

		case RAPDSW: 
		    rapid++;
		    continue;
		case NRAPDSW: 
		    rapid = 0;
		    continue;

		case BODYSW: 
		    body++;
		    continue;
		case NBODYSW: 
		    body = 0;
		    continue;

		case DOTSW: 
		    doteof++;
		    continue;
		case NDOTSW: 
		    doteof = 0;
		    continue;
	    }
	} else {
	    if (!drft)
		drft = cp;
	}

    if (!drft)
	adios (NULL, "usage: %s [switches] file", invo_name);
    if ((in = fopen (drft, "r")) == NULL)
	adios (drft, "unable to open");

    strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil));
    if ((out = fopen (tmpfil, "w")) == NULL)
	adios (tmpfil, "unable to create");
    chmod (tmpfil, 0600);

    /*
     * Are we changing the kill or erase character?
     */
    if (killp || erasep) {
#ifdef HAVE_TERMIOS_H
	cc_t save_erase, save_kill;
#else
	int save_erase, save_kill;
#endif

	/* get the current terminal attributes */
#ifdef HAVE_TERMIOS_H
	tcgetattr(0, &tio);
#else
# ifdef HAVE_TERMIO_H
	ioctl(0, TCGETA, &tio);
# else
	ioctl (0, TIOCGETP, (char *) &tio);
	ioctl (0, TIOCGETC, (char *) &tc);
# endif
#endif

	/* save original kill, erase character for later */
	save_kill = KILL;
	save_erase = ERASE;

	/* set new kill, erase character in terminal structure */
	KILL = killp ? chrcnv (killp) : save_kill;
	ERASE = erasep ? chrcnv (erasep) : save_erase;

	/* set the new terminal attributes */
#ifdef HAVE_TERMIOS_H
	 tcsetattr(0, TCSADRAIN, &tio);
#else
# ifdef HAVE_TERMIO_H
	ioctl(0, TCSETAW, &tio);
# else
	ioctl (0, TIOCSETN, (char *) &tio);
# endif
#endif

	/* print out new kill erase characters */
	chrdsp ("erase", ERASE);
	chrdsp (", kill", KILL);
	chrdsp (", intr", INTR);
	putchar ('\n');
	fflush (stdout);

	/*
	 * We set the kill and erase character back to original
	 * setup in terminal structure so we can easily
	 * restore it upon exit.
	 */
	KILL = save_kill;
	ERASE = save_erase;
    }

    sigint = 0;
    SIGNAL2 (SIGINT, intrser);

    /*
     * Loop through the lines of the draft skeleton.
     */
    for (state = FLD;;) {
	switch (state = m_getfld (state, name, field, sizeof(field), in)) {
	    case FLD: 
	    case FLDEOF: 
	    case FLDPLUS: 
		/*
		 * Check if the value of field contains anything
		 * other than space or tab.
		 */
		for (cp = field; *cp; cp++)
		    if (*cp != ' ' && *cp != '\t')
			break;

		/* If so, just add header line to draft */
		if (*cp++ != '\n' || *cp != 0) {
		    printf ("%s:%s", name, field);
		    fprintf (out, "%s:%s", name, field);
		    while (state == FLDPLUS) {
			state =
			    m_getfld (state, name, field, sizeof(field), in);
			printf ("%s", field);
			fprintf (out, "%s", field);
		    }
		} else {
		    /* Else, get value of header field */
		    printf ("%s: ", name);
		    fflush (stdout);
		    i = getln (field, sizeof(field));
		    if (i == -1) {
abort:
			if (killp || erasep) {
#ifdef HAVE_TERMIOS_H
			    tcsetattr(0, TCSADRAIN, &tio);
#else
# ifdef HAVE_TERMIO
			    ioctl (0, TCSETA, &tio);
# else
			    ioctl (0, TIOCSETN, (char *) &tio);
# endif
#endif
			}
			unlink (tmpfil);
			done (1);
		    }
		    if (i != 0 || (field[0] != '\n' && field[0] != 0)) {
			fprintf (out, "%s:", name);
			do {
			    if (field[0] != ' ' && field[0] != '\t')
				putc (' ', out);
			    fprintf (out, "%s", field);
			} while (i == 1
				    && (i = getln (field, sizeof(field))) >= 0);
			if (i == -1)
			    goto abort;
		    }
		}

		if (state == FLDEOF) {	/* moby hack */
		    fprintf (out, "--------\n");
		    printf ("--------\n");
		    if (!body)
			break;
		    goto no_body;
		}
		continue;

	    case BODY: 
	    case BODYEOF:
	    case FILEEOF: 
	        if (!body)
	            break;
		fprintf (out, "--------\n");
		if (field[0] == 0 || !prepend)
		    printf ("--------\n");
		if (field[0]) {
		    if (prepend && body) {
			printf ("\n--------Enter initial text\n\n");
			fflush (stdout);
			for (;;) {
			    getln (buffer, sizeof(buffer));
			    if (doteof && buffer[0] == '.' && buffer[1] == '\n')
				break;
			    if (buffer[0] == 0)
				break;
			    fprintf (out, "%s", buffer);
			}
		    }

		    do {
			fprintf (out, "%s", field);
			if (!rapid && !sigint)
			    printf ("%s", field);
		    } while (state == BODY &&
			    (state = m_getfld (state, name, field, sizeof(field), in)));
		    if (prepend || !body)
			break;
		    else
			printf ("\n--------Enter additional text\n\n");
		}
no_body:
		fflush (stdout);
		for (;;) {
		    getln (field, sizeof(field));
		    if (doteof && field[0] == '.' && field[1] == '\n')
			break;
		    if (field[0] == 0)
			break;
 		    fprintf (out, "%s", field);
		}
		break;

	    default: 
		adios (NULL, "skeleton is poorly formatted");
	}
	break;
    }

    if (body)
	printf ("--------\n");

    fflush (stdout);
    fclose (in);
    fclose (out);
    SIGNAL (SIGINT, SIG_IGN);

    if (killp || erasep) {
#ifdef HAVE_TERMIOS_H
	 tcsetattr(0, TCSADRAIN, &tio);
#else
# ifdef HAVE_TERMIO_H
	ioctl (0, TCSETAW, &tio);
# else
	ioctl (0, TIOCSETN, (char *) &tio);
# endif
#endif
    }

    if ((fdi = open (tmpfil, O_RDONLY)) == NOTOK)
	adios (tmpfil, "unable to re-open");
    if ((fdo = creat (drft, m_gmprot ())) == NOTOK)
	adios (drft, "unable to write");
    cpydata (fdi, fdo, tmpfil, drft);
    close (fdi);
    close (fdo);
    unlink (tmpfil);

    context_save ();	/* save the context file */
    return done (0);
}


int
getln (char *buffer, int n)
{
    int c;
    char *cp;

    cp = buffer;
    *cp = 0;

    switch (setjmp (sigenv)) {
	case OK: 
	    wtuser = 1;
	    break;

	case DONE: 
	    wtuser = 0;
	    return 0;

	default: 
	    wtuser = 0;
	    return NOTOK;
    }

    for (;;) {
	switch (c = getchar ()) {
	    case EOF: 
		clearerr (stdin);
		longjmp (sigenv, DONE);

	    case '\n': 
		if (cp[-1] == QUOTE) {
		    cp[-1] = c;
		    wtuser = 0;
		    return 1;
		}
		*cp++ = c;
		*cp = 0;
		wtuser = 0;
		return 0;

	    default: 
		if (cp < buffer + n)
		    *cp++ = c;
		*cp = 0;
	}
    }
}


static RETSIGTYPE
intrser (int i)
{
#ifndef	RELIABLE_SIGNALS
    SIGNAL (SIGINT, intrser);
#endif

    if (wtuser)
	longjmp (sigenv, NOTOK);
    sigint++;
}


static int
chrcnv (char *cp)
{
    return (*cp != QUOTE ? *cp : m_atoi (++cp));
}


static void
chrdsp (char *s, char c)
{
    printf ("%s ", s);
    if (c < ' ' || c == 0177)
	printf ("^%c", c ^ 0100);
    else
	printf ("%c", c);
}


syntax highlighted by Code2HTML, v. 0.9.1