/*
 * 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"

extern int      did_ai;

/*
 * OpenForward 
 *
 * Add a blank line below the current line. 
 */

bool_t
OpenForward(can_ai)
    int             can_ai;
{
    LINE           *l;
    LPtr           *next;
    char           *s;		/* string to be moved to new line, if any */
    int             newindex = 0;	/* index of the cursor on the new
					 * line */

    /*
     * 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 (can_ai && P(P_AI)) {
	char           *p;

	/*
	 * Copy prior line, and truncate after white space 
	 */
	strcpy(l->s, Curschar->linep->s);

	for (p = l->s; *p == ' ' || *p == TAB; p++);
	*p = NUL;
	newindex = p - l->s;
	AppendToInsbuff(l->s);
	if (*s != NUL)
	    strcat(l->s, s);

	/*
	 * If we just did an auto-indent, then we didn't type anything on the
	 * prior line, and it should be truncated. 
	 */
	if (did_ai)
	    Curschar->linep->s[0] = NUL;

	did_ai = TRUE;
    } else if (*s != NUL) {
	strcpy(l->s, s);	/* copy string to new line */
    }
    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;
    }

    if (!RedrawingDisabled) {
	/*
	 * Get the cursor to the start of the line, so that 'Cursrow' gets
	 * set to the right physical line number for the stuff that
	 * follows... 
	 */
	Curschar->index = 0;
	cursupdate();

	/*
	 * If we're doing an open on the last logical line, then go ahead and
	 * scroll the screen up. Otherwise, just insert a blank line at the
	 * right place. We use calls to plines() in case the cursor is
	 * resting on a long line. 
	 */
	if (Cursrow + plines(Curschar) == (Rows - 1))
	    scrollup(1);
	else
	    s_ins(Cursrow + plines(Curschar), 1, Rows, Columns);
    }
    *Curschar = *nextline(Curschar);	/* cursor moves down */
    Curschar->index = newindex;

    if (!RedrawingDisabled) {
	/* because Botchar is now invalid */
	updateNextscreen(VALID_TO_CURSCHAR);
	cursupdate();		/* update Cursrow before insert */
    }
    CHANGED;

    return (TRUE);
}

/*
 * OpenBackward 
 *
 * Add a blank line above the current line. 
 */

bool_t
OpenBackward(can_ai)
    int             can_ai;
{
    LINE           *l;
    LINE           *prev;
    int             newindex = 0;	/* index of the cursor on the new
					 * line */

    prev = Curschar->linep->prev;

    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 */
    prev->next = l;

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

    if (can_ai && P(P_AI)) {
	char           *p;

	/*
	 * Copy current line, and truncate after white space 
	 */
	strcpy(l->s, Curschar->linep->s);

	for (p = l->s; *p == ' ' || *p == TAB; p++);
	*p = NUL;
	newindex = p - l->s;
	AppendToInsbuff(l->s);

	did_ai = TRUE;
    }
    Curschar->linep = Curschar->linep->prev;
    Curschar->index = newindex;

    if (prev == Filetop->linep) {	/* new start of file */
	Filemem->linep = l;
	renum();
    } 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;
    }

    if (!RedrawingDisabled) {
	if (LINEOF(Curschar) < LINEOF(Topchar)) {
	    Topchar->linep = Curschar->linep;
	    updateNextscreen(NOT_VALID);
	} else {
	    updateNextscreen(VALID_TO_CURSCHAR);
	}
	cursupdate();		/* update Cursrow before insert */
	if (Cursrow != 0)
	    s_ins(Cursrow, 1, Rows, Columns);	/* insert a physical line */
    }
    CHANGED;

    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;
{
    register int    col = 0;
    register char  *s;

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

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

    for (; *s != NUL; s++) {
	if (*s == TAB && !P(P_LS))
	    col += P(P_TS) - (col % P(P_TS));
	else
	    col += chars[(unsigned) (*s & 0xff)].ch_size;
    }

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

    /*
     * If 'number' mode is on, add another 8. 
     */
    if (P(P_NU))
	col += 8;

    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;
{
    register char  *p;
    register 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) {
	Curschar->index++;
	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(VALID_TO_CURSCHAR);	/* 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);

    CHANGED;
}

void
insstr(s)
    register char  *s;
{
    register char  *p;
    register char  *pend;
    register int    n = strlen(s);

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

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

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

    for (; n > 0; n--) {
	*p++ = *s++;
	Curschar->index++;
    }
    CHANGED;
}

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--;
    }
    CHANGED;
    return TRUE;
}

void
delline(nlines, can_update)
    int             nlines;
    bool_t          can_update;
{
    register LINE  *p;
    register LINE  *q;
    int             doscreen;	/* if true, update the screen */
    int             num_plines = 0;

    doscreen = can_update;
    /*
     * 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) && can_update) {
	doscreen = FALSE;
	/* flaky way to clear rest of screen */
	s_del(Cursrow, Rows - 1, Rows, Columns);
    }
    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 == Filetop->linep) {	/* first line of file so... */
	    Filemem->linep = q;	/* adjust start of file */
	    Topchar->linep = q;	/* and screen */
	}
	p->next = q;
	q->prev = p;

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

	/*
	 * Set up to delete the correct number of physical lines on the
	 * screen 
	 */
	if (doscreen)
	    num_plines += 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? */

	CHANGED;

	/* 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;
	}
    }
    /*
     * Delete the correct number of physical lines on the screen 
     */
    if (doscreen && num_plines > 0)
	s_del(Cursrow, num_plines, Rows, Columns);
}
