/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/

/* Routines to perform all kinds of deletion.  */

#include "jove.h"

RCS("$Id: delete.c,v 14.31.0.12 1994/06/22 07:58:19 tom Exp tom $")

#include "ctype.h"

/* Assumes that either line1 or line2 is actual the current line, so it can
   put its result into linebuf. */

private void 	patchup __(( Line *_(line1), int _(char1),
			     Line *_(line2), int _(char2) ));
private void
patchup(line1, char1, line2, char2)
register Line	*line1,
		*line2;
register int	char1,
		char2;
{
	if (line1 == line2 && char1 == char2)
		return;		/* Nothing to do, really... */

	DotTo(line1, char1);
	modify();		/* line1 (!) */
	linecopy(linebuf, char1, lcontents(line2) + char2);

	if (line1 != line2) {
		ChkWindows(line1, line2);
		/* The following is a redisplay optimization. */
		if (char1 == 0 && char2 == 0)
			line1->l_dline = line2->l_dline;
	}
	DFixMarks(line1, char1, line2, char2);
	makedirty(line1);	/* curline */
}

/* Create a new Line list that contains a single Line. */

private Line *newlist __(( const char *contents ));
private Line *
newlist(contents)
const char	*contents;
{
	register Line	*nl = nbufline();

	nl->l_prev = NULL;
	nl->l_next = NULL;
	SavLine(nl, contents);

	return nl;
}

/* Unlinks the region by unlinking the lines in the middle,
   and patching things up.  The unlinked lines are still in
   order. [TRH formerly reg_delete] -- expects that region
   is in order. */

private Line *	reg_unlink __(( Line *_(line1), int _(char1),
				Line *_(line2), int _(char2) ));
private Line *
reg_unlink(l1, char1, l2, char2)
Line	*l1,
	*l2;
{
	register Line	*line1 = l1,
			*line2 = l2,
			*retline;

	ltobuf(line1, genbuf);
	if (line1 == line2)
		genbuf[char2] = '\0';			/* Shorten this line */
	patchup(line1, char1, line2, char2);
	retline = newlist(&genbuf[char1]);

	if (line1 != line2) {
		retline->l_next = line1->l_next;
		ltobuf(line2, genbuf);
		genbuf[char2] = '\0';			/* Shorten this line */
		SavLine(line2, genbuf);

		if (line1->l_next = line2->l_next)
			line1->l_next->l_prev = line1;
		else
			curbuf->b_last = line1;
		line2->l_next = NULL;
	}

	return retline;
}

/* Deletes a region by patching things up, and REMOVING the lines in
   the middle (these are irretrievably lost). Region does not have to
   be in order. [TRH - replaces lremove]  */

void
reg_delete(l1, char1, l2, char2)
Line	*l1,
	*l2;
{
	register Line	*next,
			*line1,
			*line2;

	fixorder(&l1, &char1, &l2, &char2);
	line1 = l1;
	line2 = l2;
	patchup(line1, char1, line2, char2);

	if (line1 == line2)
		return;
	next = line1->l_next;
	if (line1->l_next = line2->l_next)
		line1->l_next->l_prev = line1;
	else
		curbuf->b_last = line1;
	lfreereg(next, line2);	/* Put region at end of free line list. */
}

/* This kills a region between point, and line1/char1 and puts it on
   the kill-ring.  If the last command was one of the kill commands,
   the region is appended (prepended if backwards) to the last entry.  */

Line	*killbuf[NUMKILLS];
Line	**killptr = killbuf;

private Line **adv_killptr __(( void ));
private Line **
adv_killptr()
{
	register Line	**kp = killptr;

	*kp++;
	if (kp == &killbuf[NUMKILLS])
		kp -= NUMKILLS;
	killptr = kp;
	lfreelist(*kp);
	*kp = NULL;

	return kp;
}

void
reg_kill(line2, char2, dot_moved)
Line	*line2;
{
	int		forwards;
	int		readonly;
	Bufpos		bp1;
	register Bufpos *p1 = &bp1;

	DOTsave(p1);

	if (p1->p_line == line2 && p1->p_char == char2)
		complain((char *) 0);

	/* Kill commands are handled specially in read-only buffers:
	   just copy the text to the kill ring, don't remove it from the
	   buffer.  And move the cursor if necessary.
	   [Note that kill commands should NOT be marked as EDIT
	    commands or else we would never get here!] */
	if ((readonly = MinorMode(View))) {
		if (!dot_moved)
			DotTo(line2, char2);
	}

	forwards = fixorder(&p1->p_line, &p1->p_char, &line2, &char2);
	/* This is a kludge!  But it possible for commands that don't
	   know which direction they are deleting in (e.g., delete
	   previous word could have been called with a negative argument
	   in which case, it wouldn't know that it really deleted
	   forward. [TRH - and reg_unlink expects region to be in order] */

	if (last_cmd != KILLCMD) {
		register Line **kp = adv_killptr();

		if (!readonly) {
			*kp = reg_unlink(p1->p_line, p1->p_char, line2, char2);
			this_cmd = KILLCMD;
			return;
		}
		*kp = newlist(NullStr);
		/* Fall through... */
	}
	/*else*/ {
		register Line	*kline = *killptr;
		register int	kchar = 0;

		if (!dot_moved)
			forwards ^= YES;

		if (forwards) {		/* append at end of kill region */
			kline = lastline(kline);
			kchar = length(kline);
		}
		DoYank(p1->p_line, p1->p_char, line2, char2,
		       kline, kchar, (Buffer *) 0);
		if (!readonly)
			reg_delete(p1->p_line, p1->p_char, line2, char2);
	}
	this_cmd = KILLCMD;
}

DEF_CMD( "kill-region", DelReg, EDIT )
{
	register Mark	*mp = CurMark();

	reg_kill(mp->m_line, mp->m_char, NO);
}

/* should region be saved automatically on region-changing commands? */

DEF_INT( "auto-save-region", SaveRegion, V_BOOL ) ZERO;

/* Save a region.  A pretend kill. */

DEF_CMD( "copy-region", CopyRegion, NO )
{
	register Line	*nl;
	Bufpos		r[2];

	CurRegion(r);

	if (r[0].p_line == r[1].p_line && r[0].p_char == r[1].p_char)
		complain((char *) 0);

	(*adv_killptr()) = nl = newlist(NullStr);

	DoYank(r[0].p_line, r[0].p_char, r[1].p_line, r[1].p_char,
	       nl, 0, (Buffer *) 0);

	s_mess("[Region saved]");
}

/* Delete character forward */

DEF_CMD( "delete-next-character", DelNChar, NO/*EDIT--potential kill command*/ )
{
	register Buffer	*cb = curbuf;
	register Line	*line1 = cb->b_dot;
	register int	char1 = cb->b_char;

	if (exp_p == YES) {	/* ...even if 1 character! */
		ForChar();	/* skip */
		reg_kill(line1, char1, YES);
	}
	else {
		if (BufMinorMode(cb, View))	/* inline check for speed. */
			view_mode_check();

		ForChar();	/* skip */
		if (line1 == cb->b_dot && char1 == cb->b_char)
			complain((char *) 0);
		reg_delete(line1, char1, cb->b_dot, cb->b_char);
	}
}

/* Delete character backward */

DEF_CMD( "delete-previous-character", DelPChar, NO/*EDIT--potential kill command*/|NEGATE )
{
	register Buffer	*cb = curbuf;
	register int	num;

	if ((num = exp) >= 0 || !BufMinorMode(cb, OverWrite)) {
		DelNChar();
		return;
	}
	/*
	 * Feb 90 [TRH] tabs need special attention, so we cannot
	 * just use SelfInsert.
	 */
	view_mode_check();

	if (!bolp(cb)) {
		modify();
		makedirty(cb->b_dot);
		do {
			if (linebuf[--cb->b_char] == '\t') {
				exp = TabIncr(calc_pos(linebuf,
						       cb->b_char)) - 1;
				Insert(' ');
			}
			linebuf[cb->b_char] = ' ';
		} while (++num < 0 && !bolp(cb));
	}
}

/* Delete whitespace from current line in the given `direction'.
   (in both directions if zero, i.e., (FORWARD + BACKWARD) */
void
del_whitespace(direction)
int	direction;
{
	register Buffer	*cb = curbuf;
	register char	*ep = &linebuf[cb->b_char],
			*sp = ep;

	if (direction >= FORWARD + BACKWARD) {
		while (isspace(*ep++))
			;
		--ep;
	}
	if (direction <= FORWARD + BACKWARD) {
		while (sp > linebuf) {
			if (!isspace(*--sp)) {
				++sp;
				break;
			}
		}
	}
	if (sp != ep) {
		modify();
		makedirty(cb->b_dot);
		DFixMarks(cb->b_dot, cb->b_char = (int)(sp - linebuf),
			  cb->b_dot, (int)(ep - linebuf));
		strcpy(sp, ep);
	}
}

DEF_CMD( "delete-white-space", DelWtSpace, EDIT )
{
	del_whitespace(exp_p ? exp : FORWARD + BACKWARD);
}

DEF_CMD( "delete-blank-lines", DelBlnkLines, EDIT )
{
	register Buffer	*cb = curbuf;
	register Line	*line1 = cb->b_dot,
			*line2 = line1;
	register char	first_blank;

	if (!blnkp(&linebuf[cb->b_char]))
		return;

	if (first_blank = blnkp(linebuf)) {
		do {
			if ((line1 = line1->l_prev) == NULL)
				break;
			SetLine(line1);
		} while (blnkp(linebuf));
	}
	Eol();
	del_whitespace(FORWARD + BACKWARD);
	while ((line1 = line2->l_next) && blnkp(lcontents(line1)))
		line2 = line1;
	/*
	 * Now dot points to the start of the blank region to be deleted,
	 * and line2 points to the last blank line of this region. Keep a
	 * single blank line if original dot was on a blank line.
	 */
	if (first_blank && cb->b_char)	/* i.e. not when at start of buffer */
		line_move(FORWARD);
	reg_delete(cb->b_dot, cb->b_char, line2, length(line2));
}


/* If argument is specified, kill that many lines down.  Otherwise,
   if we "appear" to be at the end of a line, i.e. everything to the
   right of the cursor is white space, we delete the line separator
   as if we were at the end of the line. */

DEF_CMD( "kill-to-end-of-line", KillEOL, NO/*EDIT--kill command*/ )
{
	register Buffer *cb = curbuf;
	register Line	*line2 = cb->b_dot;	/* reasonable defaults */
	register int	char2 = 0;

	if (exp_p) {
		if (exp) {
			line2 = next_line(line2, exp);
			if ((LineDist(cb->b_dot, line2) < exp) ||
			    (line2 == cb->b_dot))
				char2 = length(line2);
		}
		/* else Kill to beginning of line */
	}
	else {
		if (!blnkp(&linebuf[cb->b_char]) ||
		    ((line2 = line2->l_next) == NULL &&
		     (line2 = cb->b_dot, TRUE)))
			char2 = length(line2);
	}
	reg_kill(line2, char2, NO);
}


/* Kill the region spanned by the `move' function.
   If numeric argument is zero, move in the opposite direction first,
   so that the entire entity (e.g., a word) is killed. */

private void mv_reg_kill __(( void (*_(move))(void) ));
private void
mv_reg_kill(move)
void	(*move)__((void));
{
	register Buffer	*cb = curbuf;
	register Line	*line1;
	register int	char1;

	if (exp == 0) {
		exp = (LastCmd->Type & NEGATE) ? -1 : 1;
		(*move)();
		exp = -exp;
	}

	line1 = cb->b_dot;
	char1 = cb->b_char;
	(*move)();
	reg_kill(line1, char1, YES);
}

/* ...with this in hand, the following are of a refreshing simplicity. */

DEF_CMD( "kill-next-word", DelNWord, NO/*EDIT--kill command*/ );
DEF_CMD( "kill-previous-word", DelNWord, NO/*EDIT--kill command*/|NEGATE )
{
	extern void ForWord __(( void ));
	mv_reg_kill(ForWord);
}

DEF_CMD( "kill-to-beginning-of-sentence", KillEos, NO/*EDIT--kill command*/|NEGATE );
DEF_CMD( "kill-to-end-of-sentence", KillEos, NO/*EDIT--kill command*/ )
{
	extern void Eos __(( void ));
	mv_reg_kill(Eos);
}

DEF_CMD( "kill-s-expression", KillExpr, NO/*EDIT--kill command*/ )
{
	extern void FSexpr __(( void ));
	mv_reg_kill(FSexpr);
}

/* Make next command, if it is a kill command, append to current kill region. */

DEF_CMD( "append-next-kill", KillAppend, NO )
{
	if (*killptr != NULL)
		this_cmd = KILLCMD;
}

/*======================================================================
 * $Log: delete.c,v $
 * Revision 14.31.0.12  1994/06/22  07:58:19  tom
 * (patchup): bugfix: call modify _after_ DotTo so line1 is marked as modified.
 *
 * Revision 14.31.0.11  1994/06/12  04:09:22  tom
 * (patchup): return if empty region, modify() before any other action;
 * (newlist): new support function; (reg_unlink,reg_kill,CopyRegion): use it;
 * (reg_unlink): remove empty region check;
 * (adv_killptr): new support function; (reg_kill,CopyRegion): use it;
 * (reg_kill): add empty region check, don't kill in view mode--just copy;
 * (DelNChar,DelPChar,KillEOL,DelNWord,KillEos,KillExpr): remove EDIT flag
 *  from DEF_CMD; (DelNChar,DelPChar): reorg to allow killing in View mode;
 * (KillEOL): code squeeze.
 *
 * Revision 14.31  1993/02/18  06:15:31  tom
 * remove (void) casts; lotsa random optimizations.
 *
 * Revision 14.30  1993/02/05  14:53:18  tom
 * cleanup whitespace; some random optimizations; be more careful to call
 * modify() before actual modification (so buffer remains unchanged if
 * confirmation of modification fails); make "delete-white-space" aware of
 * numeric argument.
 *
 * Revision 14.26  1992/08/27  02:05:15  tom
 * add "append-next-kill" command; make patchup(), reg_unlink() private;
 * add RCS directives.
 *
 */
