#ifndef lint
static char *RCSid = "$Id: readline.c,v 3.26 92/03/24 22:34:35 woo Exp Locker: woo $";
#endif

/* GNUPLOT - readline.c */
/*
 * Copyright (C) 1986, 1987, 1990, 1991, 1992   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted, 
 * provided that the above copyright notice appear in all copies and 
 * that both that copyright notice and this permission notice appear 
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed 
 * as patches to released version.
 *  
 * This software is provided "as is" without express or implied warranty.
 * 
 *
 * AUTHORS
 *
 *   Original Software:
 *     Tom Tkacik
 *
 *   Msdos port and some enhancements:
 *     Gershon Elber and many others.
 * 
 * Send your comments or suggestions to 
 *  info-gnuplot@ames.arc.nasa.gov.
 * This is a mailing list; to join it send a note to 
 *  info-gnuplot-request@ames.arc.nasa.gov.  
 * Send bug reports to
 *  bug-gnuplot@ames.arc.nasa.gov.
 */

#ifdef READLINE

/* a small portable version of GNU's readline */
/* this is not the BASH or GNU EMACS version of READLINE due to Copyleft 
	restrictions */
/* do not need any terminal capabilities except backspace,
/* and space overwrites a character */

/* NANO-EMACS line editing facility */
/* printable characters print as themselves (insert not overwrite) */
/* ^A moves to the beginning of the line */
/* ^B moves back a single character */
/* ^E moves to the end of the line */
/* ^F moves forward a single character */
/* ^K kills from current position to the end of line */
/* ^P moves back through history */
/* ^N moves forward through history */
/* ^H and DEL delete the previous character */
/* ^D deletes the current character, or EOF if line is empty */
/* ^L/^R redraw line in case it gets trashed */
/* ^U kills the entire line */
/* ^W kills last word */
/* LF and CR return the entire line regardless of the cursor postition */
/* EOF with an empty line returns (char *)NULL */

/* all other characters are ignored */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>

/* SIGTSTP defines job control */
/* if there is job control then we need termios.h instead of termio.h */
#ifdef SIGTSTP
#define TERMIOS
#endif


#ifndef MSDOS

/* UNIX specific stuff */
#ifdef TERMIOS
#include <termios.h>
static struct termios orig_termio, rl_termio;
#else
#include <termio.h>
static struct termio orig_termio, rl_termio;
#endif /* TERMIOS */
static int term_set = 0;	/* =1 if rl_termio set */

#else

/* MSDOS specific stuff */
#define getc(stdin) msdos_getch()
static char msdos_getch();

#endif /* MSDOS */


/* is it <string.h> or <strings.h>?   just declare what we need */
extern int strlen();
extern char *strcpy();
extern char *malloc();

#define MAXBUF	1024
#define BACKSPACE 0x08	/* ^H */
#define SPACE	' '

struct hist {
	char *line;
	struct hist *prev;
	struct hist *next;
};

static struct hist *history = NULL;  /* no history yet */
static struct hist *cur_entry = NULL;

static char cur_line[MAXBUF];  /* current contents of the line */
static int cur_pos = 0;	/* current position of the cursor */
static int max_pos = 0;	/* maximum character position */


void add_history();
static void fix_line();
static void redraw_line();
static void clear_line();
static void clear_eoline();
static void copy_line();
static void set_termio();
static void reset_termio();

char *
readline(prompt)
char *prompt;
{

	char cur_char;
	char *new_line;

	/* set the termio so we can do our own input processing */
	set_termio();

	/* print the prompt */
	fputs(prompt, stderr);
	cur_line[0] = '\0';
	cur_pos = 0;
	max_pos = 0;
	cur_entry = NULL;

	/* get characters */
	for(;;) {
		cur_char = getc(stdin);
		if(isprint(cur_char)) {
			int i;
			for(i=max_pos; i>cur_pos; i--) {
				cur_line[i] = cur_line[i-1];
			}
			putc(cur_char, stderr);
			cur_line[cur_pos] = cur_char;
			cur_pos += 1;
			max_pos += 1;
			if (cur_pos < max_pos)
			    fix_line();
			cur_line[max_pos] = '\0';

		/* else interpret unix terminal driver characters */
#ifdef VERASE
		} else if(cur_char == orig_termio.c_cc[VERASE] ){  /* DEL? */
			if(cur_pos > 0) {
				int i;
				cur_pos -= 1;
				putc(BACKSPACE, stderr);
				for(i=cur_pos; i<max_pos; i++)
					cur_line[i] = cur_line[i+1];
				max_pos -= 1;
				fix_line();
			}
#endif /* VERASE */
#ifdef VEOF
		} else if(cur_char == orig_termio.c_cc[VEOF] ){   /* ^D? */
			if(max_pos == 0) {
				reset_termio();
				return((char *)NULL);
			}
			if((cur_pos < max_pos)&&(cur_char == 004)) { /* ^D */
				int i;
				for(i=cur_pos; i<max_pos; i++)
					cur_line[i] = cur_line[i+1];
				max_pos -= 1;
				fix_line();
			}
#endif /* VEOF */
#ifdef VKILL
		} else if(cur_char == orig_termio.c_cc[VKILL] ){  /* ^U? */
			clear_line(prompt);
#endif /* VKILL */
#ifdef VWERASE
		} else if(cur_char == orig_termio.c_cc[VWERASE] ){  /* ^W? */
			while((cur_pos > 0) &&
			      (cur_line[cur_pos-1] == SPACE)) {
				cur_pos -= 1;
				putc(BACKSPACE, stderr);
			}
			while((cur_pos > 0) &&
			      (cur_line[cur_pos-1] != SPACE)) {
				cur_pos -= 1;
				putc(BACKSPACE, stderr);
			}
			clear_eoline();
			max_pos = cur_pos;
#endif /* VWERASE */
#ifdef VREPRINT
		} else if(cur_char == orig_termio.c_cc[VREPRINT] ){  /* ^R? */
			putc('\n',stderr); /* go to a fresh line */
			redraw_line(prompt);
#else
#ifdef VRPRNT   /* on Ultrix VREPRINT is VRPRNT */
		} else if(cur_char == orig_termio.c_cc[VRPRNT] ){  /* ^R? */
			putc('\n',stderr); /* go to a fresh line */
			redraw_line(prompt);
#endif /* VRPRNT */
#endif /* VREPRINT */
#ifdef VSUSP
		} else if(cur_char == orig_termio.c_cc[VSUSP]) {
			reset_termio();
			kill(0, SIGTSTP);

			/* process stops here */

			set_termio();
			/* print the prompt */
			redraw_line(prompt);
#endif /* VSUSP */
		} else {
			/* do normal editing commands */
			/* some of these are also done above */
			int i;
			switch(cur_char) {
			    case EOF:
				reset_termio();
				return((char *)NULL);
			    case 001: /* ^A */
				while(cur_pos > 0) {
					cur_pos -= 1;
					putc(BACKSPACE, stderr);
				}
				break;
			    case 002: /* ^B */
				if(cur_pos > 0) {
					cur_pos -= 1;
					putc(BACKSPACE, stderr);
				}
				break;
			    case 005: /* ^E */
				while(cur_pos < max_pos) {
					putc(cur_line[cur_pos], stderr);
					cur_pos += 1;
				}
				break;
			    case 006: /* ^F */
				if(cur_pos < max_pos) {
					putc(cur_line[cur_pos], stderr);
					cur_pos += 1;
				}
				break;
			    case 013: /* ^K */
				clear_eoline();
				max_pos = cur_pos;
				break;
			    case 020: /* ^P */
				if(history != NULL) {
					if(cur_entry == NULL) {
						cur_entry = history;
						clear_line(prompt);
						copy_line(cur_entry->line);
					} else if(cur_entry->prev != NULL) {
						cur_entry = cur_entry->prev;
						clear_line(prompt);
						copy_line(cur_entry->line);
					}
				}
				break;
			    case 016: /* ^N */
				if(cur_entry != NULL) {
					cur_entry = cur_entry->next;
					clear_line(prompt);
					if(cur_entry != NULL) 
						copy_line(cur_entry->line);
					else
						cur_pos = max_pos = 0;
				}
				break;
			    case 014: /* ^L */
			    case 022: /* ^R */
				putc('\n',stderr); /* go to a fresh line */
				redraw_line(prompt);
				break;
			    case 0177: /* DEL */
			    case 010: /* ^H */
				if(cur_pos > 0) {
					cur_pos -= 1;
					putc(BACKSPACE, stderr);
					for(i=cur_pos; i<max_pos; i++)
						cur_line[i] = cur_line[i+1];
					max_pos -= 1;
					fix_line();
				}
				break;
			    case 004: /* ^D */
				if(max_pos == 0) {
					reset_termio();
					return((char *)NULL);
				}
				if(cur_pos < max_pos) {
					for(i=cur_pos; i<max_pos; i++)
						cur_line[i] = cur_line[i+1];
					max_pos -= 1;
					fix_line();
				}
				break;
			    case 025:  /* ^U */
				clear_line(prompt);
				break;
			    case 027:  /* ^W */
				while((cur_pos > 0) &&
				      (cur_line[cur_pos-1] == SPACE)) {
					cur_pos -= 1;
					putc(BACKSPACE, stderr);
				}
				while((cur_pos > 0) &&
				      (cur_line[cur_pos-1] != SPACE)) {
					cur_pos -= 1;
					putc(BACKSPACE, stderr);
				}
				clear_eoline();
				max_pos = cur_pos;
				break;
				break;
			    case '\n': /* ^J */
			    case '\r': /* ^M */
				cur_line[max_pos+1] = '\0';
				putc('\n', stderr);
				new_line = malloc(strlen(cur_line)+1);
				strcpy(new_line,cur_line);
				reset_termio();
				return(new_line);
			    default:
				break;
			}
		}
	}
}

/* fix up the line from cur_pos to max_pos */
/* do not need any terminal capabilities except backspace,
/* and space overwrites a character */
static void
fix_line()
{
	int i;

	/* write tail of string */
	for(i=cur_pos; i<max_pos; i++)
		putc(cur_line[i], stderr);

	/* write a space at the end of the line in case we deleted one */
	putc(SPACE, stderr);

	/* backup to original position */
	for(i=max_pos+1; i>cur_pos; i--)
		putc(BACKSPACE, stderr);

}

/* redraw the entire line, putting the cursor where it belongs */
static void
redraw_line(prompt)
char *prompt;
{
	int i;

	fputs(prompt, stderr);
	fputs(cur_line, stderr);

	/* put the cursor where it belongs */
	for(i=max_pos; i>cur_pos; i--)
		putc(BACKSPACE, stderr);
}

/* clear cur_line and the screen line */
static void
clear_line(prompt)
char *prompt;
{
	int i;
	for(i=0; i<max_pos; i++)
		cur_line[i] = '\0';

	for(i=cur_pos; i>0; i--)
		putc(BACKSPACE, stderr);

	for(i=0; i<max_pos; i++)
		putc(SPACE, stderr);

	putc('\r', stderr);
	fputs(prompt, stderr);

	cur_pos = 0;
	max_pos = 0;
}

/* clear to end of line and the screen end of line */
static void
clear_eoline(prompt)
char *prompt;
{
	int i;
	for(i=cur_pos; i<max_pos; i++)
		cur_line[i] = '\0';

	for(i=cur_pos; i<max_pos; i++)
		putc(SPACE, stderr);
	for(i=cur_pos; i<max_pos; i++)
		putc(BACKSPACE, stderr);
}

/* copy line to cur_line, draw it and set cur_pos and max_pos */
static void
copy_line(line)
char *line;
{
	strcpy(cur_line, line);
	fputs(cur_line, stderr);
	cur_pos = max_pos = strlen(cur_line);
}

/* add line to the history */
void
add_history(line)
char *line;
{
	struct hist *entry;
	entry = (struct hist *)malloc(sizeof(struct hist));
	entry->line = malloc((unsigned int)strlen(line)+1);
	strcpy(entry->line, line);

	entry->prev = history;
	entry->next = NULL;
	if(history != NULL) {
		history->next = entry;
	}
	history = entry;
}

#ifdef MSDOS

/* Convert Arrow keystrokes to Control characters: */
static  char
msdos_getch()
{
    char c = getch();

    if (c == 0) {
	c = getch(); /* Get the extended code. */
	switch (c) {
	    case 75: /* Left Arrow. */
		c = 002;
		break;
	    case 77: /* Right Arrow. */
		c = 006;
		break;
	    case 72: /* Up Arrow. */
		c = 020;
		break;
	    case 80: /* Down Arrow. */
		c = 016;
		break;
	    case 115: /* Ctl Left Arrow. */
	    case 71: /* Home */
		c = 001;
		break;
	    case 116: /* Ctl Right Arrow. */
	    case 79: /* End */
		c = 005;
		break;
	    case 83: /* Delete */
		c = 004;
		break;
	    default:
		c = 0;
		break;
	}
    }
    else if (c == 033) { /* ESC */
	c = 025;
    }


    return c;
}

#endif /* MSDOS */

/* set termio so we can do our own input processing */
static void
set_termio()
{
#ifndef MSDOS
	if(term_set == 0) {
#ifdef TERMIOS
#ifdef TCGETS
		ioctl(0, TCGETS, &orig_termio);
#else
		tcgetattr(0, &orig_termio);
#endif /* TCGETS */
#else
		ioctl(0, TCGETA, &orig_termio);
#endif /* TERMIOS */
		rl_termio = orig_termio;

		rl_termio.c_iflag &= ~(BRKINT|PARMRK|INPCK|IUCLC|IXON|IXOFF);
		rl_termio.c_iflag |=  (IGNBRK|IGNPAR);

		rl_termio.c_oflag &= ~(ONOCR);

		rl_termio.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|NOFLSH);
		rl_termio.c_lflag |=  (ISIG);

		rl_termio.c_cc[VMIN] = 1;
		rl_termio.c_cc[VTIME] = 0;

#ifdef VSUSP
		/* disable suspending process on ^Z */
		rl_termio.c_cc[VSUSP] = 0;
#endif /* VSUSP */

#ifdef TERMIOS
#ifdef TCSETSW
		ioctl(0, TCSETSW, &rl_termio);
#else
		tcsetattr(0, TCSADRAIN, &rl_termio);
#endif /* TCSETSW */
#else
		ioctl(0, TCSETAW, &rl_termio);
#endif /* TERMIOS */
		term_set = 1;
	}
#endif /* MSDOS */
}

static void
reset_termio()
{
#ifndef MSDOS
	if(term_set == 1) {
#ifdef TERMIOS
#ifdef TCSETSW
		ioctl(0, TCSETSW, &orig_termio);
#else
		tcsetattr(0, TCSADRAIN, &orig_termio);
#endif /* TCSETSW */
#else
		ioctl(0, TCSETAW, &orig_termio);
#endif /* TERMIOS */
		term_set = 0;
	}
#endif /* MSDOS */
}
#endif /* READLINE */
