/*
 * textobjects.c - ZLE widgets implementing Vim style text objects
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 2014 Oliver Kiddle
 * 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 Oliver Kiddle 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 Oliver Kiddle and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Oliver Kiddle 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 Oliver Kiddle and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zle.mdh"
#include "textobjects.pro"

/* class of character: 0 is whitespace, 1 is word character, 2 is other */
static int
wordclass(ZLE_CHAR_T x)
{
    return (ZC_iblank(x) ? 0 : ((ZC_ialnum(x) || (ZWC('_') == x)) ? 1 : 2));
}

static int
blankwordclass(ZLE_CHAR_T x)
{
    return (ZC_iblank(x) ? 0 : 1);
}

/**/
int
selectword(UNUSED(char **args))
{
    int n = zmult;
    int all = IS_THINGY(bindk, selectaword) ||
	IS_THINGY(bindk, selectablankword);
    int (*viclass)(ZLE_CHAR_T) = (IS_THINGY(bindk, selectaword) ||
	    IS_THINGY(bindk, selectinword)) ? wordclass : blankwordclass;
    int sclass = viclass(zleline[zlecs]);
    int doblanks = all && sclass;

    if (!region_active || zlecs == mark || mark == -1) {
	/* search back to first character of same class as the start position
	 * also stop at the beginning of the line */
	mark = zlecs;
	while (mark) {
	    int pos = mark;
	    DECPOS(pos);
	    if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass)
		break;
	    mark = pos;
	}
	/* similarly scan forward over characters of the same class */
	while (zlecs < zlell) {
	    INCCS();
	    int pos = zlecs;
	    /* single newlines within blanks are included */
	    if (all && !sclass && pos < zlell && zleline[pos] == ZWC('\n'))
		INCPOS(pos);

	    if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass)
		break;
	}

	if (all) {
	    int nclass = viclass(zleline[zlecs]);
	    /* if either start or new position is blank advance over
	     * a new block of characters of a common type */
	    if (!nclass || !sclass) {
		while (zlecs < zlell) {
		    INCCS();
		    if (zleline[zlecs] == ZWC('\n') ||
			    viclass(zleline[zlecs]) != nclass)
			break;
		}
		if (n < 2)
		    doblanks = 0;
	    }
	}
    } else {
	/* For visual mode, advance one char so repeated
	 * invocations select subsequent words */
	if (zlecs > mark) {
	    if (zlecs < zlell)
		INCCS();
	} else if (zlecs)
	    DECCS();
	if (zlecs < mark) {
	    /* visual mode with the cursor before the mark: move cursor back */
	    while (n-- > 0) {
		int pos = zlecs;
		/* first over blanks */
		if (all && (!viclass(zleline[pos]) ||
			zleline[pos] == ZWC('\n'))) {
		    all = 0;
		    while (pos) {
			DECPOS(pos);
			if (zleline[pos] == ZWC('\n'))
			    break;
			zlecs = pos;
			if (viclass(zleline[pos]))
			    break;
		    }
		} else if (zlecs && zleline[zlecs] == ZWC('\n')) {
		    /* for in widgets pass over one newline */
		    DECPOS(pos);
		    if (zleline[pos] != ZWC('\n'))
			zlecs = pos;
		}
		pos = zlecs;
		sclass = viclass(zleline[zlecs]);
		/* now retreat over non-blanks */
		while (zleline[pos] != ZWC('\n') &&
			viclass(zleline[pos]) == sclass) {
		    zlecs = pos;
		    if (!pos) {
			zlecs = 0;
			break;
		    }
		    DECPOS(pos);
		}
		/* blanks again but only if there were none first time */
		if (all && zlecs) {
		    pos = zlecs;
		    DECPOS(pos);
		    if (!viclass(zleline[pos])) {
			while (pos) {
			    DECPOS(pos);
			    if (zleline[pos] == ZWC('\n') ||
				    viclass(zleline[pos]))
				break;
			    zlecs = pos;
			}
		    }
		}
	    }
	    return 0;
	}
	n++;
	doblanks = 0;
    }
    region_active = !!region_active; /* force to character wise */

    /* for each digit argument, advance over further block of one class */
    while (--n > 0) {
	if (zlecs < zlell && zleline[zlecs] == ZWC('\n'))
	    INCCS();
	sclass = viclass(zleline[zlecs]);
	while (zlecs < zlell) {
	    INCCS();
	    if (zleline[zlecs] == ZWC('\n') ||
		    viclass(zleline[zlecs]) != sclass)
		break;
	}
	/* for 'a' widgets, advance extra block if either consists of blanks */
	if (all) {
	    if (zlecs < zlell && zleline[zlecs] == ZWC('\n'))
		INCCS();
	    if (!sclass || !viclass(zleline[zlecs]) ) {
		sclass = viclass(zleline[zlecs]);
		if (n == 1 && !sclass)
		    doblanks = 0;
		while (zlecs < zlell) {
		    INCCS();
		    if (zleline[zlecs] == ZWC('\n') ||
			    viclass(zleline[zlecs]) != sclass)
			break;
		}
	    }
	}
    }

    /* if we didn't remove blanks at either end we remove some at the start */
    if (doblanks) {
	int pos = mark;
	while (pos) {
	    DECPOS(pos);
	    /* don't remove blanks at the start of the line, i.e indentation */
	    if (zleline[pos] == ZWC('\n'))
		break;
	    if (!ZC_iblank(zleline[pos])) {
		INCPOS(pos);
		mark = pos;
		break;
	    }
	}
    }
    /* Adjustment: vi operators don't include the cursor position, in insert
     * or emacs mode the region also doesn't but for vi visual mode it is
     * included. */
    if (!virangeflag) {
	if (!invicmdmode())
	    region_active = 1;
	else if (zlecs && zlecs > mark)
	    DECCS();
    }

    return 0;
}

/**/
int
selectargument(UNUSED(char **args))
{
    int ne = noerrs, ocs = zlemetacs;
    int owb = wb, owe= we, oadx = addedx, ona = noaliases;
    char *p;
    int ll, cs;
    char *linein;
    int wend = 0, wcur = 0;
    int n = zmult;
    int *wstarts;
    int tmpsz;

    if (n < 1 || 2*n > zlell + 1)
	return 1;

    /* if used from emacs mode enable the region */
    if (!invicmdmode()) {
	region_active = 1;
	mark = zlecs;
    }

    wstarts = (int *) zhalloc(n * sizeof(int));
    memset(wstarts, 0, n * sizeof(int));

    addedx = 0;
    noerrs = 1;
    zcontext_save();
    lexflags = LEXFLAGS_ACTIVE;
    linein = zlegetline(&ll, &cs);
    zlemetall = ll;
    zlemetacs = cs;

    if (!isfirstln && chline) {
       p = (char *) zhalloc(hptr - chline + zlemetall + 2);
       memcpy(p, chline, hptr - chline);
       memcpy(p + (hptr - chline), linein, ll);
       p[(hptr - chline) + ll] = '\0';
       inpush(p, 0, NULL);
       zlemetacs += hptr - chline;
    } else {
       p = (char *) zhalloc(ll + 1);
       memcpy(p, linein, ll);
       p[ll] = '\0';
       inpush(p, 0, NULL);
    }
    if (zlemetacs)
       zlemetacs--;
    strinbeg(0);
    noaliases = 1;
    do {
       wstarts[wcur++] = wend;
       wcur %= n;
       ctxtlex();
       if (tok == ENDINPUT || tok == LEXERR)
           break;
       wend = zlemetall - inbufct;
    } while (tok != ENDINPUT && tok != LEXERR && wend <= zlemetacs);
    noaliases = ona;
    strinend();
    inpop();
    errflag &= ~ERRFLAG_ERROR;
    noerrs = ne;
    zcontext_restore();
    zlemetacs = ocs;
    wb = owb;
    we = owe;
    addedx = oadx;

    /* convert offsets for mark and zlecs back to ZLE internal format */
    linein[wend] = '\0'; /* a bit of a hack to get two offsets */
    free(stringaszleline(linein, wstarts[wcur], &zlecs, &tmpsz, &mark));
    free(linein);

    if (IS_THINGY(bindk, selectinshellword)) {
	ZLE_CHAR_T *match = ZWS("`\'\"");
	ZLE_CHAR_T *lmatch = ZWS("\'({"), *rmatch = ZWS("\')}");
	ZLE_CHAR_T *ematch = match, *found;
	int start, end = zlecs;
	/* for 'in' widget, don't include initial blanks ... */
	while (mark < zlecs && ZC_iblank(zleline[mark]))
	    INCPOS(mark);
	/* ... or a matching pair of quotes */
	start = mark;
	if (zleline[start] == ZWC('$')) {
	    match = lmatch;
	    ematch = rmatch;
	    INCPOS(start);
	}
	found = ZS_strchr(match, zleline[start]);
	if (found) {
	    DECPOS(end);
	    if (zleline[end] == ematch[found-match]) {
		zlecs = end;
		INCPOS(start);
		mark = start;
	    }
	}
    }

    /* Adjustment: vi operators don't include the cursor position */
    if (!virangeflag && invicmdmode())
       DECCS();

    return 0;
}
