/*
 * STEVIE - Simply Try this Editor for VI Enthusiasts
 *
 * Code Contributions By : Tim Thompson           twitch!tjt
 *                         Tony Andrews           onecom!wldrdg!tony 
 *                         G. R. (Fred) Walter    watmath!watcgl!grwalter 
 */

#include "stevie.h"

static          bool_t
openfwd(), openbwd();

extern int      did_ai;

/*
 * opencmd 
 *
 * Add a blank line above or below the current line. 
 */

bool_t
opencmd(dir, can_ai)
    int             dir;
    int             can_ai;	/* if true, consider auto-indent */
{
    if (dir == FORWARD)
	return (openfwd(can_ai));
    else
	return (openbwd(can_ai));
}

static          bool_t
openfwd(can_ai)
    int             can_ai;
{
    LINE           *l;
    LPTR           *next;
    char           *s;		/* string to be moved to new line, if any */

    /*
     * If we're in insert mode, we need to move the remainder of the current
     * line onto the new line. Otherwise the new line is left blank. 
     */
    if (State == INSERT)
	s = &Curschar->linep->s[Curschar->index];
    else
	s = "";

    if ((next = nextline(Curschar)) == NULL)	/* open on last line */
	next = Fileend;

    /*
     * By asking for as much space as the prior line had we make sure that
     * we'll have enough space for any auto-indenting. 
     */
    if ((l = newline(strlen(Curschar->linep->s) + SLOP)) == NULL) {
	emsg("out of memory");
	beep();
	sleep(2);
	return (FALSE);
    }
    if (*s != NUL)
	strcpy(l->s, s);	/* copy string to new line */
#ifdef AUTO_INDENT
    else if (can_ai && P(P_AI)) {
	/*
	 * Auto-indent removed due to buggy implementation... 
	 */
	did_ai = TRUE;
    }
#endif

    if (State == INSERT)	/* truncate current line at cursor */
	*s = NUL;

    Curschar->linep->next = l;	/* link neighbors to new line */
    next->linep->prev = l;

    l->prev = Curschar->linep;	/* link new line to neighbors */
    l->next = next->linep;

    if (next == Fileend)	/* new line at end */
	l->num = Curschar->linep->num + LINEINC;

    else if ((l->prev->num) + 1 == l->next->num)	/* no gap, renumber */
	renum();

    else {			/* stick it in the middle */
	long            lnum;
	lnum = (l->prev->num + l->next->num) / 2;
	l->num = lnum;
    }

    *Curschar = *nextline(Curschar);	/* cursor moves down */
    Curschar->index = 0;

    s_ins(Cursrow + 1, 1);	/* insert a physical line */
    updateNextscreen();		/* because Botchar is now invalid */
    cursupdate();		/* update Cursrow before insert */

    return (TRUE);
}

static          bool_t
openbwd(can_ai)
    int             can_ai;
{
    LINE           *l;
    LPTR           *prev;

    prev = prevline(Curschar);

    if ((l = newline(strlen(Curschar->linep->s) + SLOP)) == NULL) {
	emsg("out of memory");
	beep();
	sleep(2);
	return (FALSE);
    }
    Curschar->linep->prev = l;	/* link neighbors to new line */
    if (prev != NULL)
	prev->linep->next = l;

    l->next = Curschar->linep;	/* link new line to neighbors */
    if (prev != NULL)
	l->prev = prev->linep;

#ifdef AUTO_INDENT
    if (can_ai && P(P_AI)) {
	/*
	 * Auto-indent removed due to buggy implementation... 
	 */
	did_ai = TRUE;
    }
#endif

    *Curschar = *prevline(Curschar);	/* cursor moves up */
    Curschar->index = 0;

    if (prev == NULL)		/* new start of file */
	Filemem->linep = l;

    renum();			/* keep it simple - we don't do this often */

    cursupdate();		/* update Cursrow before insert */
    if (Cursrow != 0)
	s_ins(Cursrow, 1);	/* insert a physical line */
    updateNextscreen();

    return (TRUE);
}

int
cntllines(pbegin, pend)
    LPTR           *pbegin, *pend;
{
    register LINE  *lp;
    register int    lnum = 1;

    for (lp = pbegin->linep; lp != pend->linep; lp = lp->next)
	lnum++;

    return (lnum);
}

/*
 * plines(p) - return the number of physical screen lines taken by line 'p' 
 */
int
plines(p)
    LPTR           *p;
{
    int             col;
    char           *s;

    if (p == NULL) {
	fprintf(stderr, "plines(p) : p == NULL ????");
	return (0);
    }
    s = p->linep->s;

    if (*s == NUL)		/* empty line */
	return 1;

    /*
     * If list mode is on, then the '$' at the end of the line takes up one
     * extra column. 
     */
    col = P(P_LS) ? 1 : 0;

    for (; *s != NUL; s++) {
	if (*s == TAB && !P(P_LS))
	    col += P(P_TS) - (col % P(P_TS));
	else
	    col += chars[*s].ch_size;
    }
    return ((col + (Columns - 1)) / Columns);
}

void
fileinfo()
{
    long            l1, l2;
    char            buf[MAX_COLUMNS + 1];

    if (bufempty()) {
	msg("Buffer Empty");
	return;
    }
    l1 = cntllines(Filemem, Curschar);
    l2 = cntllines(Filemem, Fileend) - 1;
    sprintf(buf, "\"%s\"%s line %ld of %ld -- %ld %% --",
	    (Filename != NULL) ? Filename : "No File",
	    Changed ? " [Modified]" : "",
	    l1, l2, (l1 * 100) / l2);
    msg(buf);
}

/*
 * gotoline(n) - return a pointer to line 'n' 
 *
 * Returns a pointer to the last line of the file if n is zero, or beyond the
 * end of the file. 
 */
LPTR           *
gotoline(n)
    int             n;
{
    static LPTR     l;

    l.index = 0;

    if (n == 0)
	l = *prevline(Fileend);
    else {
	LPTR           *p;

	for (l = *Filemem; --n > 0; l = *p)
	    if ((p = nextline(&l)) == NULL)
		break;
    }
    return &l;
}

void
inschar(c)
    char            c;
{
    char           *p;
    char           *pend;

    /* make room for the new char. */
    if (!canincrease(1))
	return;

    p = &Curschar->linep->s[strlen(Curschar->linep->s) + 1];
    pend = &Curschar->linep->s[Curschar->index];

    for (; p > pend; p--)
	*p = *(p - 1);

    *p = c;

    if (RedrawingDisabled) {
	inc(Curschar);
	return;
    }
    /*
     * If we're in insert mode and showmatch mode is set, then check for
     * right parens and braces. If there isn't a match, then beep. If there
     * is a match AND it's on the screen, then flash to it briefly. If it
     * isn't on the screen, don't do anything. 
     */
    if (P(P_SM) && State == INSERT && (c == ')' || c == '}' || c == ']')) {
	LPTR           *lpos, csave;

	if ((lpos = showmatch()) == NULL)	/* no match, so beep */
	    beep();
	else if (LINEOF(lpos) >= LINEOF(Topchar)) {
	    updateNextscreen();	/* show the new char first */
	    updateRealscreen();
	    csave = *Curschar;
	    *Curschar = *lpos;	/* move to matching char */
	    cursupdate();
	    windgoto(Cursrow, Curscol);
	    delay();		/* brief pause */
	    *Curschar = csave;	/* restore cursor position */
	    cursupdate();
	}
    }
    inc(Curschar);
}

void
insstr(s)
    char           *s;
{
    char           *p, *endp;
    int             k, n = strlen(s);

    /* Move everything in the file over to make */
    /* room for the new string. */
    if (!canincrease(n))
	return;

    endp = &Curschar->linep->s[Curschar->index];
    p = Curschar->linep->s + strlen(Curschar->linep->s) + 1 + n;

    for (; p > endp; p--)
	*p = *(p - n);

    p = &Curschar->linep->s[Curschar->index];
    for (k = 0; k < n; k++) {
	*p++ = *s++;
	inc(Curschar);
    }
}

bool_t
delchar(fixpos, undo)
    bool_t          fixpos;	/* if TRUE fix the cursor position when done */
    bool_t          undo;	/* if TRUE put char deleted into Undo buffer */
{
    int             i;

    /* Check for degenerate case; there's nothing in the file. */
    if (bufempty())
	return FALSE;

    if (lineempty(Curschar))	/* can't do anything */
	return FALSE;

    if (undo)
	AppendToUndobuff(mkstr(gchar(Curschar)));

    /* Delete the char. at Curschar by shifting everything in the line down. */
    for (i = Curschar->index + 1; i < Curschar->linep->size; i++)
	Curschar->linep->s[i - 1] = Curschar->linep->s[i];

    /*
     * If we just took off the last character of a non-blank line, we don't
     * want to end up positioned at the newline. 
     */
    if (fixpos) {
	if (gchar(Curschar) == NUL && Curschar->index > 0 && State != INSERT)
	    Curschar->index--;
    }
    return TRUE;
}

void
delline(nlines)
{
    LINE           *p, *q;
    int             doscreen = TRUE;	/* if true, update the screen */

    /*
     * There's no point in keeping the screen updated if we're deleting more
     * than a screen's worth of lines. 
     */
    if (nlines > (Rows - 1)) {
	doscreen = FALSE;
	s_del(Cursrow, Rows - 1);	/* flaky way to clear rest of screen */
    }
    while (nlines-- > 0) {

	if (bufempty())		/* nothing to delete */
	    break;

	if (buf1line()) {	/* just clear the line */
	    Curschar->linep->s[0] = NUL;
	    Curschar->index = 0;
	    break;
	}
	p = Curschar->linep->prev;
	q = Curschar->linep->next;

	if (p == NULL) {	/* first line of file so... */
	    Filemem->linep = q;	/* adjust start of file */
	    Topchar->linep = q;	/* and screen */
	} else
	    p->next = q;
	q->prev = p;

	clrmark(Curschar->linep);	/* clear marks for the line */

	/*
	 * Delete the correct number of physical lines on the screen 
	 */
	if (doscreen)
	    s_del(Cursrow, plines(Curschar));

	/*
	 * If deleting the top line on the screen, adjust Topchar 
	 */
	if (Topchar->linep == Curschar->linep)
	    Topchar->linep = q;

	free(Curschar->linep->s);
	free((char *) (Curschar->linep));

	Curschar->linep = q;
	Curschar->index = 0;	/* is this right? */

	/* If we delete the last line in the file, back up */
	if (Curschar->linep == Fileend->linep) {
	    Curschar->linep = Curschar->linep->prev;
	    /* and don't try to delete any more lines */
	    break;
	}
    }
}
