/*
 * zle_misc.c - miscellaneous editor routines
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1997 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zle.mdh"
#include "zle_misc.pro"

/* insert a metafied string, with repetition and suffix removal */

/**/
void
doinsert(char *str)
{
    char *s;
    int len = ztrlen(str);
    int c1 = *str == Meta ? STOUC(str[1])^32 : STOUC(*str);/* first character */
    int neg = zmult < 0;             /* insert *after* the cursor? */
    int m = neg ? -zmult : zmult;    /* number of copies to insert */

    iremovesuffix(c1, 0);
    invalidatelist();

    if(insmode)
	spaceinline(m * len);
    else if(cs + m * len > ll)
	spaceinline(cs + m * len - ll);
    while(m--)
	for(s = str; *s; s++)
	    line[cs++] = *s == Meta ? *++s ^ 32 : *s;
    if(neg)
	cs += zmult * len;
}

/**/
int
selfinsert(char **args)
{
    char s[3], *p = s;

    if(imeta(c)) {
	*p++ = Meta;
	c ^= 32;
    }
    *p++ = c;
    *p = 0;
    doinsert(s);
    return 0;
}

/**/
int
selfinsertunmeta(char **args)
{
    c &= 0x7f;
    if (c == '\r')
	c = '\n';
    return selfinsert(args);
}

/**/
int
deletechar(char **args)
{
    if (zmult < 0) {
	int ret;
	zmult = -zmult;
	ret = backwarddeletechar(args);
	zmult = -zmult;
	return ret;
    }
    if (cs + zmult <= ll) {
	cs += zmult;
	backdel(zmult);
	return 0;
    }
    return 1;
}

/**/
int
backwarddeletechar(char **args)
{
    if (zmult < 0) {
	int ret;
	zmult = -zmult;
	ret = deletechar(args);
	zmult = -zmult;
	return ret;
    }
    backdel(zmult > cs ? cs : zmult);
    return 0;
}

/**/
int
killwholeline(char **args)
{
    int i, fg, n = zmult;

    if (n < 0)
	return 1;
    while (n--) {
	if ((fg = (cs && cs == ll)))
	    cs--;
	while (cs && line[cs - 1] != '\n')
	    cs--;
	for (i = cs; i != ll && line[i] != '\n'; i++);
	forekill(i - cs + (i != ll), fg);
    }
    clearlist = 1;
    return 0;
}

/**/
int
killbuffer(char **args)
{
    cs = 0;
    forekill(ll, 0);
    clearlist = 1;
    return 0;
}

/**/
int
backwardkillline(char **args)
{
    int i = 0, n = zmult;

    if (n < 0) {
	int ret;
	zmult = -n;
	ret = killline(args);
	zmult = n;
	return ret;
    }
    while (n--) {
	if (cs && line[cs - 1] == '\n')
	    cs--, i++;
	else
	    while (cs && line[cs - 1] != '\n')
		cs--, i++;
    }
    forekill(i, 1);
    clearlist = 1;
    return 0;
}

/**/
int
gosmacstransposechars(char **args)
{
    int cc;

    if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') {
	if (cs == ll || line[cs] == '\n' ||
	    ((cs + 1 == ll || line[cs + 1] == '\n') &&
	     (!cs || line[cs - 1] == '\n'))) {
	    return 1;
	}
	cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1;
    }
    cc = line[cs - 2];
    line[cs - 2] = line[cs - 1];
    line[cs - 1] = cc;
    return 0;
}

/**/
int
transposechars(char **args)
{
    int cc, ct;
    int n = zmult;
    int neg = n < 0;

    if (neg)
	n = -n;
    while (n--) {
	if (!(ct = cs) || line[cs - 1] == '\n') {
	    if (ll == cs || line[cs] == '\n')
		return 1;
	    if (!neg)
		cs++;
	    ct++;
	}
	if (neg) {
	    if (cs && line[cs - 1] != '\n') {
		cs--;
		if (ct > 1 && line[ct - 2] != '\n')
		    ct--;
	    }
	} else {
	    if (cs != ll && line[cs] != '\n')
		cs++;
	}
	if (ct == ll || line[ct] == '\n')
	    ct--;
	if (ct < 1 || line[ct - 1] == '\n')
	    return 1;
	cc = line[ct - 1];
	line[ct - 1] = line[ct];
	line[ct] = cc;
    }
    return 0;
}

/**/
int
poundinsert(char **args)
{
    cs = 0;
    vifirstnonblank(zlenoargs);
    if (line[cs] != '#') {
	spaceinline(1);
	line[cs] = '#';
	cs = findeol();
	while(cs != ll) {
	    cs++;
	    vifirstnonblank(zlenoargs);
	    spaceinline(1);
	    line[cs] = '#';
	    cs = findeol();
	}
    } else {
	foredel(1);
	cs = findeol();
	while(cs != ll) {
	    cs++;
	    vifirstnonblank(zlenoargs);
	    if(line[cs] == '#')
		foredel(1);
	    cs = findeol();
	}
    }
    done = 1;
    return 0;
}

/**/
int
acceptline(char **args)
{
    done = 1;
    return 0;
}

/**/
int
acceptandhold(char **args)
{
    pushnode(bufstack, metafy((char *)line, ll, META_DUP));
    stackcs = cs;
    done = 1;
    return 0;
}

/**/
int
killline(char **args)
{
    int i = 0, n = zmult;

    if (n < 0) {
	int ret;
	zmult = -n;
	ret = backwardkillline(args);
	zmult = n;
	return ret;
    }
    while (n--) {
	if (line[cs] == '\n')
	    cs++, i++;
	else
	    while (cs != ll && line[cs] != '\n')
		cs++, i++;
    }
    backkill(i, 0);
    clearlist = 1;
    return 0;
}

/**/
int
killregion(char **args)
{
    if (mark > ll)
	mark = ll;
    if (mark > cs)
	forekill(mark - cs, 0);
    else
	backkill(cs - mark, 1);
    return 0;
}

/**/
int
copyregionaskill(char **args)
{
    if (mark > ll)
	mark = ll;
    if (mark > cs)
	cut(cs, mark - cs, 0);
    else
	cut(mark, cs - mark, 1);
    return 0;
}

static int kct, yankb, yanke;

/**/
int
yank(char **args)
{
    Cutbuffer buf = &cutbuf;
    int n = zmult;

    if (n < 0)
	return 1;
    if (zmod.flags & MOD_VIBUF)
	buf = &vibuf[zmod.vibuf];
    if (!buf->buf)
	return 1;
    mark = cs;
    yankb = cs;
    while (n--) {
	kct = kringnum;
	spaceinline(buf->len);
	memcpy((char *)line + cs, buf->buf, buf->len);
	cs += buf->len;
	yanke = cs;
    }
    return 0;
}

/**/
int
yankpop(char **args)
{
    int cc;

    if (!(lastcmd & ZLE_YANK) || !kring[kct].buf)
	return 1;
    cs = yankb;
    foredel(yanke - yankb);
    cc = kring[kct].len;
    spaceinline(cc);
    memcpy((char *)line + cs, kring[kct].buf, cc);
    cs += cc;
    yanke = cs;
    kct = (kct + KRINGCT - 1) % KRINGCT;
    return 0;
}

/**/
int
overwritemode(char **args)
{
    insmode ^= 1;
    return 0;
}

/**/
int
whatcursorposition(char **args)
{
    char msg[100];
    char *s = msg;
    int bol = findbol();
    int c = STOUC(line[cs]);

    if (cs == ll)
	strucpy(&s, "EOF");
    else {
	strucpy(&s, "Char: ");
	switch (c) {
	case ' ':
	    strucpy(&s, "SPC");
	    break;
	case '\t':
	    strucpy(&s, "TAB");
	    break;
	case '\n':
	    strucpy(&s, "LFD");
	    break;
	default:
	    if (imeta(c)) {
		*s++ = Meta;
		*s++ = c ^ 32;
	    } else
		*s++ = c;
	}
	sprintf(s, " (0%o, %d, 0x%x)", c, c, c);
	s += strlen(s);
    }
    sprintf(s, "  point %d of %d(%d%%)  column %d", cs+1, ll+1,
	    ll ? 100 * cs / ll : 0, cs - bol);
    showmsg(msg);
    return 0;
}

/**/
int
undefinedkey(char **args)
{
    return 1;
}

/**/
int
quotedinsert(char **args)
{
#ifndef HAS_TIO
    struct sgttyb sob;

    sob = shttyinfo.sgttyb;
    sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
    ioctl(SHTTY, TIOCSETN, &sob);
#endif
    c = getkey(0);
#ifndef HAS_TIO
    zsetterm();
#endif
    if (c < 0)
	return 1;
    else
	return selfinsert(args);
}

/**/
int
digitargument(char **args)
{
    int sign = (zmult < 0) ? -1 : 1;

    if (c < '0' || c > '9')
	return 1;

    if (!(zmod.flags & MOD_TMULT))
	zmod.tmult = 0;
    if (zmod.flags & MOD_NEG) {
	/* If we just had a negative argument, this is the digit, *
	 * rather than the -1 assumed by negargument()            */
	zmod.tmult = sign * (c & 0xf);
	zmod.flags &= ~MOD_NEG;
    } else
	zmod.tmult = zmod.tmult * 10 + sign * (c & 0xf);
    zmod.flags |= MOD_TMULT;
    prefixflag = 1;
    return 0;
}

/**/
int
negargument(char **args)
{
    if (zmod.flags & MOD_TMULT)
	return 1;
    zmod.tmult = -1;
    zmod.flags |= MOD_TMULT|MOD_NEG;
    prefixflag = 1;
    return 0;
}

/**/
int
universalargument(char **args)
{
    int digcnt = 0, pref = 0, minus = 1, gotk;
    if (*args) {
	zmod.mult = atoi(*args);
	zmod.flags |= MOD_MULT;
	return 0;
    }
    while ((gotk = getkey(0)) != EOF) {
	if (gotk == '-' && !digcnt) {
	    minus = -1;
	    digcnt++;
	} else if (gotk >= '0' && gotk <= '9') {
	    pref = pref * 10 + (gotk & 0xf);
	    digcnt++;
	} else {
	    ungetkey(gotk);
	    break;
	}
    }
    if (digcnt)
	zmod.tmult = minus * (pref ? pref : 1);
    else
	zmod.tmult *= 4;
    zmod.flags |= MOD_TMULT;
    prefixflag = 1;
    return 0;
}

/**/
int
copyprevword(char **args)
{
    int len, t0;

    for (t0 = cs - 1; t0 >= 0; t0--)
	if (iword(line[t0]))
	    break;
    for (; t0 >= 0; t0--)
	if (!iword(line[t0]))
	    break;
    if (t0)
	t0++;
    len = cs - t0;
    spaceinline(len);
    memcpy((char *)&line[cs], (char *)&line[t0], len);
    cs += len;
    return 0;
}

/**/
int
sendbreak(char **args)
{
    errflag = 1;
    return 1;
}

/**/
int
quoteregion(char **args)
{
    char *str;
    size_t len;

    if (mark > ll)
	mark = ll;
    if (mark < cs) {
	int tmp = mark;
	mark = cs;
	cs = tmp;
    }
    str = (char *)hcalloc(len = mark - cs);
    memcpy(str, (char *)&line[cs], len);
    foredel(len);
    str = makequote(str, &len);
    spaceinline(len);
    memcpy((char *)&line[cs], str, len);
    mark = cs;
    cs += len;
    return 0;
}

/**/
int
quoteline(char **args)
{
    char *str;
    size_t len = ll;

    str = makequote((char *)line, &len);
    sizeline(len);
    memcpy(line, str, len);
    cs = ll = len;
    return 0;
}

/**/
static char *
makequote(char *str, size_t *len)
{
    int qtct = 0;
    char *l, *ol;
    char *end = str + *len;

    for (l = str; l < end; l++)
	if (*l == '\'')
	    qtct++;
    *len += 2 + qtct*3;
    l = ol = (char *)zhalloc(*len);
    *l++ = '\'';
    for (; str < end; str++)
	if (*str == '\'') {
	    *l++ = '\'';
	    *l++ = '\\';
	    *l++ = '\'';
	    *l++ = '\'';
	} else
	    *l++ = *str;
    *l++ = '\'';
    return ol;
}

static char *cmdbuf;
static LinkList cmdll;
static int cmdambig;

/**/
static void
scancompcmd(HashNode hn, int flags)
{
    int l;
    Thingy t = (Thingy) hn;

    if(strpfx(cmdbuf, t->nam)) {
	addlinknode(cmdll, t->nam);
	l = pfxlen(peekfirst(cmdll), t->nam);
	if (l < cmdambig)
	    cmdambig = l;
    }

}

#define NAMLEN 60

/**/
Thingy
executenamedcommand(char *prmt)
{
    Thingy cmd;
    int len, l = strlen(prmt), ols = listshown, feep = 0;
    char *ptr;
    char *okeymap = curkeymapname;

    clearlist = 1;
    cmdbuf = zhalloc(l + NAMLEN + 2);
    strcpy(cmdbuf, prmt);
    statusline = cmdbuf;
    selectkeymap("main", 1);
    ptr = cmdbuf += l;
    len = 0;
    for (;;) {
	*ptr = '_';
	statusll = l + len + 1;
	zrefresh();
	if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) {
	    statusline = NULL;
	    selectkeymap(okeymap, 1);
	    if ((listshown = ols))
		showinglist = -2;
	    else
		clearlist = 1;
	    return NULL;
	}
	if(cmd == Th(z_clearscreen)) {
	    clearscreen(zlenoargs);
	} else if(cmd == Th(z_redisplay)) {
	    redisplay(zlenoargs);
	} else if(cmd == Th(z_viquotedinsert)) {
	    *ptr = '^';
	    zrefresh();
	    c = getkey(0);
	    if(c == EOF || !c || len == NAMLEN)
		feep = 1;
	    else
		*ptr++ = c, len++;
	} else if(cmd == Th(z_quotedinsert)) {
	    if((c = getkey(0)) == EOF || !c || len == NAMLEN)
		feep = 1;
	    else
		*ptr++ = c, len++;
	} else if(cmd == Th(z_backwarddeletechar) ||
	    	cmd == Th(z_vibackwarddeletechar)) {
	    if (len)
		len--, ptr--;
	} else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) ||
	    	cmd == Th(z_vibackwardkillword)) {
	    while (len && (len--, *--ptr != '-'));
	} else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) ||
	    	cmd == Th(z_backwardkillline)) {
	    len = 0;
	    ptr = cmdbuf;
	} else {
	    if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) {
		Thingy r;
		unambiguous:
		*ptr = 0;
		r = rthingy(cmdbuf);
		if (!(r->flags & DISABLED)) {
		    unrefthingy(r);
		    statusline = NULL;
		    selectkeymap(okeymap, 1);
		    if ((listshown = ols))
			showinglist = -2;
		    else
			clearlist = 1;
		    return r;
		}
		unrefthingy(r);
	    }
	    if(cmd == Th(z_selfinsertunmeta)) {
		c &= 0x7f;
		if(c == '\r')
		    c = '\n';
		cmd = Th(z_selfinsert);
	    }
	    if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) ||
		cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) ||
		cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) ||
		cmd == Th(z_acceptline) || c == ' ' || c == '\t') {
		cmdambig = 100;

		HEAPALLOC {
		    cmdll = newlinklist();
		    *ptr = 0;

		    scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0);
		} LASTALLOC;
		if (empty(cmdll))
		    feep = 1;
		else if (cmd == Th(z_listchoices) ||
		    cmd == Th(z_deletecharorlist)) {
		    int zmultsav = zmult;
		    *ptr = '_';
		    statusll = l + len + 1;
		    zmult = 1;
		    listlist(cmdll);
		    showinglist = 0;
		    zmult = zmultsav;
		} else if (!nextnode(firstnode(cmdll))) {
		    strcpy(ptr = cmdbuf, peekfirst(cmdll));
		    ptr += (len = strlen(ptr));
		    if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode))
			goto unambiguous;
		} else {
		    strcpy(cmdbuf, peekfirst(cmdll));
		    ptr = cmdbuf + cmdambig;
		    *ptr = '_';
		    if (isset(AUTOLIST) &&
			!(isset(LISTAMBIGUOUS) && cmdambig > len)) {
			int zmultsav = zmult;
			if (isset(LISTBEEP))
			    feep = 1;
			statusll = l + cmdambig + 1;
			zmult = 1;
			listlist(cmdll);
			showinglist = 0;
			zmult = zmultsav;
		    }
		    len = cmdambig;
		}
	    } else {
		if (len == NAMLEN || icntrl(c) || cmd != Th(z_selfinsert))
		    feep = 1;
		else
		    *ptr++ = c, len++;
	    }
	}
	if (feep)
	    handlefeep(zlenoargs);
	feep = 0;
    }
}

/*****************/
/* Suffix system */
/*****************/

/*
 * The completion system sometimes tentatively adds a suffix to a word,
 * which can be removed depending on what is inserted next.  These
 * functions provide the capability to handle a removable suffix.
 *
 * Any removable suffix consists of characters immediately before the
 * cursor.  Whether it is removed depends on the next editing action.
 * There can be more than one suffix simultaneously present, with
 * different actions deleting different numbers of characters.
 *
 * If the next editing action changes the buffer other than by inserting
 * characters, normally the suffix should be removed so as to leave a
 * meaningful complete word.  The behaviour should be the same if the
 * next character inserted is a word separator.  If the next character
 * reasonably belongs where it is typed, or if the next editing action
 * is a deletion, the suffix should not be removed.  Other reasons for
 * suffix removal may have other behaviour.
 *
 * In order to maintain a consistent state, after a suffix has been added
 * the table *must* be zeroed, one way or another, before the buffer is
 * changed.  If the suffix is not being removed, call fixsuffix() to
 * indicate that it is being permanently fixed.
 */

/* Length of suffix to remove when inserting each possible character value.  *
 * suffixlen[256] is the length to remove for non-insertion editing actions. */

/**/
int suffixlen[257];

/* Shell function to call to remove the suffix. */

/**/
static char *suffixfunc;

/* Set up suffix: the last n characters are a suffix that should be *
 * removed in the usual word end conditions.                        */

/**/
void
makesuffix(int n)
{
    suffixlen[256] = suffixlen[' '] = suffixlen['\t'] = suffixlen['\n'] = 
	suffixlen[';'] = suffixlen['&'] = suffixlen['|'] = n;
}

/* Set up suffix for parameter names: the last n characters are a suffix *
 * that should be removed if the next character is one of the ones that  *
 * needs to go immediately after the parameter name.  br indicates that  *
 * the name is in braces (${PATH} instead of $PATH), so the extra        *
 * characters that can only be used in braces are included.              */

/**/
void
makeparamsuffix(int br, int n)
{
    if(br || unset(KSHARRAYS))
	suffixlen[':'] = suffixlen['['] = n;
    if(br) {
	suffixlen['#'] = suffixlen['%'] = suffixlen['?'] = n;
	suffixlen['-'] = suffixlen['+'] = suffixlen['='] = n;
	/*{*/ suffixlen['}'] = suffixlen['/'] = n;
    }
}

/* Set up suffix given a string containing the characters on which to   *
 * remove the suffix. */

/**/
void
makesuffixstr(char *f, char *s, int n)
{
    if (f) {
	zsfree(suffixfunc);
	suffixfunc = ztrdup(f);
	suffixlen[0] = n;
    } else if (s) {
	int inv, i, v, z = 0;

	if (*s == '^' || *s == '!') {
	    inv = 1;
	    s++;
	} else
	    inv = 0;
	s = getkeystring(s, &i, 5, &z);
	s = metafy(s, i, META_USEHEAP);

	if (inv) {
	    v = 0;
	    for (i = 0; i < 257; i++)
		 suffixlen[i] = n;
	} else
	    v = n;

	if (z)
	    suffixlen[256] = v;

	while (*s) {
	    if (s[1] == '-' && s[2]) {
		int b = (int) *s, e = (int) s[2];

		while (b <= e)
		    suffixlen[b++] = v;
		s += 2;
	    } else
		suffixlen[STOUC(*s)] = v;
	    s++;
	}
    } else
	makesuffix(n);
}

/* Remove suffix, if there is one, when inserting character c. */

/**/
void
iremovesuffix(int c, int keep)
{
    if (suffixfunc) {
	List l = getshfunc(suffixfunc);

	if (l != &dummy_list) {
	    LinkList args = newlinklist();
	    char buf[20];
	    int osc = sfcontext;

	    sprintf(buf, "%d", suffixlen[0]);
	    addlinknode(args, suffixfunc);
	    addlinknode(args, buf);

	    startparamscope();
	    makezleparams(0);
	    sfcontext = SFC_COMPLETE;
	    doshfunc(suffixfunc, l, args, 0, 1);
	    sfcontext = osc;
	    endparamscope();
	}
	zsfree(suffixfunc);
	suffixfunc = NULL;
    } else {
	int sl = suffixlen[c];
	if(sl) {
	    backdel(sl);
	    if (!keep)
		invalidatelist();
	}
    }
    fixsuffix();
}

/* Fix the suffix in place, if there is one, making it non-removable. */

/**/
void
fixsuffix(void)
{
    memset(suffixlen, 0, sizeof(suffixlen));
}