/*
 * The functions in this file are a general set of line management utilities.
 * They are the only routines that touch the text. They also touch the buffer
 * and window structures, to make sure that the necessary updating gets done.
 * There are routines in this file that handle the kill buffer too. It isn't
 * here for any good reason.
 *
 * Note that this code only updates the dot and mark values in the window list.
 * Since all the code acts on the current window, the buffer that we are
 * editing must be being displayed, which means that "b_nwnd" is non zero,
 * which means that the dot and mark values in the buffer headers are nonsense.
 */

#include	<stdio.h>
#include	"estruct.h"
#include	"eproto.h"
#include	"edef.h"
#include	"elang.h"

#define	BSIZE(a)	(a + NBLOCK - 1) & (~(NBLOCK - 1))

static long last_size = -1L;	/* last # of bytes yanked */

/*
 * This routine allocates a block of memory large enough to hold a LINE
 * containing "used" characters. Return a pointer to the new block, or
 * NULL if there isn't any memory left. Print a message in the message
 * line if no space.
 */

LINE *PASCAL NEAR lalloc(used)

register int used;

{
	register LINE	*lp;

	if ((lp = (LINE *)malloc(sizeof(LINE)+used)) == NULL) {
		mlabort(TEXT94);
/*                      "%%Out of memory" */
		return(NULL);
	}
	lp->l_size = used;
	lp->l_used = used;
#if	WINDOW_MSWIN
	{
		static int o = 0;
		if (--o < 0) {
			longop(TRUE);
			o = 10;     /* to lower overhead, only 10% calls to longop */
		}
	}
#endif
	return(lp);
}

/*
 * Delete line "lp". Fix all of the links that might point at it (they are
 * moved to offset 0 of the next line. Unlink the line from whatever buffer it
 * might be in. Release the memory. The buffers are updated too; the magic
 * conditions described in the above comments don't hold here.
 */
PASCAL NEAR lfree(lp)
register LINE	*lp;
{
	register BUFFER *bp;
	SCREEN *scrp;		/* screen to fix pointers in */
	register WINDOW *wp;
	register int cmark;		/* current mark */

	/* in all screens.... */
	scrp = first_screen;
	while (scrp) {
		wp = scrp->s_first_window;
		while (wp != NULL) {
			if (wp->w_linep == lp)
				wp->w_linep = lforw(lp);
			if (wp->w_dotp	== lp) {
				wp->w_dotp  = lforw(lp);
				wp->w_doto  = 0;
			}
			for (cmark = 0; cmark < NMARKS; cmark++) {
				if (wp->w_markp[cmark] == lp) {
					wp->w_markp[cmark] = lforw(lp);
					wp->w_marko[cmark] = 0;
				}
			}
			wp = wp->w_wndp;
		}

		/* next screen! */
		scrp = scrp->s_next_screen;
	}

	bp = bheadp;
	while (bp != NULL) {
		if (bp->b_nwnd == 0) {
			if (bp->b_dotp	== lp) {
				bp->b_dotp = lforw(lp);
				bp->b_doto = 0;
			}
			for (cmark = 0; cmark < NMARKS; cmark++) {
				if (bp->b_markp[cmark] == lp) {
					bp->b_markp[cmark] = lforw(lp);
					bp->b_marko[cmark] = 0;
				}
			}
		}
		bp = bp->b_bufp;
	}
	lp->l_bp->l_fp = lp->l_fp;
	lp->l_fp->l_bp = lp->l_bp;
	free((char *) lp);
#if	WINDOW_MSWIN
	{
		static int o = 0;
		if (--o < 0) {
			longop(TRUE);
			o = 10;     /* to lower overhead, only 10% calls to longop */
		}
	}
#endif
}

/*
 * This routine gets called when a character is changed in place in the current
 * buffer. It updates all of the required flags in the buffer and window
 * system. The flag used is passed as an argument; if the buffer is being
 * displayed in more than 1 window we change EDIT t HARD. Set MODE if the
 * mode line needs to be updated (the "*" has to be set).
 */
PASCAL NEAR lchange(flag)
register int	flag;
{
	register WINDOW *wp;
	SCREEN *scrp;		/* screen to fix pointers in */

	if (curbp->b_nwnd != 1) 		/* Ensure hard. 	*/
		flag = WFHARD;
	if ((curbp->b_flag&BFCHG) == 0) {	/* First change, so	*/
		flag |= WFMODE; 		/* update mode lines.	*/
		curbp->b_flag |= BFCHG;
	}

	/* in all screens.... */
	scrp = first_screen;
	while (scrp) {
		/* make sure all the needed windows get this flag */
		wp = scrp->s_first_window;
		while (wp != NULL) {
			if (wp->w_bufp == curbp)
				wp->w_flag |= flag;
			wp = wp->w_wndp;
		}

		/* next screen! */
		scrp = scrp->s_next_screen;
	}
}

PASCAL NEAR insspace(f, n)	/* insert spaces forward into text */

int f, n;	/* default flag and numeric argument */

{
	register int	status;

	if ((status = linsert(n, ' ')) == TRUE)
		status = backchar(f, n);
	return status;
}

/*
 * linstr -- Insert a string at the current point
 */

int PASCAL NEAR linstr(instr)
char	*instr;
{
	register int status;

	status = TRUE;
	if (instr != NULL)
		while (*instr) {
			status = ((*instr == '\r') ? lnewline(): linsert(1, *instr));

			/* Insertion error? */
			if (status != TRUE) {
				mlwrite(TEXT168);
/*                                      "%%Can not insert string" */
				break;
			}
			instr++;
		}
	return(status);
}

/*
 * Insert "n" copies of the character "c" at the current location of dot. In
 * the easy case all that happens is the text is stored in the line. In the
 * hard case, the line has to be reallocated. When the window list is updated,
 * take special care; I screwed it up once. You always update dot in the
 * current window. You update mark, and a dot in another window, if it is
 * greater than the place where you did the insert. Return TRUE if all is
 * well, and FALSE on errors.
 */

#if	PROTO
PASCAL NEAR linsert(int n, char c)
#else
PASCAL NEAR linsert(n, c)

int	n;
char	c;
#endif

{
	register char	*cp1;
	register char	*cp2;
	register LINE	*lp1;
	register LINE	*lp2;
	register LINE	*lp3;
	register int	doto;
	register int	i;
	register WINDOW *wp;
	SCREEN *scrp;		/* screen to fix pointers in */
	int cmark;		/* current mark */

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/

	/* a zero insert means do nothing! */
	if (n == 0)
		return(TRUE);

	/* Negative numbers of inserted characters are right out! */
	if (n < 1)
		return(FALSE);

	/* mark the current window's buffer as changed */
	lchange(WFEDIT);

	lp1 = curwp->w_dotp;			/* Current line 	*/
	if (lp1 == curbp->b_linep) {		/* At the end: special	*/
		if (curwp->w_doto != 0) {
			mlwrite(TEXT170);
/*                              "bug: linsert" */
			return(FALSE);
		}
		if ((lp2=lalloc(BSIZE(n))) == NULL)	/* Allocate new line	*/
			return(FALSE);
		lp2->l_used = n;
		lp3 = lp1->l_bp;		/* Previous line	*/
		lp3->l_fp = lp2;		/* Link in		*/
		lp2->l_fp = lp1;
		lp1->l_bp = lp2;
		lp2->l_bp = lp3;
		for (i=0; i<n; ++i)
			lp2->l_text[i] = c;
		curwp->w_dotp = lp2;
		curwp->w_doto = n;
		return(TRUE);
	}
	doto = curwp->w_doto;			/* Save for later.	*/
	if (lp1->l_used+n > lp1->l_size) {	/* Hard: reallocate	*/
		if ((lp2=lalloc(BSIZE(lp1->l_used+n))) == NULL)
			return(FALSE);
		lp2->l_used = lp1->l_used+n;
		cp1 = &lp1->l_text[0];
		cp2 = &lp2->l_text[0];
		while (cp1 != &lp1->l_text[doto])
			*cp2++ = *cp1++;
		cp2 += n;
		while (cp1 != &lp1->l_text[lp1->l_used])
			*cp2++ = *cp1++;
		lp1->l_bp->l_fp = lp2;
		lp2->l_fp = lp1->l_fp;
		lp1->l_fp->l_bp = lp2;
		lp2->l_bp = lp1->l_bp;
		free((char *) lp1);
	} else {				/* Easy: in place	*/
		lp2 = lp1;			/* Pretend new line	*/
		lp2->l_used += n;
		cp2 = &lp1->l_text[lp1->l_used];
		cp1 = cp2-n;
		while (cp1 != &lp1->l_text[doto])
			*--cp2 = *--cp1;
	}
	for (i=0; i<n; ++i)			/* Add the characters	*/
		lp2->l_text[doto+i] = c;
	/* in all screens.... */
	scrp = first_screen;
	while (scrp) {

		wp = scrp->s_first_window;
		while (wp != NULL) {
			if (wp->w_linep == lp1)
				wp->w_linep = lp2;
			if (wp->w_dotp == lp1) {
				wp->w_dotp = lp2;
				if (wp==curwp || wp->w_doto>doto)
					wp->w_doto += n;
			}
			for (cmark = 0; cmark < NMARKS; cmark++) {
				if (wp->w_markp[cmark] == lp1) {
					wp->w_markp[cmark] = lp2;
					if (wp->w_marko[cmark] > doto)
						wp->w_marko[cmark] += n;
				}
			}
			wp = wp->w_wndp;
		}

		/* next screen! */
		scrp = scrp->s_next_screen;
	}
	return(TRUE);
}

/*
 * Overwrite a character into the current line at the current position
 *
 */

#if	PROTO
PASCAL NEAR lowrite(char c)
#else
PASCAL NEAR lowrite(c)

char c;		/* character to overwrite on current position */
#endif

{
	if (curwp->w_doto < curwp->w_dotp->l_used &&
		(lgetc(curwp->w_dotp, curwp->w_doto) != '\t' ||
		 (curwp->w_doto) % 8 == 7))
			ldelete(1L, FALSE);
	return(linsert(1, c));
}

/*
 * lover -- Overwrite a string at the current point
 */

int PASCAL NEAR lover(ostr)

char	*ostr;

{
	register int status = TRUE;

	if (ostr != NULL)
		while (*ostr && status == TRUE) {
			status = ((*ostr == '\r') ? lnewline(): lowrite(*ostr));

			/* Insertion error? */
			if (status != TRUE) {
				mlwrite(TEXT172);
/*                                      "%%Out of memory while overwriting" */
				break;
			}
			ostr++;
		}
	return(status);
}

/*
 * Insert a newline into the buffer at the current location of dot in the
 * current window. The funny ass-backwards way it does things is not a botch;
 * it just makes the last line in the file not a special case. Return TRUE if
 * everything works out and FALSE on error (memory allocation failure). The
 * update of dot and mark is a bit easier then in the above case, because the
 * split forces more updating.
 */
int PASCAL NEAR lnewline()
{
	register char	*cp1;
	register char	*cp2;
	register LINE	*lp1;
	register LINE	*lp2;
	register int	doto;
	register WINDOW *wp;
	SCREEN *scrp;		/* screen to fix pointers in */
	int cmark;		/* current mark */

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
	lchange(WFHARD);
	lp1  = curwp->w_dotp;			/* Get the address and	*/
	doto = curwp->w_doto;			/* offset of "."	*/
	if ((lp2=lalloc(doto)) == NULL) 	/* New first half line	*/
		return(FALSE);
	cp1 = &lp1->l_text[0];			/* Shuffle text around	*/
	cp2 = &lp2->l_text[0];
	while (cp1 != &lp1->l_text[doto])
		*cp2++ = *cp1++;
	cp2 = &lp1->l_text[0];
	while (cp1 != &lp1->l_text[lp1->l_used])
		*cp2++ = *cp1++;
	lp1->l_used -= doto;
	lp2->l_bp = lp1->l_bp;
	lp1->l_bp = lp2;
	lp2->l_bp->l_fp = lp2;
	lp2->l_fp = lp1;

	/* in all screens.... */
	scrp = first_screen;
	while (scrp) {

		wp = scrp->s_first_window;
		while (wp != NULL) {
			if (wp->w_linep == lp1)
				wp->w_linep = lp2;
			if (wp->w_dotp == lp1) {
				if (wp->w_doto < doto)
					wp->w_dotp = lp2;
				else
					wp->w_doto -= doto;
			}
			for (cmark = 0; cmark < NMARKS; cmark++) {
				if (wp->w_markp[cmark] == lp1) {
					if (wp->w_marko[cmark] < doto)
						wp->w_markp[cmark] = lp2;
					else
						wp->w_marko[cmark] -= doto;
				}
			}
			wp = wp->w_wndp;
		}

		/* next screen! */
		scrp = scrp->s_next_screen;
	}
	return(TRUE);
}

/*

LDELETE:

	This function deletes "n" bytes, starting at dot. Positive n
deletes forward, negative n deletes backwords. It understands how to
deal with end of lines, and with two byte characters. It returns TRUE
if all of the characters were deleted, and FALSE if they were not
(because dot ran into the buffer end). The "kflag" is TRUE if the text
should be put in the kill buffer.

*/

PASCAL NEAR ldelete(n, kflag)

long n; 	/* # of chars to delete */
int kflag;	/* put killed text in kill buffer flag */

{
	register char	*cp1;
	register char	*cp2;
	register LINE	*dotp;
	register int	doto;
	register int	chunk;
	register WINDOW *wp;
	int cmark;		/* current mark */

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/

	/* going Forward? */
	if (n >= 0) {

		while (n > 0) {
#if	DBCS
			/* never start forward on a 2 byte char */
			if (curwp->w_doto > 0 && is2byte(curwp->w_dotp->l_text,
			    &curwp->w_dotp->l_text[curwp->w_doto - 1])) {
				curwp->w_doto--;
				n++;
			}
#endif
			/* record the current point */
			dotp = curwp->w_dotp;
			doto = curwp->w_doto;
	
			/* can't delete past the end of the buffer */
			if (dotp == curbp->b_linep)
				return(FALSE);
	
			/* find out how many chars to delete on this line */
			chunk = dotp->l_used-doto;	/* Size of chunk.	*/
			if (chunk > n)
				chunk = n;
	
			/* if at the end of a line, merge with the next */
			if (chunk == 0) {
	
				/* flag that we are making a hard change */
				lchange(WFHARD);
				if (ldelnewline() == FALSE ||
				    (kflag != FALSE &&
				     kinsert(FORWARD, '\r')==FALSE))
					return(FALSE);
				--n;
				continue;
			}
	
			/* flag the fact we are changing the current line */
			lchange(WFEDIT);
	
			/* find the limits of the kill */
			cp1 = &dotp->l_text[doto];
			cp2 = cp1 + chunk;
#if	DBCS
			/* never leave half a character */
			if (is2byte(dotp->l_text, cp2 - 1)) {
				++chunk;
				++cp2;
				++n;
			}
#endif

			/* save the text to the kill buffer */
			if (kflag != FALSE) {
				while (cp1 != cp2) {
					if (kinsert(FORWARD, *cp1) == FALSE)
						return(FALSE);
					++cp1;
				}
				cp1 = &dotp->l_text[doto];
			}
	
			/* copy what is left of the line upward */
			while (cp2 != &dotp->l_text[dotp->l_used])
				*cp1++ = *cp2++;
			dotp->l_used -= chunk;
	
			/* fix any other windows with the same text displayed */
			wp = wheadp;
			while (wp != NULL) {
	
				/* reset the dot if needed */
				if (wp->w_dotp==dotp && wp->w_doto>=doto) {
					wp->w_doto -= chunk;
					if (wp->w_doto < doto)
						wp->w_doto = doto;
				}
	
				/* reset any marks if needed */
				for (cmark = 0; cmark < NMARKS; cmark++) {
					if (wp->w_markp[cmark]==dotp && wp->w_marko[cmark]>=doto) {
						wp->w_marko[cmark] -= chunk;
						if (wp->w_marko[cmark] < doto)
							wp->w_marko[cmark] = doto;
					}
				}
	
				/* onward to the next window */
				wp = wp->w_wndp;
			}
	
			/* indicate we have deleted chunk characters */
			n -= chunk;
		}
	} else {
		while (n < 0) {
#if	DBCS
			/* never start backwards on the
			   1st of a 2 byte character */
			if (curwp->w_doto > 1 && is2byte(curwp->w_dotp->l_text,
			    &curwp->w_dotp->l_text[curwp->w_doto-1])) {
				curwp->w_doto++;
				n--;
			}
#endif
			/* record the current point */
			dotp = curwp->w_dotp;
			doto = curwp->w_doto;
	
			/* can't delete past the beginning of the buffer */
			if (dotp == lforw(curbp->b_linep) && (doto == 0))
				return(FALSE);
	
			/* find out how many chars to delete on this line */
			chunk = doto;		/* Size of chunk.	*/
			if (chunk > -n)
				chunk = -n;
	
			/* if at the beginning of a line, merge with the last */
			if (chunk == 0) {
	
				/* flag that we are making a hard change */
				lchange(WFHARD);
				backchar(TRUE, 1);
				if (ldelnewline() == FALSE ||
				    (kflag != FALSE &&
				     kinsert(REVERSE, '\r')==FALSE))
					return(FALSE);
				++n;
				continue;
			}
	
			/* flag the fact we are changing the current line */
			lchange(WFEDIT);
	
			/* find the limits of the kill */
			cp1 = &dotp->l_text[doto];
			cp2 = cp1 - chunk;
#if	DBCS
			if (is2byte(dotp->l_text, cp2 - 1)) {
				++chunk;
				--cp2;
				++n;
			}
#endif
	
			/* save the text to the kill buffer */
			if (kflag != FALSE) {
				while (cp1 > cp2) {
					if (kinsert(REVERSE, *(--cp1)) == FALSE)
						return(FALSE);
				}
				cp1 = &dotp->l_text[doto];
			}
	
			/* copy what is left of the line downward */
			while (cp1 != &dotp->l_text[dotp->l_used])
				*cp2++ = *cp1++;
			dotp->l_used -= chunk;
			curwp->w_doto -= chunk;
	
			/* fix any other windows with the same text displayed */
			wp = wheadp;
			while (wp != NULL) {
	
				/* reset the dot if needed */
				if (wp->w_dotp==dotp && wp->w_doto>=doto) {
					wp->w_doto -= chunk;
					if (wp->w_doto < doto)
						wp->w_doto = doto;
				}
	
				/* reset any marks if needed */
				for (cmark = 0; cmark < NMARKS; cmark++) {
					if (wp->w_markp[cmark]==dotp && wp->w_marko[cmark]>=doto) {
						wp->w_marko[cmark] -= chunk;
						if (wp->w_marko[cmark] < doto)
							wp->w_marko[cmark] = doto;
					}
				}
	
				/* onward to the next window */
				wp = wp->w_wndp;
			}
	
			/* indicate we have deleted chunk characters */
			n += chunk;
		}
	}
	return(TRUE);
}

/* getctext:	grab and return a string with the text of
		the current line
*/

char *PASCAL NEAR getctext()

{
	register LINE *lp;	/* line to copy */
	register int size;	/* length of line to return */
	register char *sp;	/* string pointer into line */
	register char *dp;	/* string pointer into returned line */
	char rline[NSTRING];	/* line to return */

	/* find the contents of the current line and its length */
	lp = curwp->w_dotp;
	sp = ltext(lp);
	size = lused(lp);
	if (size >= NSTRING)
		size = NSTRING - 1;

	/* copy it across */
	dp = rline;
	while (size--)
		*dp++ = *sp++;
	*dp = 0;
	return(rline);
}

/* putctext:	replace the current line with the passed in text	*/

PASCAL NEAR putctext(iline)

char *iline;	/* contents of new line */

{
	register int status;

	/* delete the current line */
	curwp->w_doto = 0;	/* starting at the beginning of the line */
	if ((status = killtext(TRUE, 1)) != TRUE)
		return(status);

	/* insert the new line */
	if ((status = linstr(iline)) != TRUE)
		return(status);
	status = lnewline();
	backline(TRUE, 1);
	return(status);
}

/*
 * Delete a newline. Join the current line with the next line. If the next line
 * is the magic header line always return TRUE; merging the last line with the
 * header line can be thought of as always being a successful operation, even
 * if nothing is done, and this makes the kill buffer work "right". Easy cases
 * can be done by shuffling data around. Hard cases require that lines be moved
 * about in memory. Return FALSE on error and TRUE if all looks ok. Called by
 * "ldelete" only.
 */
int PASCAL NEAR ldelnewline()
{
	register char	*cp1;
	register char	*cp2;
	register LINE	*lp1;
	register LINE	*lp2;
	register LINE	*lp3;
	register WINDOW *wp;
	SCREEN *scrp;		/* screen to fix pointers in */
	int cmark;		/* current mark */

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
	lp1 = curwp->w_dotp;
	lp2 = lp1->l_fp;
	if (lp2 == curbp->b_linep) {		/* At the buffer end.	*/
		if (lp1->l_used == 0)		/* Blank line.		*/
			lfree(lp1);
		return(TRUE);
	}
	if (lp2->l_used <= lp1->l_size-lp1->l_used) {
		cp1 = &lp1->l_text[lp1->l_used];
		cp2 = &lp2->l_text[0];
		while (cp2 != &lp2->l_text[lp2->l_used])
		*cp1++ = *cp2++;

		/* in all screens.... */
		scrp = first_screen;
		while (scrp) {

			wp = scrp->s_first_window;
			while (wp != NULL) {
				if (wp->w_linep == lp2)
					wp->w_linep = lp1;
				if (wp->w_dotp == lp2) {
					wp->w_dotp  = lp1;
					wp->w_doto += lp1->l_used;
				}
				for (cmark = 0; cmark < NMARKS; cmark++) {
					if (wp->w_markp[cmark] == lp2) {
						wp->w_markp[cmark]  = lp1;
						wp->w_marko[cmark] += lp1->l_used;
					}
				}
				wp = wp->w_wndp;
			}

			/* next screen! */
			scrp = scrp->s_next_screen;
		}

		lp1->l_used += lp2->l_used;
		lp1->l_fp = lp2->l_fp;
		lp2->l_fp->l_bp = lp1;
		free((char *) lp2);
		return(TRUE);
	}
	if ((lp3=lalloc(lp1->l_used+lp2->l_used)) == NULL)
		return(FALSE);
	cp1 = &lp1->l_text[0];
	cp2 = &lp3->l_text[0];
	while (cp1 != &lp1->l_text[lp1->l_used])
		*cp2++ = *cp1++;
	cp1 = &lp2->l_text[0];
	while (cp1 != &lp2->l_text[lp2->l_used])
		*cp2++ = *cp1++;
	lp1->l_bp->l_fp = lp3;
	lp3->l_fp = lp2->l_fp;
	lp2->l_fp->l_bp = lp3;
	lp3->l_bp = lp1->l_bp;

	/* in all screens.... */
	scrp = first_screen;
	while (scrp) {

		wp = scrp->s_first_window;
		while (wp != NULL) {
			if (wp->w_linep==lp1 || wp->w_linep==lp2)
				wp->w_linep = lp3;
			if (wp->w_dotp == lp1)
				wp->w_dotp  = lp3;
			else if (wp->w_dotp == lp2) {
				wp->w_dotp  = lp3;
				wp->w_doto += lp1->l_used;
			}
			for (cmark = 0; cmark < NMARKS; cmark++) {
				if (wp->w_markp[cmark] == lp1)
					wp->w_markp[cmark]  = lp3;
				else if (wp->w_markp[cmark] == lp2) {
					wp->w_markp[cmark]  = lp3;
					wp->w_marko[cmark] += lp1->l_used;
				}
			}
			wp = wp->w_wndp;
		}

		/* next screen! */
		scrp = scrp->s_next_screen;
	}

	free((char *) lp1);
	free((char *) lp2);
	return(TRUE);
}

/*	Add a new line to the end of the indicated buffer.
	return FALSE if we run out of memory
	note that this works on non-displayed buffers as well!
*/

#if	PROTO
int PASCAL NEAR addline(BUFFER *bp, char *text)
#else
int PASCAL NEAR addline(bp, text)

BUFFER *bp;	/* buffer to add text to */
char *text;	/* line to add */
#endif
{
	register LINE	*lp;
	register int	i;
	register int	ntext;

	/* allocate the memory to hold the line */
	ntext = strlen(text);
	if ((lp=lalloc(ntext)) == NULL)
		return(FALSE);

	/* copy the text into the new line */
	for (i=0; i<ntext; ++i)
		lputc(lp, i, text[i]);

	/* add the new line to the end of the buffer */
	bp->b_linep->l_bp->l_fp = lp;
	lp->l_bp = bp->b_linep->l_bp;
	bp->b_linep->l_bp = lp;
	lp->l_fp = bp->b_linep;

	/* if the point was at the end of the buffer,
	   move it to the beginning of the new line */
	if (bp->b_dotp == bp->b_linep)
		bp->b_dotp = lp;
	return(TRUE);
}

/*
 * Delete all of the text saved in the kill buffer. Called by commands when a
 * new kill context is being created. The kill buffer array is released, just
 * in case the buffer has grown to immense size. No errors.
 */

VOID PASCAL NEAR kdelete()

{
	KILL *kp;	/* ptr to scan kill buffer chunk list */

	if (kbufh[kill_index] != NULL) {

		/* first, delete all the chunks */
		kbufp[kill_index] = kbufh[kill_index];
		while (kbufp[kill_index] != NULL) {
			kp = kbufp[kill_index]->d_next;
			free((char *)kbufp[kill_index]);
			kbufp[kill_index] = kp;
#if	WINDOW_MSWIN
			{
				static int o = 0;
				if (--o < 0) {
					longop(TRUE);
					o = 10;     /* to lower overhead,
						only 10% calls to longop */
				}
			}
#endif
		}

		/* and reset all the kill buffer pointers */
		kbufh[kill_index] = kbufp[kill_index] = NULL;
		kskip[kill_index] = 0;
		kused[kill_index] = KBLOCK; 	        
	}
}

/*	next_kill:	advance to the next position in the kill ring,
			pushing the current kill buffer and clearing
			what will be the new kill buffer
*/

VOID PASCAL NEAR next_kill()

{
	/* advance to the next kill ring entry */
	kill_index++;
	if (kill_index == NRING)
		kill_index = 0;

	/* and clear it, so it is ready for use */
	kdelete();
}

/*
 * Insert a character to the kill buffer, allocating new chunks as needed.
 * Return TRUE if all is well, and FALSE on errors.
 */

#if	PROTO
int PASCAL NEAR kinsert(int direct, char c)
#else
int PASCAL NEAR kinsert(direct, c)

int direct;	/* direction (FORWARD/REVERSE) to insert characters */
char c;		/* character to insert in the kill buffer */
#endif

{
	KILL *nchunk;	/* ptr to newly malloced chunk */

	if (direct == FORWARD) {

		/* check to see if we need a new chunk */
		if (kused[kill_index] >= KBLOCK) {
			if ((nchunk = (KILL *)malloc(sizeof(KILL))) == NULL) {
				mlwrite(TEXT94);
/*					"%%Out of memory" */
				return(FALSE);
			}
			if (kbufh[kill_index] == NULL)	/* set head ptr if first time */
				kbufh[kill_index] = nchunk;
			if (kbufp[kill_index] != NULL)	/* point the current to this new one */
				kbufp[kill_index]->d_next = nchunk;
			kbufp[kill_index] = nchunk;
			kbufp[kill_index]->d_next = NULL;
			kused[kill_index] = 0;
#if	WINDOW_MSWIN
			{
				static int o = 0;
				if (--o < 0) {
					longop(TRUE);
					o = 10;     /* to lower overhead,
						only 10% calls to longop */
				}
			}
#endif
		}
	
		/* and now insert the character */
		kbufp[kill_index]->d_chunk[kused[kill_index]++] = c;
	} else {
		/* REVERSE */
		/* check to see if we need a new chunk */
		if (kskip[kill_index] == 0) {
			if ((nchunk = (KILL *)malloc(sizeof(KILL))) == NULL) {
				mlwrite(TEXT94);
/*					"%%Out of memory" */
				return(FALSE);
			}
			if (kbufh[kill_index] == NULL) {	/* set head ptr if first time */
				kbufh[kill_index] = nchunk;
				kbufp[kill_index] = nchunk;
				kskip[kill_index] = KBLOCK;
				kused[kill_index] = KBLOCK;
				nchunk->d_next = (KILL *)NULL;
			} else {
				nchunk->d_next = kbufh[kill_index];
				kbufh[kill_index] = nchunk;
				kskip[kill_index] = KBLOCK;
			}
#if	WINDOW_MSWIN
			{
				static int o = 0;
				if (--o < 0) {
					longop(TRUE);
					o = 10;     /* to lower overhead,
						only 10% calls to longop */
				}
			}
#endif
		}
	
		/* and now insert the character */
		kbufh[kill_index]->d_chunk[--kskip[kill_index]] = c;
	}
	return(TRUE);
}

/*
 * Yank text back from the kill buffer. This is really easy. All of the work
 * is done by the standard insert routines. All you do is run the loop, and
 * check for errors. Bound to "C-Y".
 */

#define	Char_insert(a)	(a == '\r' ? lnewline() : linsert(1, a))

int PASCAL NEAR yank(f, n)

int f,n;	/* prefix flag and argument */

{
	register int counter;	/* counter into kill buffer data */
	register char *sp;	/* pointer into string to insert */
	short int curoff;	/* storage for line before yanking */
	LINE *curline;
	KILL *kptr;		/* pointer into kill buffer */

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
	if (n < 0)
		return(FALSE);

	/* make sure there is something to yank */
	if (kbufh[kill_index] == NULL) {
		last_size = 0L;
		return(TRUE);		/* not an error, just nothing */
	}

	/*
	 * Save the local pointers to hold global ".".
	 */
	if (yankflag) {
		/* Find the *previous* line, since the line we are on
		 * may disappear due to re-allocation.  This works even
		 * if we are on the first line of the file.
		 */
		curline = lback(curwp->w_dotp);
		curoff = curwp->w_doto;
	}

	/* for each time.... */
	while (n--) {
		last_size = 0L;
		if (kskip[kill_index] > 0) {
			kptr = kbufh[kill_index];
			sp = &(kptr->d_chunk[kskip[kill_index]]);
			counter = kskip[kill_index];
			while (counter++ < KBLOCK) {
				Char_insert(*sp);
				last_size++;
				++sp;
			}
			kptr = kptr->d_next;
		} else {
			kptr = kbufh[kill_index];
		}
	
		if (kptr != (KILL *)NULL) {
			while (kptr != kbufp[kill_index]) {
				sp = kptr->d_chunk;
				for(counter = 0; counter < KBLOCK; counter++) {
					Char_insert(*sp);
					last_size++;
					++sp;
				}
				kptr = kptr->d_next;
			}
			counter = kused[kill_index];
			sp = kptr->d_chunk;
			while (counter--) {
				Char_insert(*sp);
				last_size++;
				++sp;
			}
		}
	}

	/* If requested, set global "." back to the
	 * beginning of the yanked text.
	 */
	if (yankflag) {
		curwp->w_dotp = lforw(curline);
		curwp->w_doto = curoff;
	}
	thisflag |= CFYANK;
	return(TRUE);
}

int PASCAL NEAR cycle_ring(f, n)

int f,n;	/* prefix flag and argument */

{
	register int orig_index;	/* original kill_index */

	/* if there is an argument, cycle the kill index */
	if (f) {
		while (n) {
			orig_index = kill_index;
			do {
				kill_index--;
				if (kill_index < 0)
					kill_index = NRING - 1;
			} while ((orig_index != kill_index) &&
				(kbufh[kill_index] == (KILL *)NULL));
			n--;
		}
	}
	return TRUE;
}

int PASCAL NEAR yank_pop(f, n)

int f,n;	/* prefix flag and argument */

{
	/* defaulted non first call will cycle by 1 */
	if ((lastflag & CFYANK) && (f == FALSE)) {
		f = TRUE;
		n = 1;
	}

	/* cycle the kill ring appropriately */
	cycle_ring(f, n);

	/* if not the first consectutive time, delete the last yank */
	if ((lastflag & CFYANK))
		ldelete(-last_size, FALSE);

	/* and insert the current kill buffer */
	return(yank(FALSE, 1));
}

int PASCAL NEAR clear_ring(f, n)

int f,n;	/* prefix flag and argument */

{
	register int index;

	for (index = 0; index < NRING; index++)
		next_kill();
	mlwrite(TEXT228);
/*		"[Kill ring cleared]" */
	return(TRUE);
}

#if	0
dispkill()

{
	KILL *kptr;
	int index;
	char *sp;
	int counter;

	if (kbufh[kill_index] == (KILL *)NULL) {
		printf("<EMPTY>\n");
		return;
	}

	index = 1;
	if (kskip[kill_index] > 0) {
		kptr = kbufh[kill_index];
		printf("kskip[kill_index] = %d\nBLOCK %d <", kskip[kill_index], index++);
		sp = &(kptr->d_chunk[kskip[kill_index]]);
		counter = kskip[kill_index];
		while (counter++ < KBLOCK) {
			putchar(*sp++);
		}
		printf(">\n");
		kptr = kptr->d_next;
	} else {
		kptr = kbufh[kill_index];
	}

	if (kptr != (KILL *)NULL) {
		while (kptr != kbufp[kill_index]) {
			printf("BLOCK %d <%255s>\n", index++, kptr->d_chunk);
			kptr = kptr->d_next;
		}
		printf("BLOCK %d <", index++);
		counter = kused[kill_index];
		sp = kptr->d_chunk;
		while (counter--) {
			putchar(*sp++);
		}
		printf(">\nkused[kill_index] = %d\n", kused[kill_index]);
	}

}
#endif
