/*
 * $Id: zle_vi.c,v 1.10 1995/05/09 04:21:12 coleman Exp coleman $
 *
 * zle_vi.c - vi-specific functions
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 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 its documentation 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.
 *
 */

#define ZLE
#include "zsh.h"

static int lastmult, lastbuf, lastgotmult, lastgotbuf, inrepeat;

static void startvichange _((int im));

static void
startvichange(int im)
{
    if (im != -1) {
	insmode = im;
	vichgflag = 1;
    }
    if (inrepeat) {
	mult = lastmult;
	vibufspec = lastbuf;
	gotmult = lastgotmult;
	gotvibufspec = lastgotbuf;
	inrepeat = vichgflag = 0;
    } else {
	lastmult = mult;
	lastbuf = vibufspec;
	lastgotmult = gotmult;
	lastgotbuf = gotvibufspec;
	if (vichgbuf)
	    free(vichgbuf);
	vichgbuf = (char *)zalloc(vichgbufsz = 16);
	vichgbuf[0] = c;
	vichgbufptr = 1;
    }
}

static void startvitext _((int im));

static void
startvitext(int im)
{
    startvichange(im);
    bindtab = mainbindtab;
    undoing = 0;
    viinsbegin = cs;
}

/**/
int
vigetkey(void)
{
    int cmd;

    if((c = getkey(0)) == EOF)
	return 0;
    cmd = bindtab[c];

    if (cmd < 0 || cmd == z_sendbreak) {
	feep();
	return 0;
    }
    if (cmd == z_quotedinsert) {
	if ((c = getkey(0)) == EOF)
	    return 0;
	return c;
    } else if (cmd == z_vicmdmode)
	return 0;
    return c;
}

/**/
int
getvirange(int wf)
{
    int k2, t0, startline, endline, nofeep;
    int mult1, gotmult1;

    vilinerange = 0;
    startline = findbol();
    endline = findeol();
    /* get arguments for the command, and then the command key */
    mult1 = mult;
    gotmult1 = gotmult;
    mult = 1;
    gotmult = 0;
    lastcmd &= ~(ZLE_NEGARG | ZLE_DIGIT);
    while(1) {
	if ((k2 = getkeycmd()) < 0 || k2 == z_sendbreak) {
	    feep();
	    return -1;
	}
	if (!(zlecmds[k2].flags & ZLE_ARG))
	    break;
	(*zlecmds[k2].func) ();
	lastcmd = zlecmds[k2].flags;
    }

    /* double counts, such as in 3d4j, get multiplied */
    if (gotmult1) {
	mult *= mult1;
	gotmult = 1;
    }
    /* can't handle an empty file */
    if (!ll) {
	feep();
	return -1;
    }

    /* This bit handles repeated command keys, such as dd.  A number of lines
    is taken as the range.  The current line is included.  If the argument is
    positive, the lines go downward, otherwise vice versa.  The argument gives
    the number of lines. */
    if (k2 == bindk) {
	vilinerange = 1;
	if (!mult) {
	    feep();
	    return -1;
	}
	t0 = cs;
	if (mult > 0) {
	    while(mult-- && cs <= ll)
		cs = findeol() + 1;
	    if (mult != -1) {
		cs = t0;
		feep();
		return -1;
	    }
	    t0 = cs - 1;
	    cs = startline;
	    return t0;
	} else {
	    while(mult++ && cs >= 0)
		cs = findbol() - 1;
	    if (mult != 1) {
		cs = t0;
		feep();
		return -1;
	    }
	    cs++;
	    return endline;
	}
    }

    /* Not a movement?!  No, you can't do yd. */
    if (!(zlecmds[k2].flags & ZLE_MOVEMENT)) {
	feep();
	return -1;
    }

    /* 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 forwand 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. */
    t0 = cs;
    virangeflag = 1;
    wordflag = wf;
    nofeep = opts[(int)NOBEEP];
    opts[(int)NOBEEP] = OPT_SET;
    (*zlecmds[k2].func) ();
    wordflag = virangeflag = 0;
    opts[(int)NOBEEP] = nofeep;
    if (cs == t0) {
	/* An error occured -- couldn't move.  The movement command didn't
	feep, because we set NO_BEEP for the duration of the command. */
	feep();
	return -1;
    }

    /* get the range the right way round */
    if (cs > t0) {
	k2 = cs;
	cs = t0;
	t0 = k2;
    }

    /* Was it a line-oriented move?  In this case, entire lines are taken.  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 (zlecmds[k2].flags & ZLE_LINEMOVE) {
	vilinerange = 1;
	k2 = findbol();
	cs = t0;
	t0 = findeol();
	cs = k2;
    }
    return t0;
}

/**/
void
viaddnext(void)
{
    if (cs != findeol())
	cs++;
    startvitext(1);
}

/**/
void
viaddeol(void)
{
    cs = findeol();
    startvitext(1);
}

/**/
void
viinsert(void)
{
    startvitext(1);
}

/**/
void
viinsertbol(void)
{
    vifirstnonblank();
    startvitext(1);
}

/**/
void
videlete(void)
{
    int c2;

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

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

/**/
void
vichange(void)
{
    int c2;

    startvichange(1);
    if ((c2 = getvirange(1)) != -1) {
	forekill(c2 - cs, 0);
	bindtab = mainbindtab;
	undoing = 0;
    }
    vichgflag = vilinerange = 0;
}

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

/**/
void
vichangeeol(void)
{
    forekill(findeol() - cs, 0);
    startvitext(1);
}

/**/
void
vichangewholeline(void)
{
    vifirstnonblank();
    vichangeeol();
}

/**/
void
viyank(void)
{
    int oldcs = cs, c2;

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

/**/
void
viyankeol(void)
{
    int x = findeol();

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

/**/
void
viyankwholeline(void)
{
    int bol = findbol(), oldcs = cs;

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

/**/
void
vireplace(void)
{
    startvitext(0);
}

/**/
void
vireplacechars(void)
{
    int ch;

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

/**/
void
vicmdmode(void)
{
    if (bindtab == altbindtab)
	feep();
    else {
	bindtab = altbindtab;
	undoing = 1;
	vichgflag = 0;
    }
}

/**/
void
viopenlinebelow(void)
{
    cs = findeol();
    spaceinline(1);
    line[cs++] = '\n';
    startvitext(1);
}

/**/
void
viopenlineabove(void)
{
    cs = findbol();
    spaceinline(1);
    line[cs] = '\n';
    startvitext(1);
}

/**/
void
vioperswapcase(void)
{
    int oldcs, c2;

    /* 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;
	vifirstnonblank();
    }
    vichgflag = vilinerange = 0;
}

/**/
void
virepeatchange(void)
{
    /* make sure we have a change to repeat */
    if (!vichgbuf || vichgflag) {
	feep();
	return;
    }
    /* restore or update the saved count and buffer */
    if (gotmult) {
	lastmult = mult;
	lastgotmult = 1;
    }
    if (gotvibufspec) {
	lastbuf = vibufspec;
	lastgotbuf = 1;
    }
    /* repeat the command */
    inrepeat = 1;
    ungetkeys(vichgbuf, vichgbufptr);
}

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

    /* get the range */
    startvichange(1);
    if ((c2 = getvirange(0)) == -1) {
	vichgflag = vilinerange = 0;
	return;
    }
    vichgflag = 0;
    /* must be a line range */
    if (!vilinerange) {
	feep();
	cs = oldcs;
	return;
    }
    vilinerange = 0;
    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();
}

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

    /* get the range */
    startvichange(1);
    if ((c2 = getvirange(0)) == -1) {
	vichgflag = vilinerange = 0;
	return;
    }
    vichgflag = 0;
    /* must be a line range */
    if (!vilinerange) {
	feep();
	cs = oldcs;
	return;
    }
    vilinerange = 0;
    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();
}

/**/
void
vibackwarddeletechar(void)
{
    if (bindtab == altbindtab)
	startvichange(-1);
    /* handle negative argument */
    if (mult < 0) {
	mult = -mult;
	videletechar();
	return;
    }
    /* it is an error to be at the beginning of the line, or (in insert mode)
    to delete past the beginning of insertion */
    if ((bindtab != altbindtab && cs - mult < viinsbegin) || cs == findbol()) {
	feep();
	return;
    }
    /* put argument into the acceptable range -- it is not an error to specify
    a greater count than the number of available characters */
    if (mult > cs - findbol())
	mult = cs - findbol();
    /* do the deletion */
    backkill(mult, 1);
}

/**/
void
vikillline(void)
{
    if (viinsbegin > cs) {
	feep();
	return;
    }
    backdel(cs - viinsbegin);
}

/**/
void
viputbefore(void)
{
    int cc;
    char *buf = cutbuf;

    startvichange(-1);
    if (!cutbuf) {
	feep();
	return;
    }
    if (mult < 0)
	return;
    if (vibufspec) {
	if (!(buf = vibuf[vibufspec])) {
	    feep();
	    return;
	}
	vilinerange = vilinebuf[vibufspec];
    } else
	vilinerange = linecutbuf;
    if (vilinerange) {
	cs = findbol();
	cc = strlen(buf);
	spaceinline(cc + 1);
	strncpy((char *)line + cs, buf, cc);
	line[cs + cc] = '\n';
	vifirstnonblank();
    } else {
	while (mult--) {
	    cc = strlen(buf);
	    spaceinline(cc);
	    strncpy((char *)line + cs, buf, cc);
	    cs += cc;
	}
	if (cs)
	    cs--;
    }
}

/**/
void
viputafter(void)
{
    int cc;
    char *buf = cutbuf;

    startvichange(-1);
    if (!cutbuf) {
	feep();
	return;
    }
    if (mult < 0)
	return;
    if (vibufspec) {
	if (!(buf = vibuf[vibufspec])) {
	    feep();
	    return;
	}
	vilinerange = vilinebuf[vibufspec];
    } else
	vilinerange = linecutbuf;
    if (vilinerange) {
	cs = findeol();
	cc = strlen(buf);
	spaceinline(cc + 1);
	line[cs++] = '\n';
	strncpy((char *)line + cs, buf, cc);
	vifirstnonblank();
    } else {
	if (cs != findeol())
	    cs++;
	while (mult--) {
	    cc = strlen(buf);
	    spaceinline(cc);
	    strncpy((char *)line + cs, buf, cc);
	    cs += cc;
	}
	if (cs)
	    cs--;
    }

}

/**/
void
vijoin(void)
{
    int x;

    startvichange(-1);
    if ((x = findeol()) == ll) {
	feep();
	return;
    }
    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] = ' ';
    }
}

/**/
void
viswapcase(void)
{
    int eol;

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

/**/
void
vicapslockpanic(void)
{
    feep();
    statusline = "press a lowercase key to continue";
    refresh();
    while (!islower(getkey(0)));
    statusline = NULL;
}

int owrite;

/**/
void
visetbuffer(void)
{
    int ch;

    ch = getkey(0);
    if (!isalnum(ch)) {
	feep();
	return;
    }
    if (ch >= 'A' && ch <= 'Z')	/* needed in cut() */
	owrite = 0;
    else
	owrite = 1;
    vibufspec = tolower(ch) + (idigit(ch)) ? -'1' + 26 : -'a';
}

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

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

/**/
void
vipoundinsert(void)
{
    int oldcs = cs;

    startvichange(-1);
    cs = findbol();
    if(line[cs] != '#') {
	spaceinline(1);
	line[cs] = '#';
	viinsbegin++;
	cs = oldcs + 1;
    } else {
	foredel(1);
	if (viinsbegin > cs)
	    viinsbegin--;
	if (oldcs != cs)
	    cs = oldcs - 1;
    }
}
