/*
 *
 * zle_main.c - main routines for line editor
 *
 * This file is part of zsh, the Z shell.
 *
 * This software is Copyright 1992 by Paul Falstad
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction of this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The author make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 *
 */

#define ZLEGLOBALS
#define ZLE
#include "zsh.h"
#include <sys/types.h>
#include <sys/errno.h>
#ifdef HAS_SYS_SELECT
#include <sys/select.h>
#endif

static Key cky;

/* set up terminal */

void setterm() /**/
{
struct ttyinfo ti;
#ifdef CLOBBERS_TYPEAHEAD
#ifdef FIONREAD
long val;
#endif
#endif

#ifdef CLOBBERS_TYPEAHEAD
#ifdef FIONREAD
	ioctl(SHTTY, FIONREAD, &val);
	if (val) return;
#endif
#endif
	inittty();
	ti = shttyinfo;
#ifdef TIO
	ti.tio.c_lflag &= ~(ICANON|ECHO
#ifdef FLUSHO
		|FLUSHO
#endif
		);
	ti.tio.c_cc[VQUIT] =
#ifdef VDISCARD
		ti.tio.c_cc[VDISCARD] = 
#endif
#ifdef VSUSP
		ti.tio.c_cc[VSUSP] =
#endif
#ifdef VDSUSP
		ti.tio.c_cc[VDSUSP] =
#endif
#ifdef VSWTCH
		ti.tio.c_cc[VSWTCH] =
#endif
		VDISABLEVAL;
	ti.tio.c_cc[VMIN] = 1;
	ti.tio.c_cc[VTIME] = 0;
	ti.tio.c_iflag &= ~(INLCR|ICRNL);
#else
	ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO;
	ti.lmodes &= ~LFLUSHO;
	ti.tchars.t_quitc =
		ti.ltchars.t_suspc =
		ti.ltchars.t_flushc =
		ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1;
#endif
#ifdef TTY_NEEDS_DRAINING
	drainoutput();
#endif
	settyinfo(&ti);
}

void unsetterm() /**/
{
	settyinfo(&shttyinfo);
}

static char *kungetbuf;
static int kungetct,kungetsz;

void ungetkey(ch) /**/
int ch;
{
	if (kungetct == kungetsz)
		kungetbuf = realloc(kungetbuf,kungetsz *= 2);
	kungetbuf[kungetct++] = ch;
}

void ungetkeys(s,len) /**/
char *s;int len;
{
	s += len;
	while (len--)
		ungetkey(*--s);
}

#if (defined(hpux) || defined(pyr)) && defined(HAS_SELECT)
static int
breakread(fd, buf, n)
int fd, n;
char *buf;
{
	fd_set	f;

	FD_ZERO(&f);
	FD_SET(fd, &f);
	return(select(fd+1, &f, NULL, NULL, NULL) == -1 ? -1 : read(fd, buf, n));
}
#define	read	breakread
#endif

unsigned int getkey(tmok) /**/
int tmok;
{
char cc;
unsigned int ret;
int die = 0, r;

	if (kungetct)
		ret = (unsigned int) (unsigned char) kungetbuf[--kungetct];
	else {
		while ( (r = read(0,&cc,1)) != 1){
			if ( r == 0 ){
				stopmsg = 1;
				zexit(1);
			}
			if (errno == EINTR) {
				if (!errflag)
					continue;
				errflag = 0;
				if (tmok)
					return -1;
				return 3;
			} else if (errno == EWOULDBLOCK) {
				fcntl(0,F_SETFL,0);
			} else if (errno == EIO && !die) {
				ret = jobbing;
				jobbingv = 1;
				attachtty(mypgrp);
				refresh(); /* kludge! */
				jobbingv = ret;
				die = 1;
			} else if (errno != 0) {
				zerr("error on TTY read: %e",NULL,errno);
				stopmsg = 1;
				zexit(1);
			}
		}
		ret = (unsigned int) (unsigned char) cc;
	}
	if (vichgflag) {
		if (vichgbufptr == vichgbufsz)
			vichgbuf = realloc(vichgbuf,vichgbufsz *= 2);
		vichgbuf[vichgbufptr++] = ret;
	}
	return ret;
}

/* read a line */

unsigned char *zleread(ppt,ppt2,plen) /**/
unsigned char *ppt;unsigned char *ppt2;int plen;
{
int z;
long costmult;
unsigned char *s;
#ifdef HAS_SELECT
struct timeval tv;
fd_set foofd;

	tv.tv_sec = 0;
#endif
	fflush(stdout);
	fflush(stderr);
	intr();
	costmult = 3840000L/((baud) ? baud : 2400);
	insmode = unset(OVERSTRIKE); eofsent = 0; resetneeded =0 ;
	pmpt = (char *)ppt;
	pmpt2 = (char *)ppt2;
	permalloc();
	histline = curhist;
	pptlen = plen;
	resetneeded = 1;
#ifdef HAS_SELECT
	FD_ZERO(&foofd);
#endif
	undoing = 1;
	line = zalloc(linesz = 256);
	*line = '\0';
	virangeflag = lastcmd = done = cs = ll = mark = 0;
	curhistline = NULL;
	mult = 1;
	vibufspec = 0;
	bindtab = mainbindtab;
	addedslash = vichgflag = 0;
	viinsbegin = 0;
	statusline = NULL;
	if (s = getnode(bufstack))
		{
		setline((char *) s);
		free(s);
		if (stackcs != -1)
			{
			cs = stackcs;
			stackcs = -1;
			if (cs > ll)
				cs = ll;
			}
		if (stackhist != -1)
			{
			histline = stackhist;
			stackhist = -1;
			}
		}
	initundo();
	if (unset(NOPROMPTCR))
		putchar('\r');
	if (tmout)
		alarm(tmout);
	refresh();
	errflag = retflag = 0;
	while (!done && !errflag)
		{
		struct zlecmd *zc;
		
		statusline = NULL;
		bindk = getkeycmd();
		if (c == 4 && !ll)
			{
			eofsent = 1;
			break;
			}
		if (bindk != -1)
			{
			zc = zlecmds+bindk;
			if (!(lastcmd & ZLE_ARG))
				mult = 1;
			if ((lastcmd & ZLE_UNDO) != (zc->flags & ZLE_UNDO) && undoing)
				addundo();
			if (!(zc->flags & ZLE_MENUCMP)) {
				if (menucmp) freemenu();
				if (addedslash && !(zc->flags & ZLE_DELETE) &&
						!((zc->flags & ZLE_INSERT) && c != ' ')) {
					backdel(1);
				}
				addedslash = 0;
			}
			if (zc->func)
				(*zc->func)();
			lastcmd = zc->flags;
			if (!(lastcmd & ZLE_UNDO) && undoing) addundo();
			}
		else
			{
			errflag = 1;
			break;
			}
#ifdef HAS_SELECT
		FD_SET(0,&foofd);
		if ((tv.tv_usec = cost*costmult) > 500000)
			tv.tv_usec = 500000;
#endif
		if (!kungetct
#ifdef HAS_SELECT
			&& select(1,&foofd,NULL,NULL,&tv) <= 0
#endif
			)
			refresh();
		}
	if (menucmp)
		freemenu();
	statusline = NULL;
	trashzle();
	alarm(0);
	z = strlen((char *)line);
	line[z] = '\n';
	line[z+1] = 0;
	heapalloc();
	if (curhistline)
		free(curhistline);
	if (eofsent)
		{
		free(line);
		line = NULL;
		}
	zleactive = 0;
	freeundo();
	return line;
}

int getkeycmd() /**/
{
char buf[10];
int t0,ret;
Key ky;

	t0 = 1;
	cky = NULL;
	if ((c = getkey(1)) == -1)
		return -1;
	if ((ret = bindtab[c]) == z_sequenceleadin)
		{
		buf[0] = (c) ? c : 0x80;
		for (;;)
			{
			c = getkey(0);
			buf[t0++] = (c) ? c : 0x80;
			buf[t0] = '\0';
			if (!(ky = (Key) gethnode(buf,xbindtab)))
				return z_undefinedkey;
			if (ky->func != z_sequenceleadin)
				{
				cky = ky;
				ret = ky->func;
				break;
				}
			}
		}
	if (ret == z_vidigitorbeginningofline)
		ret = (lastcmd & ZLE_ARG) ? z_digitargument : z_beginningofline;
	else if (ret == z_executenamedcmd)
		ret = executenamedcommand();
	else if (ret == z_executelastnamedcmd)
		ret = lastnamed;
	return ret;
}

void sendstring() /**/
{
char buf[2];

	buf[0] = c;
	buf[1] = '\0';
	if (!cky)
		cky = (Key) gethnode(buf,xbindtab);
	ungetkeys(cky->str,cky->len);
}

Key makefunckey(fun) /**/
int fun;
{
Key ky = zcalloc(sizeof *ky);

	ky->func = fun;
	return ky;
}

/* initialize the bindings */

void initxbindtab() /**/
{
int t0,vi = 0;
char buf[3],*s;

	lastnamed = z_undefinedkey;
	if (s = zgetenv("VISUAL")) {
		if (ztrstr(s,"vi"))
			vi = 1;
	}
	else if ((s = zgetenv("EDITOR")) && ztrstr(s,"vi"))
		vi = 1;
	if (vi) {
		for (t0 = 0; t0 != 32; t0++)
			mainbindtab[t0] = viinsbind[t0];
		for (t0 = 32; t0 != 256; t0++)
			mainbindtab[t0] = z_selfinsert;
		mainbindtab[127] = z_backwarddeletechar;
	} else {
		for (t0 = 0; t0 != 128; t0++)
			mainbindtab[t0] = emacsbind[t0];
		for (t0 = 128; t0 != 256; t0++)
			mainbindtab[t0] = z_selfinsert;
	}
	for (t0 = 0200; t0 != 0240; t0++)
		mainbindtab[t0] = z_undefinedkey;
	for (t0 = 0; t0 != 128; t0++)
		altbindtab[t0] = vicmdbind[t0];
	for (t0 = 128; t0 != 256; t0++)
		altbindtab[t0] = emacsbind[t0];
	bindtab = mainbindtab;
	kungetbuf = zalloc(kungetsz = 32);
	kungetct = 0;
	xbindtab = newhtable(67);
	addhperm("\33\133C",makefunckey(z_forwardchar),xbindtab,(FFunc) 0);
	addhperm("\33\133D",makefunckey(z_backwardchar),xbindtab,(FFunc) 0);
	addhperm("\33\133A",makefunckey(z_uplineorhistory),xbindtab,(FFunc) 0);
	addhperm("\33\133B",makefunckey(z_downlineorhistory),xbindtab,(FFunc) 0);
	addhperm("\30*",makefunckey(z_expandword),xbindtab,(FFunc) 0);
	addhperm("\30g",makefunckey(z_listexpand),xbindtab,(FFunc) 0);
	addhperm("\30G",makefunckey(z_listexpand),xbindtab,(FFunc) 0);
	addhperm("\30\16",makefunckey(z_infernexthistory),xbindtab,(FFunc) 0);
	addhperm("\30\13",makefunckey(z_killbuffer),xbindtab,(FFunc) 0);
	addhperm("\30\6",makefunckey(z_vifindnextchar),xbindtab,(FFunc) 0);
	addhperm("\30\17",makefunckey(z_overwritemode),xbindtab,(FFunc) 0);
	addhperm("\30\25",makefunckey(z_undo),xbindtab,(FFunc) 0);
	addhperm("\30\26",makefunckey(z_vicmdmode),xbindtab,(FFunc) 0);
	addhperm("\30\12",makefunckey(z_vijoin),xbindtab,(FFunc) 0);
	addhperm("\30\2",makefunckey(z_vimatchbracket),xbindtab,(FFunc) 0);
	addhperm("\30s",makefunckey(z_historyincrementalsearchforward),
		xbindtab,(FFunc) 0);
	addhperm("\30r",makefunckey(z_historyincrementalsearchbackward),
		xbindtab,(FFunc) 0);
	addhperm("\30u",makefunckey(z_undo),xbindtab,(FFunc) 0);
	addhperm("\30\30",makefunckey(z_exchangepointandmark),
		xbindtab,(FFunc) 0);
	addhperm("run-help",mkanode(ztrdup("man"),1),aliastab,(FFunc) 0);
	addhperm("which-command",mkanode(ztrdup("whence"),1),aliastab,(FFunc) 0);
	strcpy(buf,"\33q");
	for (t0 = 128; t0 != 256; t0++)
		if (emacsbind[t0] != z_undefinedkey) {
			buf[1] = t0 & 0x7f;
			addhnode(ztrdup(buf),makefunckey(emacsbind[t0]),xbindtab,(FFunc) 0);
		}
	for (t0 = 0; t0 != 36; t0++) vibuf[t0] = NULL;
	for (t0 = 0; t0 != 26; t0++) vimarkline[t0] = 0;
	stackhist = stackcs = -1;
	vichgbufsz = 0;
	vichgbuf = NULL;
	usernamescached=0;
}

char *getkeystring(s,len) /**/
char *s;int *len;
{
static char buf[512];
char *t = buf;
int x,metanext = 0;

	for (;*s;s++)
		{
		if (*s == '\\' && s[1])
			switch(*++s)
				{
				case 'a': *t++ = '\07'; break;
				case 'n': *t++ = '\n'; break;
				case 'b': *t++ = '\010'; break;
				case 't': *t++ = '\t'; break;
				case 'v': *t++ = '\v'; break;
				case 'f': *t++ = '\f'; break;
				case 'r': *t++ = '\r'; break;
				case 'e': *t++ = '\033'; break;
				case 'M':
					if (s[1] == '-')
						s++;
					metanext = 2;
					break;
				default:
					if (idigit(*s))
						{
						for (x = 0; idigit(*s); s++)
							x = x*8+(*s-'0');
						s--;
						*t++ = x;
						}
					else
						*t++ = *s;
					break;
				}
		else if (*s == '^')
			if (*++s == '?')
				*t++ = 0x7f;
			else
				*t++ = *s & 0x9f;
		else
			*t++ = *s;
		if (metanext && !(--metanext))
			{
			t[-1] |= 0x80;
			metanext = 0;
			}
		if (t > buf+500)
			break;
		}
	*t = '\0';
	*len = t-buf;
	return buf;
}

void printbind(s,len) /**/
char *s;int len;
{
int ch;

	while (len--)
		{
		ch = (unsigned char) *s++;
		if (ch & 0x80)
			{
			printf("\\M-");
			ch &= 0x7f;
			}
		if (icntrl(ch))
			switch(ch)
				{
				case 0x7f: printf("^?"); break;
				default: printf("^%c",(ch|0x40)); break;
				}
		else
			putchar(ch);
		}
}

void printbinding(str,k) /**/
char *str;Key k;
{
	if (k->func == z_sequenceleadin)
		return;
	putchar('\"');
	printbind(str,strlen(str));
	printf("\"\t");
	if (k->func == z_sendstring)
		{
		putchar('\"');
		printbind(k->str,k->len);
		printf("\"\n");
		}
	else
		printf("%s\n",zlecmds[k->func].name);
}

int bin_bindkey(name,argv,ops,junc) /**/
char *name;char **argv;char *ops;int junc;
{
int t0 = 0,len;
char *s;
int func,*tab;

	tab = (ops['a']) ? altbindtab : mainbindtab;
	if (ops['v'] || ops['e'] || ops['d'])
		{
		if (*argv)
			{
			zerrnam(name,"too many arguments",NULL,0);
			return 1;
			}
		if (ops['d'] || ops['e'])
			if (ops['m'])
				for (t0 = 0; t0 != 256; t0++)
					tab[t0] = emacsbind[t0];
			else
				{
				for (t0 = 0; t0 != 128; t0++)
					tab[t0] = emacsbind[t0];
				for (t0 = 128; t0 != 256; t0++)
					tab[t0] = z_selfinsert;
				}
		else
			{
			for (t0 = 0; t0 != 32; t0++)
				mainbindtab[t0] = viinsbind[t0];
			for (t0 = 32; t0 != 256; t0++)
				mainbindtab[t0] = z_selfinsert;
			mainbindtab[127] = z_backwarddeletechar;
			}
		for (t0 = 0; t0 != 128; t0++)
			altbindtab[t0] = vicmdbind[t0];
		for (t0 = 128; t0 != 256; t0++)
			altbindtab[t0] = emacsbind[t0];
		for (t0 = 0200; t0 != 0240; t0++)
			tab[t0] = z_undefinedkey;
		return 0;
		}
	if (!*argv)
		{
		char buf[2];
		
		buf[0] = 'x'; buf[1] = '\0';
		for (t0 = 0; t0 != 256; t0++)
			{
			buf[0] = t0;
			putchar('\"');
			printbind(buf,1);
			if (t0 < 254 && tab[t0] == tab[t0+1] && tab[t0] == tab[t0+2])
				{
				printf("\" to \"");
				while (tab[t0] == tab[t0+1]) t0++;
				buf[0] = t0;
				printbind(buf,1);
				}
			printf("\"\t%s\n",zlecmds[tab[t0]].name);
			}
		listhtable(xbindtab,(HFunc) printbinding);
		return 0;
		}
	while (*argv)
		{
		s = getkeystring(*argv++,&len);
		if (len > 8)
			{
			zerrnam(name,"in-string too long",NULL,0);
			return 1;
			}
		if (!*argv || ops['r'])
			{
			Key ky;

			ky = gethnode(s,xbindtab);
			if (len == 1)
				func = tab[(unsigned char) *s];
			else
				func = (ky) ? ky->func : z_undefinedkey;
			if (func == z_undefinedkey)
				{
				zerrnam(name,"in-string is not bound",NULL,0);
				return 1;
				}
			if (ops['r'])
				{
				if (len == 1)
					tab[(unsigned char) *s] = z_undefinedkey;
				else
					{
					while (strlen(s) > 1)
						{
						free(remhnode(s,xbindtab));
						s[strlen(s)-1] = '\0';
						}
					}
				continue;
				}
			if (func == z_sendstring)
				{
				printbind(ky->str,ky->len);
				putchar('\n');
				return 0;
				}
			printf("%s\n",zlecmds[func].name);
			return 0;
			}
		if (!ops['s'])
			{
			for (t0 = 0; t0 != ZLECMDCOUNT; t0++)
				if (!strcmp(*argv,zlecmds[t0].name))
					break;
			if (t0 == ZLECMDCOUNT)
				{
				zerr("undefined function: %s",*argv,0);
				return 1;
				}
			func = t0;
			}
		else
			func = z_sendstring;
		if (len == 1)
			{
			Key ky;

			tab[(unsigned char) *s] = (ops['s']) ? z_sendstring : t0;
			if (ops['s'])
				{
				addhnode(ztrdup(s),ky = makefunckey(z_sendstring),xbindtab,freekey);
				ky->str = ztrdup(getkeystring(*argv,&ky->len));
				}
			}
		else
			{
			int t1;
			Key ky;

			if (tab[(unsigned char) *s] != z_undefinedkey &&
					tab[(unsigned char) *s] != z_sequenceleadin)
				{
				zerrnam(name,"in-string has already bound prefix",NULL,0);
				return 1;
				}
			tab[(unsigned char) *s] = z_sequenceleadin;
			if (!s[1])
				s[1] = 0x80;
			for (t1 = 1; t1 != len-1; t1++)
				{
				char sav;

				sav = s[t1+1];
				s[t1+1] = '\0';
				ky = gethnode(s,xbindtab);
				if (ky && ky->func != z_sequenceleadin)
					{
					zerrnam(name,"in-string has already bound prefix",NULL,0);
					return 1;
					}
				if (!ky)
					addhnode(ztrdup(s),makefunckey(z_sequenceleadin),xbindtab,
						freekey);
				if (!sav)
					sav = 0x80;
				s[t1+1] = sav;
				}
			addhnode(ztrdup(s),ky = makefunckey(func),xbindtab,freekey);
			if (ops['s'])
				ky->str = ztrdup(getkeystring(*argv,&ky->len));
			}
		argv++;
		}
	return 0;
}

void freekey(x) /**/
vptr x;
{
Key k = x;

	if (k->str)
		free(k->str);
	free(k);
}

/* this is mostly stolen from bash's draino() */

void drainoutput() /**/
{
int n = 0;

	if (!baud) return;
#ifdef TIOCOUTQ
#ifdef HAS_SELECT
	while ((ioctl(SHTTY,TIOCOUTQ,&n) >= 0) && n) {
		struct timeval tv;
		tv.tv_sec = n/baud;
		tv.tv_usec = ((n%baud)*1000000)/baud;
		select (0,(fd_set *)0,(fd_set *)0,(fd_set *)0,&tv);
	}
#endif
#endif
}

