/*
Filename:	readline.c
Author:		Martin "MAD" Dorey
Email:		mad-a@minster.york.ac.uk

Summary:
		line editing
		to provide a cursor key history list for the rc shell (perhaps)
		feeped to perform filename completion and expansion (specifically for rc)
*/

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#define COMPLETION

#include <sys/types.h>
#include <pwd.h>


/* POSIX doesn't allow SIGWINCH to be defined etc etc etc */
/* going temporarily all-SVR4y works with IRIX5 */
#ifndef AIX
#undef _POSIX_SOURCE
#define _SVR4_SOURCE
#endif
#include <signal.h>
#undef _POSIX_SOURCE
#define _POSIX_SOURCE
#undef _SVR4_SOURCE

#include <sys/stat.h>
#include "posix.h"
#include "readline.h"

#include <curses.h>

#ifndef TERMCAP
#include <term.h>
#endif

#ifdef COMPLETION

/* sections from rc.h */

/* gracefully degradating memory allocation */

extern void *ealloc (size_t);
extern void efree (void *);
extern void *erealloc (void *, size_t);

/* single command duration allocator - no memory leak worries */
extern void *nalloc (size_t);

#define nnew(x) ((x *) nalloc(sizeof(x)))

/* exception handler */
extern void sigint (int s);

/* these definitions MUST stay the same */
/* they're used to test and set binary array values */

#undef FALSE
#define FALSE (0)
#undef TRUE
#define TRUE (1)
#undef bool
#define bool int

typedef struct List List;

struct List {
	char *w, *m;
	List *n;
};

extern List *glob (List *, bool);
extern List *word (char *, char *);
extern List *listcpy (List *, void *(*)(size_t));
extern List *varlookup (char *);
extern List *flatten (List *);
extern List *concat (List *, List *);

/* meta character tables */
extern const char dnw [];
extern const char non_filename_chars [];

char *demeta (char *s);

typedef struct last_tab_t last_tab_t;

struct last_tab_t {
	int start;
	char *original;
	last_tab_t *previous;
};

#include "key.h"

#endif

#define rl_linefeed 10
#define rl_carriage_return 13


#define terminal_insane(tio_p) ((((tio_p)->c_oflag & ONLCR) != (tio_old.c_oflag & ONLCR) || ((tio_p)->c_iflag & ICRNL) != (tio_old.c_iflag & ICRNL)) ? TRUE : FALSE)

int initialised = 0;


#define buf_block 64

struct termios tio, tio_old;

typedef enum {ascii, escape, sequence, number, lnext} control_t;
control_t control;
typedef enum {insert_mode, overwrite_mode} imode_t;
imode_t imode = insert_mode;

typedef struct history_t {
	char *text;
	struct history_t *next, *previous;
} history_t;

history_t *history = NULL;

struct sigaction sig, osig;


#define min(a,b) ((a) < (b) ? (a) : (b))
#define streq(x, y) (strlen (x) == strlen (y) && strcmp ((x), (y)) == 0)	/* from rc.h */
static int charsize (int pos);
static void rl_writechar (int c);
static void delete_history (history_t *lhistory);
static void getwinsize (void);
static void handle_winch (int s);

int rc_putchar (int c);
int tccols;
char *tcleft = "\b", *tcright = " ", *tcup = "", *tcdown = "\n", *tccleartoenddisplay = "", *tcdelchar = "\b", *tcmultleft = "", *tcmultright = "";
char *tcboldon = "", *tcboldoff = "", *tcunderon = "", *tcunderoff = "", *tcstandon = "", *tcstandoff = "", *tcreverseon = "", *tcreverseoff = "";
int tcauto_left = 0, tcauto_right = 0;
int hpos, vpos;
int shpos, svpos;

#include <stdio.h>

static void
writechar_ctrl (char *ctrl)
{
	int loop;
	if (ctrl == tcleft) {
		hpos --;
		if (hpos < 0) {
			hpos = tccols - 1;
			vpos --;
			tputs (tcup, 1, rc_putchar);
			if (*tcmultright) {
				tputs (tgoto (tcmultright, tccols, tccols), 1, rc_putchar);
			} else {
				for (loop = 0; loop < tccols; loop ++) {
					tputs (tcright, 1, rc_putchar);
				}
			}
		} else {
			tputs (tcleft, 1, rc_putchar);
		}
	} else if (ctrl == tcright) {
		hpos ++;
		if (hpos == tccols) {
			hpos = 0;
			vpos ++;
			tputs (tcdown, 1, rc_putchar);
			tputs (tgoto (tcmultleft, tccols, tccols), 1, rc_putchar);
		} else {
			tputs (tcright, 1, rc_putchar);
		}
	} else {
		tputs (ctrl, 1, rc_putchar);
	}
}

static void
writechar (char c)
{
	int loop;
	if (c == '\b') {
		hpos --;
		if (hpos < 0) {
			hpos = tccols - 1;
			vpos --;
			if (tcauto_left) {
				tputs (tcleft, 1, rc_putchar);
			} else {
				tputs (tcup, 1, rc_putchar);
				if (*tcmultright) {
					tputs (tgoto (tcmultright, tccols, tccols), 1, rc_putchar);
				} else {
					for (loop = 0; loop < tccols; loop ++) {
						tputs (tcright, 1, rc_putchar);
					}
				}
			}
		} else {
			tputs (tcleft, 1, rc_putchar);
		}
	} else {
		write (1, &c, 1);
		hpos ++;
		vpos ++;
		if (hpos == tccols) {
			hpos = 0;
			if (!tcauto_right) {
				tputs (tcdown, 1, rc_putchar);
				tputs (tgoto (tcmultleft, tccols, tccols), 1, rc_putchar);
			}
		}
	}
}


#define colours 8
char *colour_a [colours] = {"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"};

static int
get_colour (char **s_p)
{
	char *s = ++ (*s_p);
	int colour;

	while (*(++ s) != '\0') {
		for (colour = 0; colour < colours; colour ++) {
			if (strncmp (*s_p, colour_a [colour], s - *s_p + 1) == 0 && s - *s_p + 1 == strlen (colour_a [colour])) {
				*s_p = s;
				return colour;
			}
		}
	}
	return -1;
}

extern int
capescape (char **s_p)
{
	char *start, *s = *s_p;
	int colour;
	static char *colour_code = NULL;
	if (colour_code == NULL) colour_code = ealloc (7);
	switch (*s) {
	case '%':
		write (1, "%", 1);
		break;
	case 'B':
		tputs (tcboldon, 1, rc_putchar);
		break;
	case 'b':
		tputs (tcboldoff, 1, rc_putchar);
		break;
	case 'R':
		tputs (tcreverseon, 1, rc_putchar);
		break;
	case 'r':
		tputs (tcreverseoff, 1, rc_putchar);
		break;
	case 'S':
		tputs (tcstandon, 1, rc_putchar);
		break;
	case 's':
		tputs (tcstandoff, 1, rc_putchar);
		break;
	case 'U':
		tputs (tcunderon, 1, rc_putchar);
		break;
	case 'u':
		tputs (tcunderoff, 1, rc_putchar);
		break;
	case 'C':
		colour = get_colour (s_p);
		if (colour >= 0) {
			sprintf (colour_code, "\033[%dm", 30 + colour);
			write (1, colour_code, strlen (colour_code));
		}
		break;
	case 'c':
		colour = get_colour (s_p);
		if (colour >= 0) {
			sprintf (colour_code, "\033[%dm", 40 + colour);
			write (1, colour_code, strlen (colour_code));
		}
		break;
	case '{':
		s ++;
		start = s;
		while (*s && !(*(s-1) == '%' && *s == '}')) s ++;
		write (1, start, s - start - 1);
		if (!*s) s --;
		*s_p = s;
		break;
	default:
		return FALSE;
	}
	return TRUE;
}


static void
writestr (char *s)
{
	bool escape = FALSE;
	while (*s) {
		if (escape) {
			escape = FALSE;
			if (!capescape (&s)) {
				writechar ('%');
				writechar (*s);
			}
		} else {
			if (*s == '%') {
				escape = TRUE;
			} else {
				writechar (*s);
			}
		}
		s ++;
	}
}


/*
char writec;
#define writechar(c) {\
	writec = c;\
	write (1, &writec, 1);}
*/


/*
#define writechar(c) {\
	int nbytes;\
	writec = c;\
	do {\
		nbytes = write (1, &writec, 1);\
	} while (nbytes == -1 && errno == EINTR);}
*/

#define BACK_MOVE writechar_ctrl (tcleft)

#define FORE_MOVE writechar_ctrl (tcright)

#define CLEAR_TO_END writechar_ctrl (tccleartoenddisplay)

#define rl_move(start, finish) \
	{\
		int loop = start, loop2;\
		for (; loop < finish; loop ++) {\
			for (loop2 = 0; loop2 < charsize (loop); loop2 ++) {\
				BACK_MOVE;\
			}\
		}\
	}

#define KILL \
	rl_move (0, pos);\
	CLEAR_TO_END;
	
	
#define GET_HISTORY \
	buf = strdup (history->text);\
	if (pos == ptr) {\
		ptr = strlen (buf);\
		pos = ptr;\
	} else {\
		ptr = strlen (buf);\
		pos = min (pos, ptr);\
	}\
	buf = erealloc (buf, ((ptr + 1 + buf_block - 1) / buf_block) * buf_block);\
	for (loop = 0; loop < ptr; loop ++) {\
		rl_writechar (buf [loop]);\
	}\
	rl_move (pos, ptr)


char *buf = NULL;


extern char *
readline (char *prompt)
{
	bool used_c, in_quotes;
	int nbytes, seq_number = 0;
	unsigned char c;
	int ptr = 0, pos = 0, loop, len, newpos, history_match_size = 0, history_match_counter = 0;
	char *history_match = NULL;
	history_t *scan_history;

	int posix_vdisable; /* enh 1996/02/29 - the value for c_cc that disables a special character. */

	#ifdef COMPLETION
		List *complete_start = NULL, *completing = NULL;
		char *scan_str, *real_str, *meta_str, *varname, *start_scan_demeta;
		int start_scan_meta = 0, seq_quotes, dollar = -1;
		bool quotes, saw_meta, flat = FALSE;
		struct stat stat_buf;
		last_tab_t *last_tab = NULL, *new_tab;
	#endif

	tcgetattr (0, &tio);
	
	/* if terminal insane, use old tio */
	if (initialised != 0 && terminal_insane (&tio)) {
		tio = tio_old;
	}

	/* terminal sanity enforcement */
	#if defined (LDISC1) && defined (USE_TERMIO)
		if (tio.c_line != LDISC1) {
			tio.c_line = LDISC1;
			tcsetattr (0, TCSANOW, &tio);
			tcgetattr (0, &tio);
		}
	#endif

	/* enh 1996/02/29 (evening, not morning :-) - added this
	** more POSIX code for determining what character we should use
	** to disable special characters. Previously the value 0 was
	** used. Just so happens Solaris reserves that to mean <undefined>,
	** but I heard somebody doesn't think all the world's a Sun. */

	#if !defined(_POSIX_VDISABLE) || (_POSIX_VDISABLE == -1)
		/* They've defined it for us. */
		posix_vdisable = _POSIX_VDISABLE;
	#else
		/* See if fpathconf can help. */
		posix_vdisable = fpathconf ( STDIN_FILENO, _PC_VDISABLE );
		if ( posix_vdisable == -1 ) {
			/* It can't, so we'll resort to a guess. */
			posix_vdisable = 0;
		}
	#endif

	/* enh 1996/02/28 - messed these around. The SWTCH stuff interferes
	** with SUSP on Solaris.
	** As I understand it, SWTCH isn't POSIX, and should never be used
	** in preference to SUSP. Moreover, on Solaris, it helps to disable
	** SWTCH, because it has the same default code as SUSP.
	** I'm wary of this because I fear it may be a product of having
	** to define __EXTENSIONS__. Maybe that drags too much in? */

	#ifdef CSUSP
		if (tio.c_cc [VSUSP] == posix_vdisable ) {
			tio.c_cc [VSUSP] = CSUSP;
			#ifdef VSWTCH
			tio.c_cc [VSWTCH] = posix_vdisable;
			#endif
		}
	#elif defined CSWTCH
		if (tio.c_cc [VSWTCH] == posix_vdisable) tio.c_cc [VSWTCH] = CSWTCH;
	#endif

	tio.c_oflag |= OPOST;
#ifndef CEOF
	#define CEOF 4
#endif
	if ((tio.c_lflag & ICANON) == 0) tio.c_cc [VEOF] = CEOF;
	tio_old = tio;
	tio_old.c_lflag |= ICANON | ISIG;

	/* non canonical mode, handle echoing myself, prevent INTR, SWTCH and KILL from generating signals (handle by key code) */
	/* terminal setup ripped off zsh by Paul Falstad */
	
	tio.c_lflag &= ~ (ICANON | ECHO | ISIG
	#ifdef FLUSHO
		| FLUSHO
	#endif
		);
	tio.c_cc [VQUIT] =
	#ifdef VDISCARD
		tio.c_cc [VDISCARD] = 
	#endif
	#ifdef VSUSP
		tio.c_cc [VSUSP] =
	#endif
	#ifdef VDSUSP
		tio.c_cc [VDSUSP] =
	#endif
	#ifdef VSWTCH
		tio.c_cc [VSWTCH] =
	#endif
		posix_vdisable;
	tio.c_cc [VMIN] = 1;
	tio.c_cc [VTIME] = 0;
	
	tcsetattr (0, TCSADRAIN, &tio);
	tcdrain (0);

	/* window width handling */

	/* size change handler */
	sig.sa_handler = handle_winch;
	sigemptyset (&sig.sa_mask);
	sig.sa_flags = 0;
	sigaction (SIGWINCH, &sig, &osig);

	getwinsize ();
	
	hpos = 0;
	vpos = 0;
	writestr (prompt);	/* must be before stty or con can hang, however con will sometimes ignore the -echo - lesser of two evils */
	shpos = hpos;
	svpos = vpos;

	#define END_READLINE_TERM \
		tcsetattr (0, TCSADRAIN, &tio_old);\
		tcdrain (0);\
		sigaction (SIGWINCH, &osig, NULL);
	
	#define END_READLINE \
		END_READLINE_TERM;\
		{\
			int loop, loopend = 1 + (hpos + ptr - pos - 2) / tccols;\
			for (loop = 0; loop < loopend; loop ++) {\
				writechar ('\n');\
			}\
		}\
		return buf
	
	buf = NULL;
	add_history ("");
	
	control = ascii;
	
#define readchar \
	do {\
		nbytes = read (0, &c, 1);\
	} while (nbytes == -1 && errno == EINTR);\

	/* enh 1996/02/28 - removed the following line as being offensive.
	** The woman I loved spoke top-bit set characters. */
	/*c = c & 0x7f*/
	/* There's a slight problem with compose character though. rc thinks
	** that two keys have been pressed. In reality I've pressed three, and
	** in my dreams rc would recognise only one. I'll come back and fix
	** this. Next time Antonietta wants to use my Sun... */

	readchar;

	while (nbytes > 0 && (control == lnext || c != tio_old.c_cc [VEOF]) && c != rl_linefeed && c != rl_carriage_return) {
		used_c = FALSE;
		if (control == ascii) {
			used_c = TRUE;
			if (c == tio.c_cc [VLNEXT]) {
				control = lnext;
			} else if (c == 27) {
				control = escape;
			} else if (c == tio.c_cc [VINTR]) {
				KILL;
				END_READLINE_TERM;
				sigint (SIGINT);
			} else if (c == tio.c_cc [VKILL]) {
				KILL;
				if (buf) {
					efree (buf);
					buf = NULL;
				}
				ptr = pos = 0;
			} else if (c == tio.c_cc [VERASE]) {
				if (pos) {
					len = charsize (pos - 1);
					for (loop = 0; loop < len; loop ++) {
						BACK_MOVE;
					}
					for (loop = pos - 1; loop < ptr - 1; loop ++) {
						buf [loop] = buf [loop + 1];
						rl_writechar (buf [loop]);
					}
					for (loop = 0; loop < len; loop ++) {
						CLEAR_TO_END;
					}
					rl_move (pos - 1, ptr - 1);
					ptr --;
					pos --;
				}
			} else if (c == key [rc_key_backspace] || c == key [rc_key_delete]) {
				if (pos < ptr) {
					len = charsize (pos);
					for (loop = pos; loop < ptr - 1; loop ++) {
						buf [loop] = buf [loop + 1];
						rl_writechar (buf [loop]);
					}
					for (loop = 0; loop < len; loop ++) {
						CLEAR_TO_END;
					}
					rl_move (pos, ptr - 1);
					ptr --;
				}
			} else if (c == key [rc_key_insovr]) {

#define INSOVR \
	if (imode == insert_mode) {\
		imode = overwrite_mode;\
	} else {\
		imode = insert_mode;\
	}

				INSOVR;
			} else if (c == key [rc_key_beep]) {
				writechar (key [rc_key_beep]);

#define CTRL_RIGHT \
	while (pos < ptr) {\
		for (loop = 0; loop < charsize (pos); loop ++) {\
			FORE_MOVE;\
		}\
		pos ++;\
	}

#define CTRL_LEFT \
	while (pos) {\
		for (loop = 0; loop < charsize (pos - 1); loop ++) {\
			BACK_MOVE;\
		}\
		pos --;\
	}

			} else if (c == key [rc_key_cleft]) {
				CTRL_LEFT;
			} else if (c == key [rc_key_cright]) {
				CTRL_RIGHT;
			} else if (c == tio_old.c_cc [VSUSP]) {
				/* do nothing */
		#ifdef COMPLETION
			} else if (c == key [rc_key_tab]) {
				if (!complete_start) {
					/* on first (sequential) tab, find string to complete/expand, resolve meta characters and lookup variables into a literal, then glob */
					scan_str = buf + pos;
					quotes = FALSE;
					/* find last unquoted space, semicolon or beginning of line */
					if (scan_str) while (-- scan_str >= buf && !(quotes == FALSE && (*scan_str == ' ' || *scan_str == ';'))) if (*scan_str == '\'') quotes = quotes == FALSE ? TRUE : FALSE;
					/* if quoted beginning of line (ie unmatched quote) then barf */
					if (quotes == TRUE) {
						writechar (key [rc_key_beep]);
					} else {
						completing = NULL; /* completing is used to assemble the string (list) to be matched, later it is used to hold the current position within the list of matched strings */
						saw_meta = FALSE;
						loop = 0;
						new_tab = nnew (last_tab_t);
						new_tab->previous = last_tab;
						if (scan_str) {
							if (scan_str < buf || *scan_str == ' ') scan_str ++;
							len = (buf + pos) - scan_str;
							start_scan_meta = scan_str - buf;
							new_tab->original = nalloc (len + 1);
							strncpy (new_tab->original, scan_str, len);
							*(new_tab->original + len) = '\0';
							/* find unquoted wildcards and resolve quotes */
							quotes = FALSE;
							seq_quotes = 0;
							real_str = nalloc (len + 2); /* extra character for possible * */
							meta_str = nalloc (len + 2);


#define LOOKUP {\
	List *lookup = NULL;\
	if (*(real_str + dollar) == '~') {\
		{\
			struct passwd *pwent;\
			char *start, *end, *dir, *whole;\
			char tpos;\
/* enh			start = scan_str;\*/\
			start = real_str+dollar+1;\
			tpos = buf [pos];\
			buf [pos] = '\0';\
			end = strchr (start, '/');\
			if (end == start || *start == '\0' ) {\
				dir = (varlookup ("home"))->w;\
			} else {\
				if (end != NULL) *end = '\0';\
				else *(start+loop-1) = '\0';\
/*fprintf( stderr, "\ntrying `%s'\n",start);*/\
				pwent = getpwnam (start);\
/*				if (end != NULL) *end = '/';\*/ \
				if (pwent == NULL) {\
/*fprintf( stderr, "\nnull pwent for `%s'\n",start);*/\
					dir = NULL;\
				} else {\
					dir = pwent->pw_dir;\
				}\
			}\
			if (dir != NULL) {\
/*fprintf(stderr,"\ndir=`%s'\n",dir);*/\
				if (end == NULL) {\
					whole = nalloc (strlen (dir) + 1);\
					strcpy (whole, dir);\
				} else {\
					whole = nalloc (strlen (dir) + strlen (end) + 1);\
					strcpy (whole, dir);\
					/*if ( strlen(whole)>1 && whole[strlen(whole)-1] != '/' ) strcat (whole,"/");*/\
/*fprintf(stderr,"\nwhole=`%s'\n",whole);*/\
					{char *elizabeth=end;do{elizabeth++;}while(*elizabeth&&*elizabeth=='/');\
					strcat (whole, elizabeth);}\
				}\
/*fprintf( stderr, "\nword=`%s'\n", whole);*/\
				lookup = word (whole, NULL);\
			} else {\
				dollar = -1;\
			}\
			buf [pos] = tpos;\
		}\
	} else {\
		/* get variable name */\
		varname = nalloc (loop - dollar);\
		strncpy (varname, real_str + dollar + 1, loop - dollar - 1);\
		*(varname + loop - dollar - 1) = '\0';\
		/* lookup variable */\
		lookup = varlookup (varname);\
		if (flat && lookup) lookup = flatten (lookup);\
	}\
	if (dollar > -1) {\
		*(real_str + dollar) = '\0';\
		*(meta_str + dollar) = '\0';\
/*fprintf(stderr,"\nreal=`%s',meta=`%s'\n",real_str,meta_str);*/\
		completing = concat (completing, word (real_str, meta_str));\
		if (lookup) completing = concat (completing, lookup);\
		loop = 0;\
		dollar = -1;\
	}\
	flat = FALSE;\
}


							/* rc itself does not reparse lookedup variables, which makes this considerably easier */
							for (; scan_str < buf + pos; scan_str ++, loop ++) {
								*(real_str + loop) = *scan_str;
								*(meta_str + loop) = FALSE;
								/* if seen start of variable name and found invalid character in variable name, then substitute variable literally */
								if (dollar > -1 && dnw [(unsigned char) *scan_str] != FALSE) {
									if (*scan_str == '^' && loop == dollar + 1) {
										dollar ++;
										flat = TRUE;
									} else {
										LOOKUP;
									}
								}
								if (*scan_str == '^') scan_str ++;
								if (!(scan_str < buf + pos)) {
									loop --;
									saw_meta = TRUE;	/* prevent appending of * if matching string ends in ^ */
								} else {
									if (*scan_str == '\'') {
										quotes = quotes == FALSE ? TRUE : FALSE;
										seq_quotes ++;
										/* display a quote if weren't in quotes and this is the second (quote in quotes) or third (quote at end of quotes) consecutive quote (modulo three) */
										if ((quotes == TRUE && seq_quotes == 1) || quotes == FALSE) {
											loop --;
										} else {
											*(real_str + loop) = '\'';
											*(meta_str + loop) = FALSE;
										}
										if (seq_quotes == 3) seq_quotes = 1;
									} else {
										seq_quotes = 0;
										*(real_str + loop) = *scan_str;
										if (quotes == TRUE) {
											*(meta_str + loop) = FALSE;
										} else {
											switch (*scan_str) {
											case '?':
											case '[':
											case '*':
												*(meta_str + loop) = TRUE;
												saw_meta = TRUE;
												break;
											case '$':
											case '~':
												dollar = loop;
												*(meta_str + loop) = FALSE;
												break;
											default:
												*(meta_str + loop) = FALSE;
											}
										}
									}
								}
							}
							if (dollar > -1) LOOKUP;
						} else {
							start_scan_meta = pos;
							new_tab->original = nalloc (1);
							*(new_tab->original) = '\0';
							real_str = nalloc (2);
							meta_str = nalloc (2);
						}

/* enh - it's around this area that you'll fix the ~root<tab> bug. this is so very very nasty... */

						/* if no wildcards found, add * to complete, rather than expand */
						if (!saw_meta) {
							*(real_str + loop) = '*';
							*(meta_str + loop) = TRUE;
							loop ++;
						}
						*(real_str + loop) = '\0';
						*(meta_str + loop) = FALSE;
						completing = concat (completing, word (real_str, meta_str));

						/* glob */
						complete_start = glob (completing, TRUE);

						/* test for failure to complete/expand for each element - remove elements unmatched */
						/* if matching succeeded then meta string will be NULL (not just empty) */
						if (complete_start) {
							List *s = complete_start, *old_s = NULL;
							while (s) {
								if (s->m) {
									if (old_s) {
										old_s->n = s->n;
									} else {
										complete_start = s->n;
									}
								} else {
									old_s = s;
									/* also append slashes to directory names */
									stat (s->w, &stat_buf);
#ifndef S_ISDIR
									if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) {
#else
									if (S_ISDIR (stat_buf.st_mode)) {
#endif
										s->w = strcpy (nalloc (strlen (s->w) + 2), s->w);
										strcat (s->w, "/");
									}
								}
								s = s->n;
							}
						}

#define PUT_COMPLETE(first_call) \
	/* move from current position to beginning of string to complete, blank to end of line and move back to beginning of string to complete */\
	rl_move (start_scan_meta, pos);\
	CLEAR_TO_END;\
	/* allocate space for new line buffer */\
	len = start_scan_meta + strlen (start_scan_demeta);\
	if (first_call) {\
		in_quotes = FALSE;\
		for (newpos = 0; newpos < ptr; newpos ++) {\
			if (!in_quotes && newpos >= pos && (buf [newpos] == ' ' || buf [newpos] == ';')) break;\
			if (buf [newpos] == '\'') in_quotes = !in_quotes;\
		}\
		if (newpos > pos) {\
			char *original = nalloc (strlen (last_tab->original) + newpos - pos + 1);\
			strcpy (original, last_tab->original);\
			last_tab->original = original;\
			strncat (last_tab->original, buf + pos, newpos - pos);\
		}\
	} else {\
		newpos = pos;\
	}\
	/* enough (or, if newpos > pos, more than enough) space */\
	buf = erealloc (buf, ((len + ptr - pos + 1 + buf_block - 1) / buf_block) * buf_block);\
	/* preserve trailing characters - copy from after current cursor position to new position */\
	/* order of overlapping copy depends on whether moving up or down */\
	if (len > newpos) {\
		for (loop = len + ptr - newpos - 1; loop >= newpos; loop --) buf [loop] = buf [loop - (len - newpos)];\
	} else {\
		for (loop = len; loop < len + ptr - newpos; loop ++) buf [loop] = buf [loop - (len - newpos)];\
	}\
	/* update current position and length pointers */\
	ptr += len - newpos;\
	pos = len;\
	/* copy matched string into buffer */\
	strncpy (buf + start_scan_meta, start_scan_demeta, strlen (start_scan_demeta));\
	/* update display */\
	for (loop = start_scan_meta; loop < ptr; loop ++) {\
		rl_writechar (buf [loop]);\
	}\
	rl_move (pos, ptr)
					

						if (complete_start) {
							new_tab->start = start_scan_meta;
							last_tab = new_tab;
							completing = complete_start;
							/* quote shell metacharacters returned in literal filename */
							start_scan_demeta = demeta (completing->w);
							PUT_COMPLETE (TRUE);
							if (completing->n) {
								completing = completing->n;
							} else {
								complete_start = NULL;
							}
						} else {
							/* if no match on globbing then barf */
							writechar (key [rc_key_beep]);
						}
					}
				} else {
					/* display next match in list */
					start_scan_demeta = demeta (completing->w);
					PUT_COMPLETE (FALSE);
					completing = completing->n;
					if (!completing) completing = complete_start;
				}
			} else if (c == key [rc_key_untab]) {

#define UNTAB \
	if (last_tab) {\
		start_scan_meta = last_tab->start;\
		start_scan_demeta = last_tab->original;\
		PUT_COMPLETE (FALSE);\
		complete_start = NULL;\
		last_tab = last_tab->previous;\
	}
				UNTAB;
		#endif
			} else {
				used_c = FALSE;
			}
		}
		if (!used_c) {
		switch (control) {
		case ascii:
		case lnext:
			if (control == lnext) control = ascii;
			if (imode == insert_mode || pos == ptr) {
				if (ptr % buf_block == 0) {
					buf = erealloc (buf, ptr + buf_block);
				}
				for (loop = ptr - 1; loop >= pos; loop --) {
					buf [loop + 1] = buf [loop];
				}
				buf [pos ++] = c;
				ptr ++;
				rl_writechar (c);
				for (loop = pos; loop < ptr; loop ++) {
					rl_writechar (buf [loop]);
				}
				rl_move (pos, ptr);
			} else {
				buf [pos ++] = c;
				rl_writechar (c);
			}
			break;
		case escape:
			if (c == '[' || c == 'O') {
				/* ^[O[ABCD] are application cursors */
				control = sequence;
				seq_number = 0;
			} else {
				control = ascii;
			}
			break;


#define SCAN_HISTORY(dir, notdir) \
	/* if can move in the first direction, do so, else go to other end */\
	if (scan_history->dir) {\
		scan_history = scan_history->dir;\
	} else if (scan_history->notdir) {\
		while (scan_history->notdir) scan_history = scan_history->notdir;\
	}

		case sequence:
		case number:
			switch (c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				control = number;
				seq_number = seq_number * 10 + c - '0';
				break;
			case 'q':
				switch (seq_number) {
				case 139:
					INSOVR;
					break;
				case 158:	/* shift-left */
					if (pos) {
						do {
							for (loop = 0; loop < charsize (pos - 1); loop ++) {
								BACK_MOVE;
							}
							pos --;
						} while (pos && isalnum (buf [pos]));
					}
					break;
				case 167:	/* shift-right */
					if (pos < ptr) {
						do {
							for (loop = 0; loop < charsize (pos); loop ++) {
								FORE_MOVE;
							}
							pos ++;
						} while (pos < ptr && isalnum (buf [pos]));
					}
					break;
				case 159:	/* ctrl-left */
					CTRL_LEFT;
					break;
				case 168:	/* ctrl right */
					CTRL_RIGHT;
					break;
				}
				control = ascii;
				break;
			case 'A':	/* cursor up */
			case 'B':	/* cursor down */
				scan_history = history;
				if (history_match == NULL) {
					history_match_size = pos;
					history_match = nalloc (history_match_size + 1);
					strncpy (history_match, buf, history_match_size);
					*(history_match + history_match_size) = '\0';
				}
				if (history_match_size) {
					do {
						if (c == 'A') {
							SCAN_HISTORY (previous, next);
						} else {
							SCAN_HISTORY (next, previous);
						}
						history_match_counter = strncmp (scan_history->text, history_match, history_match_size) == 0 && strlen (scan_history->text) >= history_match_size;
					} while (!history_match_counter && scan_history != history);
				} else {
					if (c == 'A') {
						SCAN_HISTORY (previous, next);
					} else {
						SCAN_HISTORY (next, previous);
					}
					history_match_counter = !0;
				}
				if (history_match_counter) {
					KILL;
					if (buf) efree (buf);
					history = scan_history;
					GET_HISTORY;
				}
				history_match_counter = 3;	/* escape, left bracket, A | B - if the next key isn't a cursor up/down then previous matching buffer discarded */
				control = ascii;
				break;
			case 'C':	/* cursor right */
				if (pos < ptr) {
					for (loop = 0; loop < charsize (pos); loop ++) {
						FORE_MOVE;
					}
					pos ++;
				}
				control = ascii;
				break;
			case 'D':	/* cursor left */
				if (pos) {
					for (loop = 0; loop < charsize (pos - 1); loop ++) {
						BACK_MOVE;
					}
					pos --;
				}
				control = ascii;
				break;
		#ifdef COMPLETION
			case 'Z':
				UNTAB;
				control = ascii;
				break;
		#endif
			default:
				control = ascii;
			}
		}
		}

		readchar;

		if (nbytes > 0) {

			#ifdef COMPLETION
				if (last_tab && c != key [rc_key_tab] && c != key [rc_key_untab] && !((control == ascii && c == 27) || (control == escape && c == '[') || (control == sequence && c == 'Z'))) {
					complete_start = NULL;
					last_tab = NULL;
				}
			#endif

			if (history_match) {
				if (history_match_counter -- == 0) {
					history_match = NULL;
				}
			}

		}
	}

	if ((nbytes == 0 || (c != rl_linefeed && c != rl_carriage_return)) && buf != NULL && ptr == 0) {
		efree (buf);
		buf = NULL;
	}
	
	if (buf || (nbytes > 0 && (c == rl_linefeed || c == rl_carriage_return))) {
		if (ptr % buf_block == 0) {
			buf = erealloc (buf, ptr + buf_block);
		}
		buf [ptr ++] = '\0';
	}

	END_READLINE;
}


#ifdef COMPLETION


char *
demeta (char *s)
{
	/* absolutely the maximum length */
	char *scan_str, *start_scan_demeta = nalloc (strlen (s) * 4 + 1);
	bool quotes = FALSE;
	int loop = 0, len = strlen (s);

	scan_str = start_scan_demeta;
	for (; loop < len; loop ++) {
		if (*(s + loop) == '\'') {
				*(scan_str ++) = '\'';
				if (quotes == FALSE) {
					*(scan_str ++) = '\'';
					quotes = TRUE;
				}
		/* note that there is no point in quoting any initial hyphen in the completed name, as hyphen is not a shell metacharacter, and quoting only affects the shell's interpretation - not that of any program the filename might be an argument to */
		} else if (non_filename_chars [(unsigned char) *(s + loop)] == TRUE) {
			if (quotes == FALSE) {
				*(scan_str ++) = '\'';
				quotes = TRUE;
			}
		} else {
			if (quotes == TRUE) {
				*(scan_str ++) = '\'';
				quotes = FALSE;
			}
		}
		*(scan_str ++) = *(s + loop);
	}
	if (quotes == TRUE) *(scan_str ++) = '\'';
	*scan_str = '\0';

	return start_scan_demeta;
}

#endif


int
charsize (int pos)
{
	int len = 1;

	/* enh 1996/03/01 - changed in the name of Antonietta. Now both less
	** restrictive and more correct. */
	if ( iscntrl ( (unsigned char)(buf [pos]) ) ) {
		#ifdef ECHOCTL
			len = tio.c_lflag & ECHOCTL ? 2 : 0;
		#else
			len = 2;
		#endif
	}
	return len;
}


void
rl_writechar (int c)
{
	if (
#ifdef ECHOCTL
	tio.c_lflag & ECHOCTL &&
#endif
	c == 127) {
		writechar ('^');
		writechar ('?');
	} else if (c > 31) {
		writechar (c);
	} else if (
#ifdef ECHOCTL
	tio.c_lflag & ECHOCTL &&
#endif
	c < 32) {
		writechar ('^');
		writechar (c + '@');
	}
}


extern void
add_history (char *line)
{
	history_t *scan, *old_scan;
	if (history) {
		history_t *old_next = history->next;
		history->next = ealloc (sizeof (history_t));
		history->next->previous = history;
		history = history->next;
		history->next = old_next;
		if (history->next) history->next->previous = history;
	} else {
		history = ealloc (sizeof (history_t));
		history->next = NULL;
		history->previous = NULL;
	}
	history->text = strdup (line);
	
	/* remove duplicates */

	scan = history->previous;
	while (scan) {
		old_scan = scan;
		scan = scan->previous;
		if (streq (old_scan->text, history->text)) delete_history (old_scan);
	}
	scan = history->next;
	while (scan) {
		old_scan = scan;
		scan = scan->next;
		if (streq (old_scan->text, history->text)) delete_history (old_scan);
	}
}


void
delete_history (history_t *lhistory)
{
	efree (lhistory->text);
	if (lhistory->previous) lhistory->previous->next = lhistory->next;
	if (lhistory->next) lhistory->next->previous = lhistory->previous;
	efree (lhistory);
}


int
rc_putchar (int c)
{
	char cchar = (char) c;
	write (1, &cchar, 1);
	return (int) cchar;
}


char **atmp;


char *
rl_tgetstr (char *capname)
{
	char *cap;

	cap = tgetstr (capname, atmp);
	return cap ? cap : "";
}


extern void
new_term (void)
{
	static char *bp = NULL;
	static char *area = NULL;
	static char *termname;
	List *l;
	if (bp == NULL) {
		bp = malloc (1024);
		area = malloc (1024);
	}
	atmp = &area;
	if (initialised == 0 && tcgetpgrp (0) == getpid ()) {
		tcdrain (0);
		tcflush (0, TCIOFLUSH);
	}
	initialised = !0;
	l = varlookup ("TERM");
	if (l && l->w) {
		termname = l->w;
		tgetent (bp, termname);
		tcauto_left = tgetflag ("bw");
		tcauto_right = tgetflag ("am");
/* if (strstr (termname, "vt") == termname && !strstr (termname, "am")) tcauto_right = FALSE; */

		tcleft = rl_tgetstr ("le");
		if (!*tcleft) tcleft = "\b";
		tcright = rl_tgetstr ("nd");
		tcup = rl_tgetstr ("up");
		tcdown = rl_tgetstr ("do");
		if (!*tcdown) tcdown = "\n";
		tccleartoenddisplay = rl_tgetstr ("cd");
/* if (streq (termname, "nvitty")) tccleartoenddisplay = "\033[0J"; */
		tcdelchar = rl_tgetstr ("dc");
		tcmultleft = rl_tgetstr ("LE");
		tcmultright = rl_tgetstr ("RI");

		tcboldon = rl_tgetstr ("md");
		tcboldoff = rl_tgetstr ("me");
		tcunderon = rl_tgetstr ("us");
		tcunderoff = rl_tgetstr ("ue");
		tcstandon = rl_tgetstr ("so");
		tcstandoff = rl_tgetstr ("se");
		tcreverseon = rl_tgetstr ("mr");
		tcreverseoff = rl_tgetstr ("me");
		if (!*tcboldon) tcboldon = tcreverseon;
		if (!*tcboldon) tcboldon = tcreverseon = tcstandon;
		if (!*tcboldoff) tcboldoff = tcreverseoff = tcstandoff;
	}
	tcgetattr (0, &tio_old);
}


extern void
restore_sanity (void)
{
	tcsetattr (0, TCSADRAIN, &tio_old);
	tcdrain (0);
}

extern void
update_tio (void) {
	struct termios tio;
	tcgetattr (0, &tio);
	if (!terminal_insane (&tio)) tio_old = tio;
}

static void
handle_winch (int s)
{
	getwinsize ();
	if (osig.sa_handler != SIG_DFL && osig.sa_handler != SIG_IGN) {
		osig.sa_handler (s);
	}
}

static void
getwinsize (void)
{
#ifdef TIOCGWINSZ
	struct winsize winsize;
	ioctl (0, TIOCGWINSZ, &winsize);
	tccols = winsize.ws_col;
#endif
	/* DO NOT, REPEAT, DO NOT, LET tccols == 0 */
	if (tccols <= 0) {
		tccols = tgetnum ("co");
		if (tccols <= 0) tccols = 80;
	}
}
