/*
 * zle_vi.c - vi-specific functions
 *
 * 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_vi.pro"

/* != 0 if we're getting a vi range */

/**/
int virangeflag;

/* kludge to get cw and dw to work right */

/**/
int wordflag;

/* != 0 if we're killing lines into a buffer, vi-style */

/**/
int vilinerange;

/* last vi change buffer, for vi change repetition */

/**/
int vichgbufsz, vichgbufptr, vichgflag;

/**/
char *vichgbuf;

/* point where vi insert mode was last entered */

/**/
int viinsbegin;

static struct modifier lastmod;
static int inrepeat, vichgrepeat;

/**/
static void
startvichange(int im)
{
    if (im != -1) {
	insmode = im;
	vichgflag = 1;
    }
    if (inrepeat) {
	zmod = lastmod;
	inrepeat = vichgflag = 0;
	vichgrepeat = 1;
    } else {
	lastmod = zmod;
	if (vichgbuf)
	    free(vichgbuf);
	vichgbuf = (char *)zalloc(vichgbufsz = 16);
	vichgbuf[0] = c;
	vichgbufptr = 1;
	vichgrepeat = 0;
    }
}

/**/
static void
startvitext(int im)
{
    startvichange(im);
    selectkeymap("main", 1);
    undoing = 0;
    viinsbegin = cs;
}

/**/
int
vigetkey(void)
{
    Keymap mn = openkeymap("main");
    char m[3], *str;
    Thingy cmd;

    if((c = getkey(0)) == EOF)
	return -1;

    m[0] = c;
    metafy(m, 1, META_NOALLOC);
    if(mn)
	cmd = keybind(mn, m, &str);
    else
	cmd = t_undefinedkey;

    if (!cmd || cmd == Th(z_sendbreak)) {
	return -1;
    } else if (cmd == Th(z_quotedinsert)) {
	if ((c = getkey(0)) == EOF)
	    return -1;
    } else if(cmd == Th(z_viquotedinsert)) {
	char sav = line[cs];

	line[cs] = '^';
	zrefresh();
	c = getkey(0);
	line[cs] = sav;
	if(c == EOF)
	    return -1;
    } else if (cmd == Th(z_vicmdmode))
	return -1;
    return c;
}

/**/
static int
getvirange(int wf)
{
    int pos = cs, ret = 0;
    int mult1 = zmult, hist1 = histline;
    Thingy k2;

    virangeflag = 1;
    wordflag = wf;
    /* Now we need to execute the movement command, to see where it *
     * actually goes.  virangeflag here indicates to the movement   *
     * function that it should place the cursor at the end of the   *
     * range, rather than where the cursor would actually go if it  *
     * were executed normally.  This makes a difference to some     *
     * commands, but not all.  For example, if searching forward    *
     * for a character, under normal circumstances the cursor lands *
     * on the character.  For a range, the range must include the   *
     * character, so the cursor gets placed after the character if  *
     * virangeflag is set.  vi-match-bracket needs to change the    *
     * value of virangeflag under some circumstances, meaning that  *
     * we need to change the *starting* position.                   */
    zmod.flags &= ~MOD_TMULT;
    do {
	vilinerange = 0;
	prefixflag = 0;
	if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) ||
		k2 == Th(z_sendbreak)) {
	    wordflag = 0;
	    virangeflag = 0;
	    return -1;
	}
	/*
	 * With k2 == bindk, the command key is repeated:
	 * a number of lines is used.  If the function used
	 * returns 1, we fail.
	 */
	if ((k2 == bindk) ? dovilinerange() : execzlefunc(k2, zlenoargs))
	    ret = -1;
	if(vichgrepeat)
	    zmult = mult1;
	else
	    zmult = mult1 * zmod.tmult;
    } while(prefixflag && !ret);
    wordflag = 0;
    virangeflag = 0;

    /* It is an error to use a non-movement command to delimit the *
     * range.  We here reject the case where the command modified  *
     * the line, or selected a different history line.             */
    if (histline != hist1 || ll != lastll || memcmp(line, lastline, ll)) {
	histline = hist1;
	memcpy(line, lastline, ll = lastll);
	cs = pos;
	return -1;
    }

    /* Can't handle an empty file.  Also, if the movement command *
     * failed, or didn't move, it is an error.                    */
    if (!ll || (cs == pos && virangeflag != 2) || ret == -1)
	return -1;

    /* vi-match-bracket changes the value of virangeflag when *
     * moving to the opening bracket, meaning that we need to *
     * change the *starting* position.                        */
    if(virangeflag == -1)
	pos++;

    /* Get the range the right way round.  cs is placed at the *
     * start of the range, and pos (the return value of this   *
     * function) is the end.                                   */
    if (cs > pos) {
	int tmp = cs;
	cs = pos;
	pos = tmp;
    }

    /* Was it a line-oriented move?  If so, the command will have set *
     * the vilinerange flag.  In this case, entire lines are taken,   *
     * rather than just the sequence of characters delimited by pos   *
     * and cs.  The terminating newline is left out of the range,     *
     * which the real command must deal with appropriately.  At this  *
     * point we just need to make the range encompass entire lines.   */
    if(vilinerange) {
	int newcs = findbol();
	cs = pos;
	pos = findeol();
	cs = newcs;
    }
    return pos;
}

/**/
static int
dovilinerange(void)
{
    int pos = cs, n = zmult;

    /* A number of lines is taken as the range.  The current line *
     * is included.  If the repeat count is positive the lines go *
     * downward, otherwise upward.  The repeat count gives the    *
     * number of lines.                                           */
    vilinerange = 1;
    if (!n)
	return 1;
    if (n > 0) {
	while(n-- && cs <= ll)
	    cs = findeol() + 1;
	if (n != -1) {
	    cs = pos;
	    return 1;
	}
	cs--;
    } else {
	while(n++ && cs >= 0)
	    cs = findbol() - 1;
	if (n != 1) {
	    cs = pos;
	    return 1;
	}
	cs++;
    }
    virangeflag = 2;
    return 0;
}

/**/
int
viaddnext(char **args)
{
    if (cs != findeol())
	cs++;
    startvitext(1);
    return 0;
}

/**/
int
viaddeol(char **args)
{
    cs = findeol();
    startvitext(1);
    return 0;
}

/**/
int
viinsert(char **args)
{
    startvitext(1);
    return 0;
}

/**/
int
viinsertbol(char **args)
{
    vifirstnonblank(zlenoargs);
    startvitext(1);
    return 0;
}

/**/
int
videlete(char **args)
{
    int c2, ret = 1;

    startvichange(1);
    if ((c2 = getvirange(0)) != -1) {
	forekill(c2 - cs, 0);
	ret = 0;
	if (vilinerange && ll) {
	    if (cs == ll)
		cs--;
	    foredel(1);
	    vifirstnonblank(zlenoargs);
	}
    }
    vichgflag = 0;
    return ret;
}

/**/
int
videletechar(char **args)
{
    int n = zmult;

    startvichange(-1);
    /* handle negative argument */
    if (n < 0) {
	int ret;
	zmult = -n;
	ret = vibackwarddeletechar(args);
	zmult = n;
	return ret;
    }
    /* it is an error to be on the end of line */
    if (cs == ll || line[cs] == '\n')
	return 1;
    /* Put argument into the acceptable range -- it is not an error to  *
     * specify a greater count than the number of available characters. */
    if (n > findeol() - cs)
	n = findeol() - cs;
    /* do the deletion */
    forekill(n, 0);
    return 0;
}

/**/
int
vichange(char **args)
{
    int c2, ret = 1;

    startvichange(1);
    if ((c2 = getvirange(1)) != -1) {
	ret = 0;
	forekill(c2 - cs, 0);
	selectkeymap("main", 1);
	viinsbegin = cs;
	undoing = 0;
    }
    return ret;
}

/**/
int
visubstitute(char **args)
{
    int n = zmult;

    startvichange(1);
    if (n < 0)
	return 1;
    /* it is an error to be on the end of line */
    if (cs == ll || line[cs] == '\n')
	return 1;
    /* Put argument into the acceptable range -- it is not an error to  *
     * specify a greater count than the number of available characters. */
    if (n > findeol() - cs)
	n = findeol() - cs;
    /* do the substitution */
    forekill(n, 0);
    startvitext(1);
    return 0;
}

/**/
int
vichangeeol(char **args)
{
    forekill(findeol() - cs, 0);
    startvitext(1);
    return 0;
}

/**/
int
vichangewholeline(char **args)
{
    vifirstnonblank(args);
    return vichangeeol(zlenoargs);
}

/**/
int
viyank(char **args)
{
    int oldcs = cs, c2, ret = 1;

    startvichange(1);
    if ((c2 = getvirange(0)) != -1) {
	cut(cs, c2 - cs, 0);
	ret = 0;
    }
    vichgflag = 0;
    cs = oldcs;
    return ret;
}

/**/
int
viyankeol(char **args)
{
    int x = findeol();

    startvichange(-1);
    if (x == cs)
	return 1;
    cut(cs, x - cs, 0);
    return 0;
}

/**/
int
viyankwholeline(char **args)
{
    int bol = findbol(), oldcs = cs;
    int n = zmult;

    startvichange(-1);
    if (n < 1)
	return 1;
    while(n--) {
     if (cs > ll) {
	cs = oldcs;
	return 1;
     }
     cs = findeol() + 1;
    }
    vilinerange = 1;
    cut(bol, cs - bol - 1, 0);
    cs = oldcs;
    return 0;
}

/**/
int
vireplace(char **args)
{
    startvitext(0);
    return 0;
}

/* vi-replace-chars has some oddities relating to vi-repeat-change.  In *
 * the real vi, if one does 3r at the end of a line, it feeps without   *
 * reading the argument, and won't repeat the action.  A successful rx  *
 * followed by 3. at the end of a line (or 3rx followed by . at the end *
 * of a line) will obviously feep after the ., even though it has the   *
 * argument available.  Here repeating is tied very closely to argument *
 * reading, so some trickery is needed to emulate this.  When repeating *
 * a change, we always read the argument normally, even if the count    *
 * was bad.  When recording a change for repeating, and a bad count is  *
 * given, we squash the repeat buffer to avoid repeating the partial    *
 * command; we've lost the previous change, but that can't be avoided   *
 * without a rewrite of the repeat code.                                */

/**/
int
vireplacechars(char **args)
{
    int ch, n = zmult;

    startvichange(1);
    /* check argument range */
    if (n < 1 || n + cs > findeol()) {
	if(vichgrepeat)
	    vigetkey();
	if(vichgflag) {
	    free(vichgbuf);
	    vichgbuf = NULL;
	    vichgflag = 0;
	}
	return 1;
    }
    /* get key */
    if((ch = vigetkey()) == -1) {
	vichgflag = 0;
	return 1;
    }
    /* do change */
    if (ch == '\r' || ch == '\n') {
	/* <return> handled specially */
	cs += n - 1;
	backkill(n - 1, 0);
	line[cs++] = '\n';
    } else {
	while (n--)
	    line[cs++] = ch;
	cs--;
    }
    vichgflag = 0;
    return 0;
}

/**/
int
vicmdmode(char **args)
{
    if (invicmdmode() || selectkeymap("vicmd", 0))
	return 1;
    undoing = 1;
    vichgflag = 0;
    if (cs != findbol())
	cs--;
    return 0;
}

/**/
int
viopenlinebelow(char **args)
{
    cs = findeol();
    spaceinline(1);
    line[cs++] = '\n';
    startvitext(1);
    clearlist = 1;
    return 0;
}

/**/
int
viopenlineabove(char **args)
{
    cs = findbol();
    spaceinline(1);
    line[cs] = '\n';
    startvitext(1);
    clearlist = 1;
    return 0;
}

/**/
int
vioperswapcase(char **args)
{
    int oldcs, c2, ret = 1;

    /* get the range */
    startvichange(1);
    if ((c2 = getvirange(0)) != -1) {
	oldcs = cs;
	/* swap the case of all letters within range */
	while (cs < c2) {
	    if (islower(line[cs]))
		line[cs] = tuupper(line[cs]);
	    else if (isupper(line[cs]))
		line[cs] = tulower(line[cs]);
	    cs++;
	}
	/* go back to the first line of the range */
	cs = oldcs;
	ret = 0;
#if 0
	vifirstnonblank();
#endif
    }
    vichgflag = 0;
    return ret;
}

/**/
int
virepeatchange(char **args)
{
    /* make sure we have a change to repeat */
    if (!vichgbuf || vichgflag)
	return 1;
    /* restore or update the saved count and buffer */
    if (zmod.flags & MOD_MULT) {
	lastmod.mult = zmod.mult;
	lastmod.flags |= MOD_MULT;
    }
    if (zmod.flags & MOD_VIBUF) {
	lastmod.vibuf = zmod.vibuf;
	lastmod.flags = (lastmod.flags & ~MOD_VIAPP) |
	    MOD_VIBUF | (zmod.flags & MOD_VIAPP);
    }
    /* repeat the command */
    inrepeat = 1;
    ungetkeys(vichgbuf, vichgbufptr);
    return 0;
}

/**/
int
viindent(char **args)
{
    int oldcs = cs, c2;

    /* get the range */
    startvichange(1);
    if ((c2 = getvirange(0)) == -1) {
	vichgflag = 0;
	return 1;
    }
    vichgflag = 0;
    /* must be a line range */
    if (!vilinerange) {
	cs = oldcs;
	return 1;
    }
    oldcs = cs;
    /* add a tab to the beginning of each line within range */
    while (cs < c2) {
	spaceinline(1);
	line[cs] = '\t';
	cs = findeol() + 1;
    }
    /* go back to the first line of the range */
    cs = oldcs;
    vifirstnonblank(zlenoargs);
    return 0;
}

/**/
int
viunindent(char **args)
{
    int oldcs = cs, c2;

    /* get the range */
    startvichange(1);
    if ((c2 = getvirange(0)) == -1) {
	vichgflag = 0;
	return 1;
    }
    vichgflag = 0;
    /* must be a line range */
    if (!vilinerange) {
	cs = oldcs;
	return 1;
    }
    oldcs = cs;
    /* remove a tab from the beginning of each line within range */
    while (cs < c2) {
	if (line[cs] == '\t')
	    foredel(1);
	cs = findeol() + 1;
    }
    /* go back to the first line of the range */
    cs = oldcs;
    vifirstnonblank(zlenoargs);
    return 0;
}

/**/
int
vibackwarddeletechar(char **args)
{
    int n = zmult;

    if (invicmdmode())
	startvichange(-1);
    /* handle negative argument */
    if (n < 0) {
	int ret;
	zmult = -n;
	ret = videletechar(args);
	zmult = n;
	return ret;
    }
    /* It is an error to be at the beginning of the line, or (in *
     * insert mode) to delete past the beginning of insertion.   */
    if ((!invicmdmode() && cs - n < viinsbegin) || cs == findbol()) {
	return 1;
    }
    /* Put argument into the acceptable range -- it is not an error to  *
     * specify a greater count than the number of available characters. */
    if (n > cs - findbol())
	n = cs - findbol();
    /* do the deletion */
    backkill(n, 1);
    return 0;
}

/**/
int
vikillline(char **args)
{
    if (viinsbegin > cs)
	return 1;
    backdel(cs - viinsbegin);
    return 0;
}

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

    startvichange(-1);
    if (n < 0)
	return 1;
    if (zmod.flags & MOD_VIBUF)
	buf = &vibuf[zmod.vibuf];
    if (!buf->buf)
	return 1;
    if(buf->flags & CUTBUFFER_LINE) {
	cs = findbol();
	spaceinline(buf->len + 1);
	memcpy((char *)line + cs, buf->buf, buf->len);
	line[cs + buf->len] = '\n';
	vifirstnonblank(zlenoargs);
    } else {
	while (n--) {
	    spaceinline(buf->len);
	    memcpy((char *)line + cs, buf->buf, buf->len);
	    cs += buf->len;
	}
	if (cs)
	    cs--;
    }
    return 0;
}

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

    startvichange(-1);
    if (n < 0)
	return 1;
    if (zmod.flags & MOD_VIBUF)
	buf = &vibuf[zmod.vibuf];
    if (!buf->buf)
	return 1;
    if(buf->flags & CUTBUFFER_LINE) {
	cs = findeol();
	spaceinline(buf->len + 1);
	line[cs++] = '\n';
	memcpy((char *)line + cs, buf->buf, buf->len);
	vifirstnonblank(zlenoargs);
    } else {
	if (cs != findeol())
	    cs++;
	while (n--) {
	    spaceinline(buf->len);
	    memcpy((char *)line + cs, buf->buf, buf->len);
	    cs += buf->len;
	}
	if (cs)
	    cs--;
    }
    return 0;
}

/**/
int
vijoin(char **args)
{
    int x;

    startvichange(-1);
    if ((x = findeol()) == ll)
	return 1;
    cs = x + 1;
    for (x = 1; cs != ll && iblank(line[cs]); cs++, x++);
    backdel(x);
    if (cs && iblank(line[cs-1]))
	cs--;
    else {
	spaceinline(1);
	line[cs] = ' ';
    }
    return 0;
}

/**/
int
viswapcase(char **args)
{
    int eol, n = zmult;

    startvichange(-1);
    if (n < 1)
	return 1;
    eol = findeol();
    while (cs < eol && n--) {
	if (islower(line[cs]))
	    line[cs] = tuupper(line[cs]);
	else if (isupper(line[cs]))
	    line[cs] = tulower(line[cs]);
	cs++;
    }
    if (cs && cs == eol)
	cs--;
    return 0;
}

/**/
int
vicapslockpanic(char **args)
{
    clearlist = 1;
    zbeep();
    statusline = "press a lowercase key to continue";
    statusll = strlen(statusline);
    zrefresh();
    while (!islower(getkey(0)));
    statusline = NULL;
    return 0;
}

/**/
int
visetbuffer(char **args)
{
    int ch;

    if ((zmod.flags & MOD_VIBUF) ||
	(((ch = getkey(0)) < '1' || ch > '9') &&
	 (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')))
	return 1;
    if (ch >= 'A' && ch <= 'Z')	/* needed in cut() */
	zmod.flags |= MOD_VIAPP;
    else
	zmod.flags &= ~MOD_VIAPP;
    zmod.vibuf = tulower(ch) + (idigit(ch) ? -'1' + 26 : -'a');
    zmod.flags |= MOD_VIBUF;
    prefixflag = 1;
    return 0;
}

/**/
int
vikilleol(char **args)
{
    int n = findeol() - cs;

    startvichange(-1);
    if (!n) {
	/* error -- line already empty */
	return 1;
    }
    /* delete to end of line */
    forekill(findeol() - cs, 0);
    return 0;
}

/**/
int
vipoundinsert(char **args)
{
    int oldcs = cs;

    startvichange(-1);
    vifirstnonblank(zlenoargs);
    if(line[cs] != '#') {
	spaceinline(1);
	line[cs] = '#';
	if(cs <= viinsbegin)
	    viinsbegin++;
	cs = oldcs + (cs <= oldcs);
    } else {
	foredel(1);
	if (cs < viinsbegin)
	    viinsbegin--;
	cs = oldcs - (cs < oldcs);
    }
    return 0;
}

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

    spaceinline(1);
    line[cs] = '^';
    zrefresh();
#ifndef HAS_TIO
    sob = shttyinfo.sgttyb;
    sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
    ioctl(SHTTY, TIOCSETN, &sob);
#endif
    c = getkey(0);
#ifndef HAS_TIO
    zsetterm();
#endif
    foredel(1);
    if(c < 0)
	return 1;
    else
	return selfinsert(args);
}

/* the 0 key in vi: continue a repeat count in the manner of      *
 * digit-argument if possible, otherwise do vi-beginning-of-line. */

/**/
int
vidigitorbeginningofline(char **args)
{
    if(zmod.flags & MOD_TMULT)
	return digitargument(args);
    else {
	removesuffix();
	invalidatelist();
	return vibeginningofline(args);
    }
}
