/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

/*
 * ops.c: implementation of various operators: doshift, dodelete, dotilde,
 *		  dochange, doyank, doput, dojoin, doruler
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "ops.h"
#ifdef JP
#include "jp.h"
#endif
#ifdef FEPCTRL
#include "fepctrl.h"
#endif

/*
 * We have one yank buffer for normal yanks and puts, nine yank buffers for
 * deletes and 26 yank buffers for use by name.
 * Each yank buffer is an array of pointers to lines.
 */
static struct yankbuf
{
	char		**y_array;		/* pointer to array of line pointers */
	linenr_t 	y_size; 		/* number of lines in y_array */
	char		y_type; 		/* MLINE, MCHAR or MBLOCK */
} y_buf[36];					/* 0..9 = number buffers, 10..35 = char buffers */

static struct	yankbuf *y_current;		/* ptr to current yank buffer */
static int		yankappend;				/* TRUE when appending */
static struct	yankbuf *y_previous = NULL; /* ptr to last written yank buffer */

static void		get_yank_buffer __ARGS((int));
static int		stuff_yank __ARGS((int, char *));
static void		free_yank __ARGS((long));
static void		free_yank_all __ARGS((void));
static void		block_prep __ARGS((linenr_t, int));

/* variables use by block_prep, dodelete and doyank */
static int		startspaces;
static int		endspaces;
static int		textlen;
static char		*textstart;
static colnr_t	textcol;

/*
 * doshift - handle a shift operation
 */
	void
doshift(op)
	int 			op;
{
	register long i;

	if (!u_save((linenr_t)(Curpos.lnum - 1), (linenr_t)(Curpos.lnum + nlines)))
		return;

	Curpos.lnum += nlines;		/* start with last line, leave cursor on first */
	for (i = nlines; --i >= 0; )
		if (lineempty(--Curpos.lnum))
			Curpos.col = 0;
		else
		{
			/* if (Visual_block)
					shift the block, not the whole line
			else */
				shift_line(op == LSHIFT, p_sr);
		}

	updateScreen(CURSUPD);

	if (nlines > p_report)
		smsg("%ld line%s %ced", nlines, plural(nlines),
									(op == RSHIFT) ? '>' : '<');
}

/*
 * shift the current line one shiftwidth left (if left != 0) or right
 * leaves cursor on first blank in the line
 */
	void
shift_line(left, round)
	int left;
	int	round;
{
	register int count;
	register int i, j;

	count = get_indent();			/* get current indent */

	if (round)						/* round off indent */
	{
		i = count / (int)p_sw;		/* compute new indent */
		j = count % (int)p_sw;
		if (j)
		{
			if (!left)
				++i;
		}
		else if (left)
		{
			if (i)
				--i;
		}
		else
			++i;
		count = i * (int)p_sw;
	}
	else				/* original vi indent */
	{
		if (left)
		{
			count -= (int)p_sw;
			if (count < 0)
				count = 0;
		}
		else
			count += (int)p_sw;
	}
	set_indent(count, TRUE);		/* set new indent */
}

/*
 * Set y_current and yankappend, according to the value of yankbuffer.
 */
	static void
get_yank_buffer(writing)
	int		writing;
{
	register int i;

	yankappend = FALSE;
	if (((yankbuffer == 0 && !writing) || yankbuffer == '"') && y_previous != NULL)
	{
		y_current = y_previous;
		return;
	}
	i = yankbuffer;
	if (isdigit(i))
		i -= '0';
	else if (islower(i))
		i -= 'a' - 10;
	else if (isupper(i))
	{
		i -= 'A' - 10;
		yankappend = TRUE;
	}
	else			/* not 0-9, a-z or A-Z: use buffer 0 */
		i = 0;
	y_current = &(y_buf[i]);
	if (writing)		/* remember the buffer we write into for doput() */
		y_previous = y_current;
}

/*
 * (stop) recording into a yank buffer
 */
	int
dorecord(c)
	int c;
{
	char *p;
	static int bufname;

	if (Recording == FALSE) 		/* start recording */
	{
		if (!isalnum(c) && c != '"')	/* registers 0-9, a-z and " are allowed */
			return FALSE;
		Recording = TRUE;
		showmode();
		bufname = c;
		return TRUE;
	}
	else							/* stop recording */
	{
		Recording = FALSE;
		msg("");
			/* the trailing 'q' command will not have been put in the buffer */
		p = (char *)get_recorded();
		if (p == NULL)
			return FALSE;
		return (stuff_yank(bufname, p));
	}
}

/*
 * stuff string 'p' into yank buffer 'bufname' (append if uppercase)
 * 'p' is assumed to be alloced.
 */
	static int
stuff_yank(bufname, p)
	int bufname;
	char *p;
{
	char *lp;
	char **pp;

	yankbuffer = bufname;
	if (yankbuffer == '.' || yankbuffer == '%')		/* read-only buffer */
		return FALSE;
	get_yank_buffer(TRUE);
	if (yankappend && y_current->y_array != NULL)
	{
		pp = &(y_current->y_array[y_current->y_size - 1]);
		lp = lalloc((u_long)(strlen(*pp) + strlen(p) + 1), TRUE);
		if (lp == NULL)
		{
			free(p);
			return FALSE;
		}
		strcpy(lp, *pp);
		strcat(lp, p);
		free(p);
		free(*pp);
		*pp = lp;
	}
	else
	{
		free_yank_all();
		if ((y_current->y_array = (char **)alloc((unsigned)sizeof(char *))) == NULL)
		{
			free(p);
			return FALSE;
		}
		y_current->y_array[0] = p;
		y_current->y_size = 1;
		y_current->y_type = MCHAR;	/* used to be MLINE, why? */
	}
	return TRUE;
}

/*
 * execute a yank buffer (register): copy it into the stuff buffer
 */
	int
doexecbuf(c)
	int c;
{
	static int lastc = NUL;
	long i;

	if (c == '@')			/* repeat previous one */
		c = lastc;

	lastc = c;
	if (!isalnum(c) && c != '"')		/* registers 0-9, a-z and " are allowed */
		return FALSE;

	yankbuffer = c;
	get_yank_buffer(FALSE);
	if (y_current->y_array == NULL)
		return FALSE;

	for (i = y_current->y_size; --i >= 0; )
	{
	/* insert newline between lines and after last line if type is MLINE */
		if (y_current->y_type == MLINE || i < y_current->y_size - 1)
		{
			if (ins_typestr("\n", FALSE) < 0)
				return FALSE;
		}
		if (ins_typestr(y_current->y_array[i], FALSE) < 0)
			return FALSE;
	}
	Exec_reg = TRUE;		/* disable the 'q' command */
	return TRUE;
}

/*
 * insert a yank buffer: copy it into the Read buffer
 */
	int
insertbuf(c)
	int c;
{
	long i;

	if (c == '%')						/* insert file name */
	{
		if (check_fname())
			return FALSE;
		stuffReadbuff(xFilename);
		return TRUE;
	}

	if (!isalnum(c) && c != '"')		/* registers 0-9, a-z and " are allowed */
		return FALSE;

	yankbuffer = c;
	get_yank_buffer(FALSE);
	if (y_current->y_array == NULL)
		return FALSE;

	for (i = 0; i < y_current->y_size; ++i)
	{
		stuffReadbuff(y_current->y_array[i]);
	/* insert newline between lines and after last line if type is MLINE */
		if (y_current->y_type == MLINE || i < y_current->y_size - 1)
			stuffReadbuff("\n");
	}
	return TRUE;
}

/*
 * dodelete - handle a delete operation
 */
	void
dodelete()
{
	register int	n;
	linenr_t		lnum;
	char			*ptr;
	linenr_t		old_lcount = line_count;

	/*
	 * Imitate the strange Vi behaviour: If the delete spans more than one line
	 * and mtype == MCHAR and the result is a blank line, make the delete
	 * linewise. Don't do this for the change command.
	 */
	if (mtype == MCHAR && nlines > 1 && operator == DELETE)
	{
		ptr = nr2ptr(endop.lnum) + endop.col + mincl;
		skipspace(&ptr);
		if (*ptr == NUL && startinmargin())
			mtype = MLINE;
	}

		/*
		 * Shift number buffers if there is no yankbuffer defined and we do a
		 * delete that contains a line break.
		 */
	if (yankbuffer == 0 && (mtype == MLINE || nlines > 1))
	{
		y_current = &y_buf[9];
		free_yank_all();				/* free buffer nine */
		for (n = 9; n > 1; --n)
			y_buf[n] = y_buf[n - 1];
		y_previous = y_current = &y_buf[1];
		y_buf[1].y_array = NULL;		/* set buffer one to empty */
	}
	else if (yankbuffer == '.' || yankbuffer == '%')	/* read-only buffer */
	{
		beep();
		return;
	}
	else								/* yank into specified buffer */
		get_yank_buffer(TRUE);

	/*
	 * Do a yank of whatever we're about to delete. If there's too much stuff
	 * to fit in the yank buffer, then get a confirmation before doing the
	 * delete. This is crude, but simple. And it avoids doing a delete of
	 * something we can't put back if we want.
	 */
	if (!doyank(TRUE))
	{
		if (ask_yesno("cannot yank; delete anyway") != 'y')
		{
			emsg(e_abort);
			return;
		}
	}

/*
 * block mode
 */
	if (Visual_block)
	{
		if (!u_save((linenr_t)(startop.lnum - 1), (linenr_t)(endop.lnum + 1)))
			return;

		for (lnum = Curpos.lnum; Curpos.lnum <= endop.lnum; ++Curpos.lnum)
		{
			block_prep(Curpos.lnum, TRUE);
			if (textlen == 0)		/* nothing to delete */
				continue;

		/*
		 * If we delete a TAB, it may be replaced by several characters.
		 * Thus the number of characters may increase!
		 */
			n = textlen - startspaces - endspaces;
		/* number of characters increases - make room */
			if (n < 0 && !canincrease(-n))
				continue;
			ptr = nr2ptr(Curpos.lnum) + textcol;
		/* copy the part after the deleted part */
			memmove(ptr + startspaces + endspaces, ptr + textlen, strlen(ptr + textlen) + 1);
		/* insert spaces */
			copy_spaces(ptr, (size_t)(startspaces + endspaces));
			if (n > 0)
				canincrease(0);
		}
		Curpos.lnum = lnum;
		CHANGED;
		updateScreen(VALID_TO_CURSCHAR);
		nlines = 0;		/* no lines deleted */
	}
	else if (mtype == MLINE)
	{
		if (operator == CHANGE)
		{
			dellines((long)(nlines - 1), TRUE, TRUE);
			if (!u_saveCurpos())
				return;
			while (delchar(TRUE));		/* slow but simple */
		}
		else
		{
			dellines(nlines, TRUE, TRUE);
		}
		u_clearline();	/* "U" command should not be possible after "dd" */
	}
	else if (nlines == 1)		/* delete characters within one line */
	{
		if (!u_saveCurpos())
			return;
		n = endop.col - startop.col + 1 - !mincl;
		while (n-- > 0)
			if (!delchar(TRUE))
				break;
	}
	else						/* delete characters between lines */
	{
		if (!u_saveCurpos())	/* save first line for undo */
			return;
		n = Curpos.col;
		while (Curpos.col >= n)	/* delete from cursor to end of line */
			if (!delchar(TRUE))
				break;

		startop = Curpos;		/* remember Curpos */
		++Curpos.lnum;
		dellines((long)(nlines - 2), TRUE, TRUE);	/* includes save for undo */

		if (!u_saveCurpos())	/* save last line for undo */
			return;
		n = endop.col - !mincl;
		Curpos.col = 0;
		while (n-- >= 0)		/* delete from start of line until endop */
			if (!delchar(TRUE))
				break;
		Curpos = startop;		/* restore Curpos */
		dojoin(FALSE, TRUE);
	}

	if ((mtype == MCHAR && nlines == 1) || operator == CHANGE)
	{
		cursupdate();
		updateline();
	}
	else
		updateScreen(CURSUPD);

	msgmore(line_count - old_lcount);

		/* correct endop for deleted text (for "']" command) */
	if (Visual_block)
		endop.col = startop.col;
	else
		endop = startop;
}

/*
 * dotilde - handle the (non-standard vi) tilde operator
 */
	void
dotilde()
{
	FPOS pos;

	if (!u_save((linenr_t)(startop.lnum - 1), (linenr_t)(endop.lnum + 1)))
		return;

	pos = startop;
	if (Visual_block)		/* block mode */
	{
		for (; pos.lnum <= endop.lnum; ++pos.lnum)
		{
			block_prep(pos.lnum, FALSE);
			pos.col = textcol;
			while (textlen > 0)
			{
				colnr_t col;

				col = pos.col;
				swapchar(&pos);
				if (inc(&pos) == -1)	/* at end of file */
					break;
				textlen -= pos.col - col;
			}
		}
	}
	else			/* not block mode */
	{
		if (mtype == MLINE)
		{
				pos.col = 0;
				endop.col = strlen(nr2ptr(endop.lnum));
				if (endop.col)
						--endop.col;
		}
		else if (!mincl)
			dec(&endop);

		while (ltoreq(pos, endop))
		{
			swapchar(&pos);
			if (inc(&pos) == -1)	/* at end of file */
				break;
		}
	}

	if (mtype == MCHAR && nlines == 1 && !Visual_block)
	{
		cursupdate();
		updateline();
	}
	else
		updateScreen(CURSUPD);

	if (nlines > p_report)
			smsg("%ld line%s ~ed", nlines, plural(nlines));
}

/*
 * If operator == UPPER: make uppercase,
 * if operator == LOWER: make lowercase,
 * else swap case of character at 'pos'
 */
	void
swapchar(pos)
	FPOS	*pos;
{
	u_char	c;

	c = gchar(pos);
#ifdef JP
	if (p_jt)
	{
		FPOS	pos2;
		char	c2;

		pos2.lnum = pos->lnum;
		pos2.col  = pos->col + 1;
		c2 = gchar(&pos2);

		if ((IsKanji(c) && IsKanji(c2)) || c2 == NUL)
		{
			pchar(*pos, c  & 0x7f);
			pchar(pos2, c2 & 0x7f);
			pos->col = pos2.col;
			CHANGED;
		}
		else if (c > ' ' && c2 > ' ' && !IsKanji(c2))
		{
			pchar(*pos, c  | 0x80);
			pchar(pos2, c2 | 0x80);
			CHANGED;
		}
	}
	else if (IsKanji(c))
	{
		FPOS	pos2;
		u_char	k;
		u_char	c1, c2;

		pos2.lnum = pos->lnum;
		pos2.col  = pos->col + 1;

		c1 = c;
		c2 = k = gchar(&pos2);

		jptocase(&c1, &c2, operator);
		if (c1 != c || c2 != k)
		{
			pchar(*pos, c1);
			pchar(pos2, c2);
			CHANGED;
		}
	}
	else
#endif
	if (islower(c) && operator != LOWER)
	{
		pchar(*pos, toupper(c));
		CHANGED;
	}
	else if (isupper(c) && operator != UPPER)
	{
		pchar(*pos, tolower(c));
		CHANGED;
	}
}

/*
 * dochange - handle a change operation
 */
	void
dochange()
{
	register colnr_t 		   l;

	l = startop.col;

	if (!no_op)
		dodelete();

	if ((l > Curpos.col) && !lineempty(Curpos.lnum))
		incCurpos();

	startinsert(NUL, FALSE, (linenr_t)1);
}

/*
 * set all the yank buffers to empty (called from main())
 */
	void
init_yank()
{
		register int i;

		for (i = 0; i < 36; ++i)
				y_buf[i].y_array = NULL;
}

/*
 * Free "n" lines from the current yank buffer.
 * Called for normal freeing and in case of error.
 */
	static void
free_yank(n)
	long n;
{
	if (y_current->y_array != NULL)
	{
		register long i;

		for (i = n; --i >= 0; )
		{
			if (i % 1000 == 999)					/* this may take a while */
				smsg("freeing %ld lines", i + 1);
			free(y_current->y_array[i]);
		}
		free((char *)y_current->y_array);
		y_current->y_array = NULL;
		if (n >= 1000)
			msg("");
	}
}

	static void
free_yank_all()
{
		free_yank(y_current->y_size);
}

/*
 * Yank the text between Curpos and startpos into a yank buffer.
 * If we are to append ("uppercase), we first yank into a new yank buffer and
 * then concatenate the old and the new one (so we keep the old one in case
 * of out-of-memory).
 */
	int
doyank(deleting)
	int deleting;
{
	long 				i;				/* index in y_array[] */
	struct yankbuf		*curr;			/* copy of y_current */
	struct yankbuf		new; 			/* new yank buffer when appending */
	char				**new_ptr;
	register linenr_t	lnum;			/* current line number */
	long 				j;
	int					yanktype = mtype;
	long				yanklines = nlines;
	linenr_t			yankendlnum = endop.lnum;

	char				*pnew;

#ifdef ONEW
	void				Onew_addword();
#endif

	if (yankbuffer == '.' || yankbuffer == '%')			/* read-only buffer */
	{
		beep();
		return FALSE;
	}
	if (!deleting)					/* dodelete() already set y_current */
		get_yank_buffer(TRUE);

	curr = y_current;
	if (yankappend && y_current->y_array != NULL) /* append to existing contents */
		y_current = &new;
	else
		free_yank_all();		/* free previously yanked lines */

/*
 * If the cursor was in column 1 before and after the movement, the
 * yank is always linewise.
 */
	if (mtype == MCHAR && startop.col == 0 && endop.col == 0 && nlines > 1)
	{
		yanktype = MLINE;
		if (mincl == FALSE && yankendlnum > startop.lnum)
		{
			--yankendlnum;
			--yanklines;
		}
	}

	y_current->y_size = yanklines;
	y_current->y_type = yanktype;	/* set the yank buffer type */
	y_current->y_array = (char **)lalloc((u_long)(sizeof(char *) * yanklines), TRUE);

	if (y_current->y_array == NULL)
	{
		y_current = curr;
		return FALSE;
	}

	i = 0;
	lnum = startop.lnum;

	if (Visual_block)
	{
/*
 * block mode
 */
		y_current->y_type = MBLOCK;	/* set the yank buffer type */
		for ( ; lnum <= yankendlnum; ++lnum)
		{
			block_prep(lnum, FALSE);

#ifdef JP
			j = startspaces > 0 ? startspaces : 0;
			j +=  endspaces > 0 ?   endspaces : 0;
#else
			j = startspaces + endspaces;
#endif
			if ((pnew = alloc(j + textlen + 1)) == NULL)
				goto fail;
			y_current->y_array[i++] = pnew;
			if (startspaces > 0)
			{
				copy_spaces(pnew, (size_t)startspaces);
				pnew += startspaces;
			}
			strncpy(pnew, textstart, (size_t)textlen);
			pnew += textlen;
			if (endspaces > 0)
			{
				copy_spaces(pnew, (size_t)endspaces);
				pnew += endspaces;
			}
			*pnew = NUL;
		}
	}
	else
	{
/*
 * there are three parts for non-block mode:
 * 1. if yanktype != MLINE yank last part of the top line
 * 2. yank the lines between startop and endop, inclusive when yanktype == MLINE
 * 3. if yanktype != MLINE yank first part of the bot line
 */
		if (yanktype != MLINE)
		{
			if (yanklines == 1)		/* startop and endop on same line */
			{
					j = endop.col - startop.col + 1 - !mincl;
					if ((y_current->y_array[0] = strnsave(nr2ptr(lnum) + startop.col, (int)j)) == NULL)
					{
	fail:
							free_yank(i);	/* free the lines that we allocated */
							y_current = curr;
							return FALSE;
					}
					goto success;
			}
			if ((y_current->y_array[0] = strsave(nr2ptr(lnum++) + startop.col)) == NULL)
					goto fail;
			++i;
		}

		while (yanktype == MLINE ? (lnum <= yankendlnum) : (lnum < yankendlnum))
		{
			if ((y_current->y_array[i] = strsave(nr2ptr(lnum++))) == NULL)
					goto fail;
			++i;
		}
		if (yanktype != MLINE)
		{
			if ((y_current->y_array[i] = strnsave(nr2ptr(yankendlnum), endop.col + 1 - !mincl)) == NULL)
					goto fail;
		}
	}

success:
	if (curr != y_current)		/* append the new block to the old block */
	{
		new_ptr = (char **)lalloc((u_long)(sizeof(char *) * (curr->y_size + y_current->y_size)), TRUE);
		if (new_ptr == NULL)
				goto fail;
		for (j = 0; j < curr->y_size; ++j)
				new_ptr[j] = curr->y_array[j];
		free(curr->y_array);
		curr->y_array = new_ptr;

		if (yanktype == MLINE) 	/* MLINE overrides MCHAR and MBLOCK */
				curr->y_type = MLINE;
		if (curr->y_type == MCHAR)		/* concatenate the last line of the old
										block with the first line of the new block */
		{
				new_ptr = (char **)lalloc((u_long)(strlen(curr->y_array[curr->y_size - 1]) + strlen(y_current->y_array[0]) + 1), TRUE);
				if (new_ptr == NULL)
				{
						i = y_current->y_size - 1;
						goto fail;
				}
				strcpy((char *)new_ptr, curr->y_array[--j]);
				strcat((char *)new_ptr, y_current->y_array[0]);
				free(curr->y_array[j]);
				free(y_current->y_array[0]);
				curr->y_array[j++] = (char *)new_ptr;
				i = 1;
		}
		else
				i = 0;
		while (i < y_current->y_size)
				curr->y_array[j++] = y_current->y_array[i++];
		curr->y_size = j;
		free(y_current->y_array);
		y_current = curr;
	}
#ifdef ONEW
	Onew_addword("yank", curr->y_array[0]);
#endif
	if (operator == YANK)		/* don't do this when deleting */
	{
		if (yanktype == MCHAR && !Visual_block)
			--yanklines;
		if (yanklines > p_report)
		{
			cursupdate();		/* redisplay now, so message is not deleted */
			smsg("%ld line%s yanked", yanklines, plural(yanklines));
		}
	}

	return TRUE;
}

	void
doput(dir, count)
	int dir;
	long count;
{
	char		*ptr, *ep;
	int 		newlen;
	int			totlen = 0;		/* init for gcc */
	linenr_t	lnum;
	int			col;
	long 		i;		/* index in y_array[] */
	int 		y_type;
	long 		y_size;
	char		**y_array;
	long 		nlines = 0;
	int			vcol;
	int			delchar;
	int			incr = 0;
	long		j;
	FPOS		newCurpos;
	int			commandchar;
	char		temp[2];

	startop = Curpos;			/* default for "'[" command */
	if (dir == FORWARD)
		startop.col++;
	endop = Curpos;				/* default for "']" command */
	commandchar = (dir == FORWARD ? (count == -1 ? 'o' : 'a') : (count == -1 ? 'O' : 'i'));
	if (yankbuffer == '.')		/* use inserted text */
	{
		stuff_inserted(commandchar, count, FALSE);
		return;
	}
	else if (yankbuffer == '%')	/* use file name */
	{
		if (!check_fname())
		{
			stuffcharReadbuff(commandchar);
			stuffReadbuff(xFilename);
			stuffcharReadbuff(ESC);
		}
		return;
	}
	get_yank_buffer(FALSE);

	y_type = y_current->y_type;
	y_size = y_current->y_size;
	y_array = y_current->y_array;

	if (count == -1)		/* :put command */
	{
		y_type = MLINE;
		count = 1;
	}

	if (y_size == 0 || y_array == NULL)
	{
		temp[0] = yankbuffer;
		temp[1] = NUL;
		emsg2("Nothing in register %s", temp);
		return;
	}

	if (y_type == MBLOCK)
	{
		lnum = Curpos.lnum + y_size + 1;
		if (lnum > line_count)
			lnum = line_count + 1;
		if (!u_save(Curpos.lnum - 1, lnum))
			return;
	}
	else if (!u_saveCurpos())
		return;

	newlen = strlen(y_array[0]);
	CHANGED;

	lnum = Curpos.lnum;
	col = Curpos.col;

/*
 * block mode
 */
	if (y_type == MBLOCK)
	{
		if (dir == FORWARD && gcharCurpos() != NUL)
		{
			col = getvcol(&Curpos, 3) + 1;
			++Curpos.col;
		}
		else
			col = getvcol(&Curpos, 2);
		for (i = 0; i < y_size; ++i)
		{
			startspaces = 0;
			endspaces = 0;
			textcol = 0;
			vcol = 0;
			delchar = 0;

		/* add a new line */
			if (Curpos.lnum > line_count)
			{
				ep = alloc_line(0);
				if (ep == NULL)
						goto error;
				appendline(line_count, ep);
				++nlines;
			}
			ptr = nr2ptr(Curpos.lnum);
			while (vcol < col && *ptr)
			{
#ifdef JP
				if (IsKanji(*ptr))
				{
					incr = 2;
					vcol    += incr;
					ptr     += incr;
					textcol += incr;
					continue;
				}
#endif
				/* Count a tab for what it's worth (if list mode not on) */
				incr = chartabsize(*ptr, vcol);
				vcol += incr;
				++ptr;
				++textcol;
			}
			if (vcol < col)	/* line too short, padd with spaces */
			{
				startspaces = col - vcol;
			}
			else if (vcol > col)
			{
				endspaces = vcol - col;
				startspaces = incr - endspaces;
#ifdef JP
				if (IsKanji(*(ptr - 1)))
				{
					/*	leave text */
					endspaces = startspaces = 0;
					/*	destroy text
					textcol -= 2;
					delchar = 2;
					*/
				}
				else
#endif
				{
					--textcol;
					delchar = 1;
				}
			}
			newlen = strlen(y_array[i]);
			totlen = count * newlen + startspaces + endspaces;
			if (!canincrease(totlen))
				break;
			ptr = nr2ptr(Curpos.lnum) + textcol;

		/* move the text after the cursor to the end of the line. */
			memmove(ptr + totlen - delchar, ptr, strlen(ptr) + 1);
		/* may insert some spaces before the new text */
			copy_spaces(ptr, (size_t)startspaces);
			ptr += startspaces;
		/* insert the new text */
			for (j = 0; j < count; ++j)
			{
					strncpy(ptr, y_array[i], (size_t)newlen);
					ptr += newlen;
			}
		/* may insert some spaces after the new text */
			copy_spaces(ptr, (size_t)endspaces);

			++Curpos.lnum;
			if (i == 0)
				Curpos.col += startspaces;
		}
		endop.lnum = Curpos.lnum - 1;		/* for "']" command */
		endop.col = textcol + totlen - 1;
		Curpos.lnum = lnum;
		cursupdate();
		updateScreen(VALID_TO_CURSCHAR);
	}
	else		/* not block mode */
	{
		if (y_type == MCHAR)
		{
	/* if type is MCHAR, FORWARD is the same as BACKWARD on the next character */
#ifdef JP
			if (dir == FORWARD && IsKanji(gcharCurpos()))
			{
				col += 2;
				if (newlen)
				{
					Curpos.col += 2;
					endop.col += 2;
				}
			}
			else if (dir == FORWARD && gcharCurpos() != NUL)
#else
			if (dir == FORWARD && gcharCurpos() != NUL)
#endif
			{
				++col;
				if (newlen)
				{
					++Curpos.col;
					++endop.col;
				}
			}
			newCurpos = Curpos;
		}
		else if (dir == BACKWARD)
	/* if type is MLINE, BACKWARD is the same as FORWARD on the previous line */
			--lnum;
		else	/* type == MLINE, dir == FORWARD */
		{
			startop.col = 0;
			startop.lnum++;
		}

/*
 * simple case: insert into current line
 */
		if (y_type == MCHAR && y_size == 1)
		{
			i = count * newlen;
			if (i)
			{
				if (!canincrease((int)i))
					return; 				/* alloc() will give error message */
				ep = nr2ptr(lnum) + col;
				memmove(ep + i, ep, strlen(ep) + 1);
				Curpos.col += (colnr_t)(i - 1);	/* put cursor on last putted char */
				for (i = 0; i < count; ++i)
				{
					strncpy(ep, y_array[0], (size_t)newlen);
					ep += newlen;
				}
#ifdef JP
				if (IsKanji(*(ep - 1)))
					Curpos.col --;
#endif
			}
			endop = Curpos;
			updateline();
		}
		else
		{
			if (y_type == MCHAR)
				--y_size;
			while (--count >= 0)
			{
				i = 0;
				if (y_type == MCHAR)
				{
					/*
					 * Split the current line in two at the insert position.
					 * Append y_array[0] to first line.
					 * Insert y_array[size - 1] in front of second line.
					 */
					ptr = nr2ptr(lnum) + col;
					col = strlen(y_array[y_size]);
					ep = alloc_line((unsigned)(strlen(ptr) + col));
					if (ep == NULL)
						goto error;
					strcpy(ep, y_array[y_size]);
					strcat(ep, ptr);
					appendline(lnum, ep);			/* insert in second line */
					++nlines;
					*ptr = NUL;
					Curpos.lnum = lnum;
					if (!canincrease(newlen))		/* lnum == Curpos.lnum! */
						goto error;
					strcat(nr2ptr(lnum), y_array[0]);/* append to first line */
					i = 1;
				}

				while (i < y_size)
				{
					ep = save_line(y_array[i++]);
					if (ep == NULL)
						goto error;
					appendline(lnum++, ep);
					++nlines;
				}
				if (y_type == MCHAR)
					++lnum; 	/* lnum is now number of line below inserted lines */
			}

			endop.lnum = lnum;		/* for "']" command */
			if (y_type == MLINE)
			{
				Curpos.col = 0;
				endop.col = 0;
				if (dir == FORWARD)
				{
					updateScreen(NOT_VALID);		/* recompute Botline */
					++Curpos.lnum;
				}
					/* put cursor on first non-blank in last inserted line */
				beginline(TRUE);
			}
			else		/* put cursor on first inserted character */
			{
				if (col > 1)
					endop.col = col - 1;
				else
					endop.col = 0;
				Curpos = newCurpos;
			}

error:
			updateScreen(CURSUPD);
		}
	}

	msgmore(nlines);
	set_want_col = TRUE;
}

/*
 * display the contents of the yank buffers
 */
	void
dodis()
{
	register int i, n;
	register long j;
	register char *p;
	register struct yankbuf *yb;

#ifdef AMIGA
	settmode(0);			/* set cooked mode so output can be halted */
#endif
	for (i = -1; i < 36; ++i)
	{
		if (i == -1)
		{
			if (y_previous != NULL)
				yb = y_previous;
			else
				yb = &(y_buf[0]);
		}
		else
			yb = &(y_buf[i]);
		if (yb->y_array != NULL)
		{
			if (i == -1)
				outstrn("\"\"");
			else
			{
				outchar('"');
				if (i < 10)
					outchar(i + '0');
				else
					outchar(i + 'a' - 10);
			}
			outchar(' ');

			n = (int)Columns - 4;
			for (j = 0; j < yb->y_size && n > 0; ++j)
			{
				if (j)
				{
					outstrn("^J");
					n -= 2;
				}
#ifdef JP
				p = yb->y_array[j];
				if (strlen(p) < (u_int)n)
					n -= outtrans(p, -1);
				else
					n -= outtrans(p, n);
#else
				for (p = yb->y_array[j]; *p && n > 0; ++p)
				{
					outstrn(transchar(*p));
					n -= charsize(*p);
				}
#endif
			}
			outchar('\n');
			flushbuf();
		}
	}
#ifdef AMIGA
	settmode(1);
#endif
	wait_return(TRUE);
}

/*
 * join 'count' lines (minimal 2), including u_save()
 */
	void
dodojoin(count, insert_space, redraw)
	long	count;
	int		insert_space;
	int		redraw;
{
	if (!u_save((linenr_t)(Curpos.lnum - 1), (linenr_t)(Curpos.lnum + count)))
		return;

	while (--count > 0)
		if (!dojoin(insert_space, redraw))
		{
				beep();
				break;
		}

	if (redraw)
		updateScreen(VALID_TO_CURSCHAR);
}

	int
dojoin(insert_space, redraw)
	int			insert_space;
	int			redraw;
{
	char		*curr;
	char		*next;
	char		*endcurr;
	int 		currsize;		/* size of the current line */
	int 		nextsize;		/* size of the next line */
	int			spaces;			/* number of spaces to insert */
	int			rows_to_del;	/* number of rows on screen to delete */
	linenr_t	t;

	if (Curpos.lnum == line_count)		/* on last line */
		return FALSE;

	rows_to_del = plines_m(Curpos.lnum, Curpos.lnum + 1);
	curr = nr2ptr(Curpos.lnum);
	currsize = strlen(curr);
	next = nr2ptr((linenr_t)(Curpos.lnum + 1));
	spaces = 0;
	if (insert_space)
	{
		skipspace(&next);
		spaces = 1;
#ifdef JP
		if (*next == ')' || IsKanji(*next) || currsize == 0)
#else
		if (*next == ')' || currsize == 0)
#endif
			spaces = 0;
		else
		{
			endcurr = curr + currsize - 1;
			if (*endcurr == ' ' || *endcurr == TAB)
			{
				spaces = 0;
				if (currsize > 1)
					--endcurr;
			}
#ifdef JP
			if (IsKanji(*endcurr))
				spaces = 0;
#endif
			if (p_js && strchr(".!?", *endcurr) != NULL)
				spaces = 2;
		}
	}
	nextsize = strlen(next);
	if (!canincrease(nextsize + spaces))
		return FALSE;

	/*
	 * Append the spaces and the next line. Curr has to be obtained again,
	 * because canincrease() will have changed the pointer.
	 */
	curr = nr2ptr(Curpos.lnum) + currsize;
	while (spaces--)
		*curr++ = ' ';
	strcpy(curr, next);

	/*
	 * Delete the following line. To do this we move the cursor there
	 * briefly, and then move it back. After dellines() the cursor may
	 * have moved up (last line deleted), so the current lnum is kept in t.
	 */
	t = Curpos.lnum;
	++Curpos.lnum;
	dellines(1L, FALSE, FALSE);
	Curpos.lnum = t;

	/*
	 * the number of rows on the screen is reduced by the difference
	 * in number of rows of the two old lines and the one new line
	 */
	if (redraw)
	{
		rows_to_del -= plines(Curpos.lnum);
		if (rows_to_del > 0)
			s_del(Cursrow, rows_to_del, TRUE);
	}

 	/*
	 * go to first character of the joined line
	 */
	if (currsize == 0)
		Curpos.col = 0;
	else
	{
		Curpos.col = currsize - 1;
#ifdef JP
		if (IsKanji(*(curr - 1)))
			Curpos.col --;
#endif
		oneright();
	}
	CHANGED;

	return TRUE;
}

/*
 * implementation of the format operator 'Q'
 */
	void
doformat()
{
		/* prepare undo and join the lines */
	dodojoin((long)nlines, TRUE, FALSE);

		/* put cursor on last non-space */
	coladvance(MAXCOL);
	while (Curpos.col && isspace(gcharCurpos()))
		decCurpos();
	curs_columns(FALSE);			/* update Cursvcol */

		/* do the formatting */
	State = INSERT;		/* for Opencmd() */
#ifdef JP
	insertchar(NUL, NUL);
#else
	insertchar(NUL);
#endif
	State = NORMAL;
	updateScreen(NOT_VALID);
	msg("");
}

	void
startinsert(initstr, startln, count)
	int			initstr;
	int 		startln;		/* if set, insert at start of line */
	long 		count;
{
	Insstart = Curpos;
	if (startln)
		Insstart.col = 0;

	if (initstr != NUL)
	{
			ResetRedobuff();
			AppendNumberToRedobuff(count);
			AppendCharToRedobuff(initstr);
	}

	if (initstr == 'R')
		State = REPLACE;
	else
		State = INSERT;

	if (p_smd)
		showmode();

	change_warning();		/* give a warning if readonly */
#ifdef FEPCTRL
	if (p_fc)
	{
		fep_on();
		edit(count);
		fep_off();
	}
	else
#endif
	edit(count);
}

/*
 * prepare a few things for block mode yank/delete/tilde
 *
 * for delete:
 * - textlen includes the first/last char to be (partly) deleted
 * - start/endspaces is the number of columns that are taken by the
 *	 first/last deleted char minus the number of columns that have to be deleted.
 * for yank and tilde:
 * - textlen includes the first/last char to be wholly yanked
 * - start/endspaces is the number of columns of the first/last yanked char
 *   that are to be yanked.
 */
	static void
block_prep(lnum, delete)
	linenr_t	lnum;
	int			delete;
{
	int			vcol;
	int			incr = 0;
	char		*pend;

	startspaces = 0;
	endspaces = 0;
	textlen = 0;
	textcol = 0;
	vcol = 0;
	textstart = nr2ptr(lnum);
	while (*textstart)
	{
#ifdef JP
		if (IsKanji(*textstart))
		{
			incr = 2;
			vcol += incr;
			if (vcol > startvcol)
				break;
			textstart += incr;
			textcol   += incr;
			continue;
		}
#endif
		/* Count a tab for what it's worth (if list mode not on) */
		incr = chartabsize(*textstart, vcol);

		vcol += incr;
		if (vcol > startvcol)
			break;

		++textstart;
		++textcol;
	}
	if (vcol <= startvcol)	/* line too short */
	{
		if (!delete)
			endspaces = endvcol - startvcol + 1;
		return;
	}

	/* vcol > startvcol */
	startspaces = incr - (vcol - startvcol);
	if (!delete)
	{
#ifdef JP
		if (IsKanji(*textstart))
			startspaces = - startspaces;
		else
#endif
		if (startspaces)	/* for tab char */
			startspaces = incr - startspaces;
	}

	pend = textstart;
#ifdef JP
	if (IsKanji(*pend))
		pend += 2;
	else
#endif
	pend ++;

	while (vcol <= endvcol && *pend)
	{
#ifdef JP
		if (IsKanji(*pend))
		{
			incr = 2;
			pend += 2;
		}
		else
#endif
		{
			/* Count a tab for what it's worth (if list mode not on) */
			incr = chartabsize(*pend, vcol);
			++pend;
		}
		vcol += incr;
	}
	pend --;

	if (vcol <= endvcol)	/* line too short */
	{
		if (!delete)
			endspaces = endvcol - vcol + 1;
	}
	else
	{
		endspaces = vcol - endvcol - 1;
#ifdef JP
		if (IsKanji(*pend))
		{
			if (!delete)
				endspaces = -endspaces;
		}
		else
#endif
		if (!delete && endspaces)
		{					/* tab char. */
			if (pend == textstart)
			{				/* do not yank single tab */
				startspaces -= endspaces;
				endspaces = textlen = 0;
				return;
			}
			else
				endspaces = incr - endspaces;
		}
	}
	textlen = (int)(pend - textstart) +1;
}

	int
doaddsub(c, Prenum1)
	u_char		c;
	linenr_t	Prenum1;
{
	register int 	col;
#ifdef JP
	char			buf[30];
	char			*bp;
#else
	char			buf[30];
#endif
	u_char			hex;		/* 'x' or 'X': hexadecimal; '0': octal */
	static int		hexupper = FALSE;	/* 0xABC */
	long			n;
	u_char			*ptr;

	ptr = (u_char *)nr2ptr(Curpos.lnum);
	col = Curpos.col;

		/* first check if we are on a hexadecimal number */
	while (col > 0 && isxdigit(ptr[col]))
		--col;
	if (col > 0 && (ptr[col] == 'X' || ptr[col] == 'x') &&
						ptr[col - 1] == '0' && isxdigit(ptr[col + 1]))
		--col;		/* found hexadecimal number */
	else
	{
		/* first search forward and then backward for start of number */
		col = Curpos.col;

		while (ptr[col] != NUL && !isdigit(ptr[col]))
			++col;

		while (col > 0 && isdigit(ptr[col - 1]))
			--col;
	}

	if (isdigit(ptr[col]) && u_saveCurpos())
	{
		set_want_col = TRUE;

		if (ptr[col] != '0')
			hex = 0;				/* decimal */
		else
		{
			hex = TO_UPPER(ptr[col + 1]);		/* assume hexadecimal */
			if (hex != 'X' || !isxdigit(ptr[col + 2]))
			{
				if (isdigit(hex))
					hex = '0';		/* octal */
				else
					hex = 0;		/* 0 by itself is decimal */
			}
		}

		if (!hex && col > 0 && ptr[col - 1] == '-')
			--col;

		ptr += col;
#ifdef JP		/* sscanf()/atol() does not work on 8-bit char */
		bp = buf;
		if (!hex && col > 0 && ptr[col - 1] == '-')
			*bp ++ = *ptr ++;
		if (hex && hex != '0')
		{
			*bp ++ = *ptr ++;
			*bp ++ = *ptr ++;
		}
		while (hex ? (hex == '0' ? *ptr >= '0' && *ptr <= '7' : isxdigit(*ptr)) : isdigit(*ptr))
			*bp ++ = *ptr ++;
		*bp = NUL;
		ptr = (u_char *)buf;
#endif
		if (hex == '0')
			sscanf((char *)ptr, "%lo", &n);
		else if (hex)
			sscanf((char *)ptr + 2, "%lx", &n);
		else
			n = atol((char *)ptr);

		if (c == Ctrl('A'))
			n += Prenum1;
		else
			n -= Prenum1;

		if (hex == 'X')					/* skip the '0x' */
			col += 2;
		Curpos.col = col;
		do								/* delete the old number */
		{
			if (isalpha(c))
			{
				if (isupper(c))
					hexupper = TRUE;
				else
					hexupper = FALSE;
			}
			delchar(FALSE);
			c = gcharCurpos();
		}
		while (hex ? (hex == '0' ? c >= '0' && c <= '7' : isxdigit(c)) : isdigit(c));

		if (hex == '0')
			sprintf(buf, "0%lo", n);
		else if (hexupper)
			sprintf(buf, "%lX", n);
		else if (hex)
			sprintf(buf, "%lx", n);
		else
			sprintf(buf, "%ld", n);
		insstr(buf);					/* insert the new number */
		--Curpos.col;
		updateline();
		return TRUE;
	}
	else
	{
		beep();
		return FALSE;
	}
}

/*
 * Return TRUE if startop is on or before the first non-blank character in the line
 */
	int
startinmargin()
{
	int		n;
	char	*ptr;

	n = 0;
	for (ptr = nr2ptr(startop.lnum); *ptr && isspace(*ptr); ++ptr)
		++n;
	return (n >= startop.col);
}



syntax highlighted by Code2HTML, v. 0.9.1