# include "ed.h"
# include "buffer.h"

/*
 * This file defines the basic editing operations.
 */

/*
 * NAME:	editbuf->new()
 * DESCRIPTION:	create a new edit buffer
 */
editbuf *eb_new(tmpfile)
char *tmpfile;
{
    register editbuf *eb;

    eb = ALLOC(editbuf, 1);
    eb->lb = lb_new((linebuf *) NULL, tmpfile);
    eb->buffer = (block) 0;
    eb->lines = 0;

    return eb;
}

/*
 * NAME:	editbuf->del()
 * DESCRIPTION:	delete an edit buffer
 */
void eb_del(eb)
editbuf *eb;
{
    lb_del(eb->lb);
    FREE(eb);
}

/*
 * NAME:	editbuf->clear()
 * DESCRIPTION:	reinitialize an edit buffer
 */
void eb_clear(eb)
register editbuf *eb;
{
    lb_new(eb->lb, (char *) NULL);
    eb->buffer = (block) 0;
    eb->lines = 0;
}

/*
 * NAME:	editbuf->add()
 * DESCRIPTION:	add a new block of lines to the edit buffer after a given line.
 *		If this line is 0 the block is inserted before the other lines
 *		in the edit buffer.
 */
void eb_add(eb, ln, getline, context)
register editbuf *eb;
register Int ln;
char *(*getline) P((char*)), *context;
{
    register block b;

    b = bk_new(eb->lb, getline, context);
    if (b != (block) 0) {
	Int size;

	size = eb->lines + bk_size(eb->lb, b);
	if (size < 0) {
	    error("Too many lines");
	}

	if (ln == 0) {
	    if (eb->lines == 0) {
		eb->buffer = b;
	    } else {
		eb->buffer = bk_cat(eb->lb, b, eb->buffer);
	    }
	} else if (ln == eb->lines) {
	    eb->buffer = bk_cat(eb->lb, eb->buffer, b);
	} else {
	    block head, tail;

	    bk_split(eb->lb, eb->buffer, ln, &head, &tail);
	    eb->buffer = bk_cat(eb->lb, bk_cat(eb->lb, head, b), tail);
	}

	eb->lines = size;
    }
}

/*
 * NAME:	editbuf->delete()
 * DESCRIPTION:	delete a subrange of lines in the edit buffer
 */
block eb_delete(eb, first, last)
register editbuf *eb;
register Int first, last;
{
    block head, mid, tail;
    Int size;

    size = last - first + 1;

    if (last < eb->lines) {
	bk_split(eb->lb, eb->buffer, last, &mid, &tail);
	if (first > 1) {
	    bk_split(eb->lb, mid, first - 1, &head, &mid);
	    eb->buffer = bk_cat(eb->lb, head, tail);
	} else {
	    eb->buffer = tail;
	}
    } else {
	mid = eb->buffer;
	if (first > 1) {
	    bk_split(eb->lb, mid, first - 1, &head, &mid);
	    eb->buffer = head;
	} else {
	    eb->buffer = (block) 0;
	}
    }
    eb->lines -= size;

    return mid;
}

/*
 * NAME:	editbuf->change()
 * DESCRIPTION:	change a subrange of lines in the edit buffer
 */
void eb_change(eb, first, last, b)
register editbuf *eb;
register Int first, last;
register block b;
{
    Int size;
    block head, tail;

    size = eb->lines - (last - first + 1);
    if (b != (block) 0) {
	size += bk_size(eb->lb, b);
	if (size < 0) {
	    error("Too many lines");
	}
    }

    if (last < eb->lines) {
	if (first > 1) {
	    bk_split(eb->lb, eb->buffer, first - 1, &head, (block *) NULL);
	    bk_split(eb->lb, eb->buffer, last, (block *) NULL, &tail);
	    if (b != (block) 0) {
		b = bk_cat(eb->lb, bk_cat(eb->lb, head, b), tail);
	    } else {
		b = bk_cat(eb->lb, head, tail);
	    }
	} else {
	    bk_split(eb->lb, eb->buffer, last, (block *) NULL, &tail);
	    if (b != (block) 0) {
		b = bk_cat(eb->lb, b, tail);
	    } else {
		b = tail;
	    }
	}
    } else if (first > 1) {
	bk_split(eb->lb, eb->buffer, first - 1, &head, (block *) NULL);
	if (b != (block) 0) {
	    b = bk_cat(eb->lb, head, b);
	} else {
	    b = head;
	}
    }
    eb->buffer = b;
    eb->lines = size;
}

/*
 * NAME:	editbuf->yank()
 * DESCRIPTION:	return a subrange block of the edit buffer
 */
block eb_yank(eb, first, last)
register editbuf *eb;
register Int first, last;
{
    block head, mid, tail;

    if (last < eb->lines) {
	bk_split(eb->lb, eb->buffer, last, &mid, &tail);
    } else {
	mid = eb->buffer;
    }
    if (first > 1) {
	bk_split(eb->lb, mid, first - 1, &head, &mid);
    }

    return mid;
}

/*
 * NAME:	editbuf->put()
 * DESCRIPTION:	put a block after a line in the edit buffer. The block is
 *		supplied immediately.
 */
void eb_put(eb, ln, b)
register editbuf *eb;
register Int ln;
register block b;
{
    Int size;

    size = eb->lines + bk_size(eb->lb, b);
    if (size < 0) {
	error("Too many lines");
    }

    if (ln == 0) {
	if (eb->lines == 0) {
	    eb->buffer = b;
	} else {
	    eb->buffer = bk_cat(eb->lb, b, eb->buffer);
	}
    } else if (ln == eb->lines) {
	eb->buffer = bk_cat(eb->lb, eb->buffer, b);
    } else {
	block head, tail;

	bk_split(eb->lb, eb->buffer, ln, &head, &tail);
	eb->buffer = bk_cat(eb->lb, bk_cat(eb->lb, head, b), tail);
    }

    eb->lines = size;
}

/*
 * NAME:	editbuf->range()
 * DESCRIPTION:	output a subrange of the edit buffer, without first making
 *		a subrange block for it
 */
void eb_range(eb, first, last, putline, context, reverse)
register editbuf *eb;
Int first, last;
void (*putline) P((char*, char*));
char *context;
int reverse;
{
    bk_put(eb->lb, eb->buffer, first - 1, last - first + 1, putline, context,
	   reverse);
}

/*
 * Routines to add lines to a block in pieces. It would be nice if bk_new could
 * be used for this, but this is only possible if the editor functions as a
 * stand-alone program.
 * Lines are stored in a local buffer, which is flushed into a block when full.
 */

/*
 * NAME:	add_line()
 * DESCRIPTION:	return the next line from the lines buffer
 */
static char *add_line(ptr)
char *ptr;
{
    register editbuf *eb;

    eb = (editbuf *) ptr;
    if (eb->szlines > 0) {
	char *p;
	int len;

	len = strlen(p = eb->llines) + 1;
	eb->llines += len;
	eb->szlines -= len;
	return p;
    }
    return (char *) NULL;
}

/*
 * NAME:	flush_line()
 * DESCRIPTION:	flush the lines buffer into a block
 */
static void flush_line(eb)
register editbuf *eb;
{
    block b;

    eb->llines = eb->llbuf;
    b = bk_new(eb->lb, add_line, (char *) eb);
    if (eb->flines == (block) 0) {
	eb->flines = b;
    } else {
	eb->flines = bk_cat(eb->lb, eb->flines, b);
    }
}

/*
 * NAME:	editbuf->startblock()
 * DESCRIPTION:	start a block of lines
 */
void eb_startblock(eb)
register editbuf *eb;
{
    eb->flines = (block) 0;
    eb->szlines = 0;
}

/*
 * NAME:	editbuf->addblock()
 * DESCRIPTION:	add a line to the current block of lines
 */
void eb_addblock(eb, text)
register editbuf *eb;
register char *text;
{
    register int len;

    len = strlen(text) + 1;

    if (eb->szlines + len >= sizeof(eb->llines)) {
	flush_line(eb);
    }
    memcpy(eb->llbuf + eb->szlines, text, len);
    eb->szlines += len;
}

/*
 * NAME:	editbuf->endblock()
 * DESCRIPTION:	finish the current block
 */
void eb_endblock(eb)
register editbuf *eb;
{
    if (eb->szlines > 0) {
	flush_line(eb);
    }
}
