ftp.nice.ch/pub/next/unix/editor/vim-5.0f.s.tar.gz#/vim-5.0f/src/ops.c

This is ops.c in view mode; [Download] [Up]

/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
 *		  op_change, op_yank, do_put, do_join
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

/*
 * Number of registers.
 *      0 = unnamed register, for normal yanks and puts
 *   1..9 = number registers, for deletes
 * 10..35 = named registers
 *     36 = delete register (-)
 *     37 = Clipboard register (*). Only if USE_CLIPBOARD defined
 */
#ifdef USE_CLIPBOARD
# define NUM_REGISTERS			38
#else
# define NUM_REGISTERS			37
#endif

/*
 * Symbolic names for some registers.
 */
#define DELETION_REGISTER		36
#ifdef USE_CLIPBOARD
# define CLIPBOARD_REGISTER		37
#endif

/*
 * Each yank register is an array of pointers to lines.
 */
static struct yankreg
{
	char_u		**y_array;		/* pointer to array of line pointers */
	linenr_t 	y_size; 		/* number of lines in y_array */
	char_u		y_type; 		/* MLINE, MCHAR or MBLOCK */
} y_regs[NUM_REGISTERS];

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

/*
 * structure used by block_prep, op_delete and op_yank for blockwise operators
 */
struct block_def
{
	int			startspaces;
	int			endspaces;
	int			textlen;
	char_u		*textstart;
	colnr_t		textcol;
};

static char_u	*get_expr_line __ARGS((void));
static void		get_yank_register __ARGS((int regname, int writing));
static int		stuff_yank __ARGS((int, char_u *));
static void		free_yank __ARGS((long));
static void		free_yank_all __ARGS((void));
static void		block_prep __ARGS((OPARG *oap, struct block_def *, linenr_t, int));
static int		same_leader __ARGS((int, char_u *, int, char_u *));
static int		fmt_end_block __ARGS((linenr_t, int *, char_u **));

/*
 * op_shift - handle a shift operation
 */
	void
op_shift(oap, curs_top, amount)
	OPARG			*oap;
	int				curs_top;
	int				amount;
{
	long			i;
	int				first_char;

	if (u_save((linenr_t)(curwin->w_cursor.lnum - 1),
				 (linenr_t)(curwin->w_cursor.lnum + oap->line_count)) == FAIL)
		return;
	for (i = oap->line_count; --i >= 0; )
	{
		first_char = *ml_get_curline();
		if (first_char == NUL)							/* empty line */
			curwin->w_cursor.col = 0;
		/*
		 * Don't move the line right if it starts with # and p_si is set.
		 */
		else
#if defined(SMARTINDENT) || defined(CINDENT)
			if (first_char != '#' || (
# ifdef SMARTINDENT
						 !curbuf->b_p_si
# endif
# if defined(SMARTINDENT) && defined(CINDENT)
						 	&&
# endif
# ifdef CINDENT
						 (!curbuf->b_p_cin || !in_cinkeys('#', ' ', TRUE))
# endif
										))
#endif
		{
			/* if (oap->block_mode)
					shift the block, not the whole line
			else */
				shift_line(oap->op_type == OP_LSHIFT, p_sr, amount);
		}
		++curwin->w_cursor.lnum;
	}

	if (curs_top)			/* put cursor on first line, for ">>" */
	{
		curwin->w_cursor.lnum -= oap->line_count;
		beginline(MAYBE);	/* shift_line() may have changed cursor.col */
	}
	else
		--curwin->w_cursor.lnum;		/* put cursor on last line, for ":>" */
	update_topline();
	update_screen(NOT_VALID);

	if (oap->line_count > p_report)
	   smsg((char_u *)"%ld line%s %ced %d time%s", oap->line_count,
			  plural(oap->line_count), (oap->op_type == OP_RSHIFT) ? '>' : '<',
						 amount, plural((long)amount));

	/*
	 * Set "'[" and "']" marks.
	 */
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;
}

/*
 * 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, amount)
	int left;
	int	round;
	int	amount;
{
	int 	count;
	int 	i, j;
	int 	p_sw = (int)curbuf->b_p_sw;

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

	if (round)						/* round off indent */
	{
		i = count / p_sw;			/* number of p_sw rounded down */
		j = count % p_sw;			/* extra spaces */
		if (j && left)				/* first remove extra spaces */
			--amount;
		if (left)
		{
			i -= amount;
			if (i < 0)
				i = 0;
		}
		else
			i += amount;
		count = i * p_sw;
	}
	else				/* original vi indent */
	{
		if (left)
		{
			count -= p_sw * amount;
			if (count < 0)
				count = 0;
		}
		else
			count += p_sw * amount;
	}
	set_indent(count, TRUE);		/* set new indent */
}

#if defined(LISPINDENT) || defined(CINDENT)
/*
 * op_reindent - handle reindenting a block of lines for C or lisp.
 *
 * mechanism copied from op_shift, above
 */
	void
op_reindent(oap, how)
	OPARG		*oap;
	int			(*how) __ARGS((void));
{
	long   		i;
	char_u		*l;
	int			count;

	if (u_save((linenr_t)(curwin->w_cursor.lnum - 1),
				 (linenr_t)(curwin->w_cursor.lnum + oap->line_count)) == FAIL)
		return;

	for (i = oap->line_count; --i >= 0 && !got_int; )
	{
		/* it's a slow thing to do, so give feedback so there's no worry that
		 * the computer's just hung. */

		if (	   (i % 50 == 0
					|| i == oap->line_count - 1)
				&& oap->line_count > p_report)
			smsg((char_u *)"%ld line%s to indent... ", i, plural(i));

		/*
		 * Be vi-compatible: For lisp indenting the first line is not
		 * indented, unless there is only one line.
		 */
#ifdef LISPINDENT
		if (i != oap->line_count - 1 || oap->line_count == 1 ||
													   how != get_lisp_indent)
#endif
		{
			l = skipwhite(ml_get_curline());
			if (*l == NUL)					/* empty or blank line */
				count = 0;
			else
				count = how();				/* get the indent for this line */

			set_indent(count, TRUE);
		}
		++curwin->w_cursor.lnum;
	}

	/* put cursor on first non-blank of indented line */
	curwin->w_cursor.lnum -= oap->line_count;
	beginline(MAYBE);

	update_topline();
	update_screen(NOT_VALID);

	if (oap->line_count > p_report)
	{
		i = oap->line_count - (i + 1);
		smsg((char_u *)"%ld line%s indented ", i, plural(i));
	}
	/* set '[ and '] marks */
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;
}
#endif /* defined(LISPINDENT) || defined(CINDENT) */

/*
 * Keep the last expression line here, for repeating.
 */
static char_u	*expr_line = NULL;

/*
 * Get an expression for the "\"=expr1" or "CTRL-R =expr1"
 * Returns '=' when OK, NUL otherwise.
 */
	int
get_expr_register()
{
	char_u		*new_line;

	new_line = getcmdline('=', 0L, 0);
	if (new_line == NULL)
		return NUL;
	if (*new_line == NUL)		/* use previous line */
		vim_free(new_line);
	else
	{
		vim_free(expr_line);
		expr_line = new_line;
	}
	return '=';
}

/*
 * Get the result of the '=' register expression.
 * Returns a pointer to allocated memory, or NULL for failuer.
 */
	static char_u *
get_expr_line()
{
	if (expr_line == NULL)
		return NULL;
	return string_eval(expr_line);
}

/*
 * check if character is name of yank register
 * Note: There is no check for 0 (default register), caller should do this
 */
 	int
is_yank_register(regname, writing)
	int		regname;
	int		writing;		/* if TRUE check for writable registers */
{
	if (regname > '~')
		return FALSE;
	if (isalnum(regname) ||
			(!writing && vim_strchr((char_u *)".%#:=", regname) != NULL) ||
			regname == '"' || regname == '-'
#ifdef USE_CLIPBOARD
			|| (clipboard.available && regname == '*')
#endif
														)
		return TRUE;
	return FALSE;
}

/*
 * Set y_current and yankappend, according to the value of "regname".
 *
 * If regname is 0 and writing, use register 0
 * If regname is 0 and reading, use previous register
 */
	static void
get_yank_register(regname, writing)
	int		regname;
	int		writing;
{
	int 	i;

	yankappend = FALSE;
	if (((regname == 0 && !writing) || regname == '"') && y_previous != NULL)
	{
		y_current = y_previous;
		return;
	}
	i = regname;
	if (isdigit(i))
		i -= '0';
	else if (islower(i))
		i -= 'a' - 10;
	else if (isupper(i))
	{
		i -= 'A' - 10;
		yankappend = TRUE;
	}
	else if (regname == '-')
		i = DELETION_REGISTER;
#ifdef USE_CLIPBOARD
	else if (clipboard.available && regname == '*')
		i = CLIPBOARD_REGISTER;
#endif
	else				/* not 0-9, a-z, A-Z or '-': use register 0 */
		i = 0;
	y_current = &(y_regs[i]);
	if (writing)		/* remember the register we write into for do_put() */
		y_previous = y_current;
}

/*
 * return TRUE if the current yank register has type MLINE
 */
	int
yank_register_mline(regname)
	int		regname;
{
	if (regname != 0 && !is_yank_register(regname, FALSE))
		return FALSE;
	get_yank_register(regname, FALSE);
	return (y_current->y_type == MLINE);
}

/*
 * start or stop recording into a yank register
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_record(c)
	int c;
{
	char_u		*p;
	static int	regname;
	struct yankreg *old_y_previous, *old_y_current;
	int			retval;

	if (Recording == FALSE) 		/* start recording */
	{
						/* registers 0-9, a-z and " are allowed */
		if (c > '~' || (!isalnum(c) && c != '"'))
			retval = FAIL;
		else
		{
			Recording = TRUE;
			showmode();
			regname = c;
			retval = OK;
		}
	}
	else							/* stop recording */
	{
		Recording = FALSE;
		MSG("");
		p = get_recorded();
		if (p == NULL)
			retval = FAIL;
		else
		{
			/*
			 * We don't want to change the default register here, so save and
			 * restore the current register name.
			 */
			old_y_previous = y_previous;
			old_y_current = y_current;

			retval = stuff_yank(regname, p);

			y_previous = old_y_previous;
			y_current = old_y_current;
		}
	}
	return retval;
}

/*
 * stuff string 'p' into yank register 'regname' (append if uppercase)
 * 'p' must have been alloced.
 *
 * return FAIL for failure, OK otherwise
 */
	static int
stuff_yank(regname, p)
	int		regname;
	char_u	*p;
{
	char_u *lp;
	char_u **pp;

											/* check for read-only register */
	if (regname != 0 && !is_yank_register(regname, TRUE))
		return FAIL;
	get_yank_register(regname, TRUE);
	if (yankappend && y_current->y_array != NULL)
	{
		pp = &(y_current->y_array[y_current->y_size - 1]);
		lp = lalloc((long_u)(STRLEN(*pp) + STRLEN(p) + 1), TRUE);
		if (lp == NULL)
		{
			vim_free(p);
			return FAIL;
		}
		STRCPY(lp, *pp);
		STRCAT(lp, p);
		vim_free(p);
		vim_free(*pp);
		*pp = lp;
	}
	else
	{
		free_yank_all();
		if ((y_current->y_array =
						(char_u **)alloc((unsigned)sizeof(char_u *))) == NULL)
		{
			vim_free(p);
			return FAIL;
		}
		y_current->y_array[0] = p;
		y_current->y_size = 1;
		y_current->y_type = MCHAR;	/* used to be MLINE, why? */
	}
	return OK;
}

/*
 * execute a yank register: copy it into the stuff buffer
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_execreg(regname, colon, addcr)
	int 	regname;
	int		colon;				/* insert ':' before each line */
	int 	addcr;				/* always add '\n' to end of line */
{
	static int	lastc = NUL;
	long		i;
	char_u		*p;
	int			truncated;
	int			retval;


	if (regname == '@')					/* repeat previous one */
		regname = lastc;
										/* check for valid regname */
	if (regname == '%' || regname == '#' || !is_yank_register(regname, FALSE))
		return FAIL;
	lastc = regname;

	if (regname == ':')					/* use last command line */
	{
		if (last_cmdline == NULL)
		{
			EMSG(e_nolastcmd);
			return FAIL;
		}
		vim_free(new_last_cmdline);	/* don't keep the cmdline containing @: */
		new_last_cmdline = NULL;
		if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL)
			return FAIL;
		if (ins_typebuf(last_cmdline, FALSE, 0, TRUE) == FAIL)
			return FAIL;
		if (ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL)
			return FAIL;
	}
	else if (regname == '=')
	{
		p = get_expr_line();
		if (p == NULL)
			return FAIL;
		retval = ins_typebuf(p, FALSE, 0, TRUE);
		vim_free(p);
		return retval;
	}
	else if (regname == '.')			/* use last inserted text */
	{
		p = get_last_insert();
		if (p == NULL)
		{
			EMSG(e_noinstext);
			return FAIL;
		}
		i = STRLEN(p);
		if (i > 0 && p[i - 1] == ESC)	/* remove trailing ESC */
		{
			p[i - 1] = NUL;
			truncated = TRUE;
		}
		else
			truncated = FALSE;
		retval = ins_typebuf(p, FALSE, 0, TRUE);
		if (truncated)
			p[i - 1] = ESC;
		return retval;
	}
	else
	{
		get_yank_register(regname, FALSE);
		if (y_current->y_array == NULL)
			return FAIL;

		/*
		 * Insert lines into typeahead buffer, from last one to first one.
		 */
		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
																	 || addcr)
			{
				if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL)
					return FAIL;
			}
			if (ins_typebuf(y_current->y_array[i], FALSE, 0, TRUE) == FAIL)
				return FAIL;
			if (colon && ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL)
				return FAIL;
		}
		Exec_reg = TRUE;		/* disable the 'q' command */
	}
	return OK;
}

/*
 * Insert a yank register: copy it into the Read buffer.
 * Used by CTRL-R command and middle mouse button in insert mode.
 *
 * return FAIL for failure, OK otherwise
 */
	int
insert_reg(regname)
	int regname;
{
	long	i;
	int		retval = OK;
	char_u	*arg;

	/*
	 * It is possible to get into an endless loop by having CTRL-R a in
	 * register a and then, in insert mode, doing CTRL-R a.
	 * If you hit CTRL-C, the loop will be broken here.
	 */
	ui_breakcheck();
	if (got_int)
		return FAIL;

	/* check for valid regname */
	if (regname != NUL && !is_yank_register(regname, FALSE))
		return FAIL;

#ifdef USE_CLIPBOARD
	if (regname == '*')
		clip_get_selection();			/* may fill * register */
#endif

	if (regname == '.')					/* insert last inserted text */
		retval = stuff_inserted(NUL, 1L, TRUE);
	else if (regname == '%')			/* insert file name */
	{
		if (check_fname() == FAIL)
			return FAIL;
		stuffReadbuff(curbuf->b_fname);
	}
	else if (regname == '#')			/* insert alternate file name */
	{
		if ((arg = getaltfname()) == NULL)
			return FAIL;
		stuffReadbuff(arg);
	}
	else if (regname == '=')			/* insert result of expression */
	{
		if ((arg = get_expr_line()) == NULL)
			return FAIL;
		stuffReadbuff(arg);
		vim_free(arg);
	}
	else if (regname == ':')			/* insert last command line */
	{
		if (last_cmdline == NULL)
		{
			EMSG(e_nolastcmd);
			return FAIL;
		}
		stuffReadbuff(last_cmdline);
	}
	else								/* name or number register */
	{
		get_yank_register(regname, FALSE);
		if (y_current->y_array == NULL)
			retval = FAIL;
		else
		{
			for (i = 0; i < y_current->y_size; ++i)
			{
				stuffReadbuff(y_current->y_array[i]);
				/*
				 * Insert a newline between lines and after last line if
				 * y_type is MLINE.
				 */
				if (y_current->y_type == MLINE || i < y_current->y_size - 1)
					stuffReadbuff((char_u *)"\n");
			}
		}
	}

	return retval;
}

/*
 * paste a yank register into the command line.
 * used by CTRL-R command in command-line mode
 * insert_reg() can't be used here, because special characters from the
 * register contents will be interpreted as commands.
 *
 * return FAIL for failure, OK otherwise
 */
	int
cmdline_paste(regname)
	int regname;
{
	long		i;
	char_u		*arg;
	int			retval;

	if (!is_yank_register(regname, FALSE))		/* check for valid regname */
		return FAIL;

#ifdef USE_CLIPBOARD
	if (regname == '*')
		clip_get_selection();
#endif

	if (regname == '.')					/* insert last inserted text */
		return FAIL;					/* Unimplemented */

	if (regname == '%')					/* insert file name */
	{
		if (check_fname() == FAIL)
			return FAIL;
		return put_on_cmdline(curbuf->b_fname, -1, TRUE);
	}

	if (regname == '#')					/* insert alternate file name */
	{
		if ((arg = getaltfname()) == NULL)
			return FAIL;
		return put_on_cmdline(arg, -1, TRUE);
	}

	if (regname == '=')					/* insert result of expression */
	{
		if ((arg = get_expr_line()) == NULL)
			return FAIL;
		retval =  put_on_cmdline(arg, -1, TRUE);
		vim_free(arg);
		return retval;
	}

	if (regname == ':')					/* insert last command line */
	{
		if (last_cmdline == NULL)
			return FAIL;
		return put_on_cmdline(last_cmdline, -1, TRUE);
	}

	get_yank_register(regname, FALSE);
	if (y_current->y_array == NULL)
		return FAIL;

	for (i = 0; i < y_current->y_size; ++i)
	{
		put_on_cmdline(y_current->y_array[i], -1, FALSE);

		/* insert ^M between lines and after last line if type is MLINE */
		if (y_current->y_type == MLINE || i < y_current->y_size - 1)
			put_on_cmdline((char_u *)"\r", 1, FALSE);
	}
	return OK;
}

/*
 * op_delete - handle a delete operation
 */
	void
op_delete(oap)
	OPARG	*oap;
{
	int					n;
	linenr_t			lnum;
	char_u				*ptr;
	char_u				*newp, *oldp;
	linenr_t			old_lcount = curbuf->b_ml.ml_line_count;
	int					did_yank = FALSE;
	struct block_def	bd;

	if (curbuf->b_ml.ml_flags & ML_EMPTY)			/* nothing to do */
		return;

/*
 * Imitate the strange Vi behaviour: If the delete spans more than one line
 * and motion_type == MCHAR and the result is a blank line, make the delete
 * linewise.  Don't do this for the change command.
 */
	if (	   oap->motion_type == MCHAR
			&& oap->line_count > 1
			&& oap->op_type == OP_DELETE)
	{
		ptr = ml_get(oap->end.lnum) + oap->end.col + oap->inclusive;
		ptr = skipwhite(ptr);
		if (*ptr == NUL && inindent(0))
			oap->motion_type = MLINE;
	}

/*
 * Check for trying to delete (e.g. "D") in an empty line.
 * Note: For the change operator it is ok.
 */
	if (	   oap->motion_type == MCHAR
			&& oap->line_count == 1
			&& oap->op_type == OP_DELETE
			&& *ml_get(oap->start.lnum) == NUL)
	{
		beep_flush();
		return;
	}

/*
 * Do a yank of whatever we're about to delete.
 * If a yank register was specified, put the deleted text into that register
 */
	if (oap->regname != 0)
	{
										/* check for read-only register */
		if (!is_yank_register(oap->regname, TRUE))
		{
			beep_flush();
			return;
		}
		get_yank_register(oap->regname, TRUE);	/* yank into specified reg. */
		if (op_yank(oap, TRUE, FALSE) == OK)	/* yank without message */
			did_yank = TRUE;
	}

/*
 * Put deleted text into register 1 and shift number registers if
 * the delete contains a line break, or when a regname has been specified!
 */
	if (oap->regname != 0 || oap->motion_type == MLINE || oap->line_count > 1)
	{
		y_current = &y_regs[9];
		free_yank_all();				/* free register nine */
		for (n = 9; n > 1; --n)
			y_regs[n] = y_regs[n - 1];
		y_previous = y_current = &y_regs[1];
		y_regs[1].y_array = NULL;		/* set register one to empty */
		oap->regname = 0;
	}
	else if (oap->regname == 0)			/* yank into unnamed register */
	{
		oap->regname = '-';				/* use special delete register */
		get_yank_register(oap->regname, TRUE);
		oap->regname = 0;
	}

	if (oap->regname == 0 && op_yank(oap, TRUE, FALSE) == OK)
		did_yank = TRUE;

/*
 * If there's too much stuff to fit in the yank register, 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 (!did_yank)
	{
		if (ask_yesno((char_u *)"cannot yank; delete anyway", TRUE) != 'y')
		{
			emsg(e_abort);
			return;
		}
	}

/*
 * block mode delete
 */
	if (oap->block_mode)
	{
		if (u_save((linenr_t)(oap->start.lnum - 1),
							   (linenr_t)(oap->end.lnum + 1)) == FAIL)
			return;

		for (lnum = curwin->w_cursor.lnum;
							   curwin->w_cursor.lnum <= oap->end.lnum;
													  ++curwin->w_cursor.lnum)
		{
			block_prep(oap, &bd, curwin->w_cursor.lnum, TRUE);
			if (bd.textlen == 0)		/* nothing to delete */
				continue;

			/* n == number of chars deleted
			 * If we delete a TAB, it may be replaced by several characters.
			 * Thus the number of characters may increase!
			 */
			n = bd.textlen - bd.startspaces - bd.endspaces;
			oldp = ml_get_curline();
			newp = alloc_check((unsigned)STRLEN(oldp) + 1 - n);
			if (newp == NULL)
				continue;
			/* copy up to deleted part */
			vim_memmove(newp, oldp, (size_t)bd.textcol);
			/* insert spaces */
			copy_spaces(newp + bd.textcol,
									 (size_t)(bd.startspaces + bd.endspaces));
			/* copy the part after the deleted part */
			oldp += bd.textcol + bd.textlen;
			vim_memmove(newp + bd.textcol + bd.startspaces + bd.endspaces,
													  oldp, STRLEN(oldp) + 1);
			/* replace the line */
			ml_replace(curwin->w_cursor.lnum, newp, FALSE);
		}

		curwin->w_cursor.lnum = lnum;
		changed_cline_bef_curs();		/* recompute cursor pos. on screen */
		approximate_botline();			/* w_botline may be wrong now */
		adjust_cursor();

		CHANGED;
		update_screen(VALID_TO_CURSCHAR);
		oap->line_count = 0;		/* no lines deleted */
	}
	else if (oap->motion_type == MLINE)
	{
		if (oap->op_type == OP_CHANGE)
		{
				/* Delete the lines except the first one.
				 * Temporarily move the cursor to the next line.
				 * Save the current line number, if the last line is deleted
				 * it may be changed.
				 */
			if (oap->line_count > 1)
			{
				lnum = curwin->w_cursor.lnum;
				++curwin->w_cursor.lnum;
				del_lines((long)(oap->line_count - 1), TRUE, TRUE);
				curwin->w_cursor.lnum = lnum;
			}
			if (u_save_cursor() == FAIL)
				return;
			if (curbuf->b_p_ai)				/* don't delete indent */
			{
				beginline(TRUE);			/* put cursor on first non-white */
				did_ai = TRUE;				/* delete the indent when ESC hit */
			}
			truncate_line(FALSE);
			if (curwin->w_cursor.col > 0)
				--curwin->w_cursor.col;		/* put cursor on last char in line */
		}
		else
		{
			del_lines(oap->line_count, TRUE, TRUE);
		}
		u_clearline();	/* "U" command should not be possible after "dd" */
		beginline(TRUE);
	}
	else if (oap->line_count == 1)		/* delete characters within one line */
	{
		if (u_save_cursor() == FAIL)
			return;
			/* if 'cpoptions' contains '$', display '$' at end of change */
		if (	   vim_strchr(p_cpo, CPO_DOLLAR) != NULL
				&& oap->op_type == OP_CHANGE
				&& oap->end.lnum == curwin->w_cursor.lnum
				&& !oap->is_VIsual)
			display_dollar(oap->end.col - !oap->inclusive);
		n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
		(void)del_chars((long)n, TRUE);
	}
	else								/* delete characters between lines */
	{
		if (u_save_cursor() == FAIL)	/* save first line for undo */
			return;
		truncate_line(TRUE);			/* delete from cursor to end of line */

		oap->start = curwin->w_cursor;	/* remember curwin->w_cursor */
		++curwin->w_cursor.lnum;
										/* includes save for undo */
		del_lines((long)(oap->line_count - 2), TRUE, TRUE);

		if (u_save_cursor() == FAIL)	/* save last line for undo */
			return;
		/* delete from start of line until op_end */
		curwin->w_cursor.col = 0;
		(void)del_chars((long)(oap->end.col + 1 - !oap->inclusive), TRUE);
		curwin->w_cursor = oap->start;	/* restore curwin->w_cursor */
		(void)do_join(FALSE, TRUE);
	}

	/*
	 * For a change within one line, the screen is updated differently (to
	 * take care of 'dollar').
	 */
	if (oap->motion_type == MCHAR && oap->line_count == 1)
	{
		if (dollar_vcol)
			must_redraw = 0;		/* don't want a redraw now */
		else
			update_screenline();
	}
	else if (!global_busy)			/* no need to update screen for :global */
	{
		update_topline();
		update_screen(NOT_VALID);
	}

	msgmore(curbuf->b_ml.ml_line_count - old_lcount);

	/*
	 * Set "'[" and "']" marks.
	 */
	curbuf->b_op_start = oap->start;
	if (oap->block_mode)
	{
		curbuf->b_op_end.lnum = oap->end.lnum;
		curbuf->b_op_end.col = oap->start.col;
	}
	else
		curbuf->b_op_end = oap->start;
}

/*
 * op_tilde - handle the (non-standard vi) tilde operator
 */
	void
op_tilde(oap)
	OPARG		*oap;
{
	FPOS				pos;
	struct block_def	bd;

	if (u_save((linenr_t)(oap->start.lnum - 1),
									   (linenr_t)(oap->end.lnum + 1)) == FAIL)
		return;

	/*
	 * Set '[ and '] marks.
	 */
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;

	pos = oap->start;
	if (oap->block_mode)					/* Visual block mode */
	{
		for (; pos.lnum <= oap->end.lnum; ++pos.lnum)
		{
			block_prep(oap, &bd, pos.lnum, FALSE);
			pos.col = bd.textcol;
			while (--bd.textlen >= 0)
			{
				swapchar(oap->op_type, &pos);
				if (inc(&pos) == -1)		/* at end of file */
					break;
			}
		}
	}
	else									/* not block mode */
	{
		if (oap->motion_type == MLINE)
		{
			pos.col = 0;
			oap->end.col = STRLEN(ml_get(oap->end.lnum));
			if (oap->end.col)
				--oap->end.col;
		}
		else if (!oap->inclusive)
			dec(&(oap->end));

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

	if (oap->motion_type == MCHAR && oap->line_count == 1 && !oap->block_mode)
		update_screenline();
	else
	{
		update_topline();
		update_screen(NOT_VALID);
	}

	if (oap->line_count > p_report)
		smsg((char_u *)"%ld line%s ~ed",
									oap->line_count, plural(oap->line_count));
}

/*
 * If op_type == OP_UPPER: make uppercase,
 * if op_type == OP_LOWER: make lowercase,
 * else swap case of character at 'pos'
 */
	void
swapchar(op_type, pos)
	int		op_type;
	FPOS	*pos;
{
	int		c;

	c = gchar(pos);
	if (islower(c) && op_type != OP_LOWER)
	{
		pchar(*pos, TO_UPPER(c));
		CHANGED;
	}
	else if (isupper(c) && op_type != OP_UPPER)
	{
		pchar(*pos, TO_LOWER(c));
		CHANGED;
	}
}

/*
 * op_change - handle a change operation
 *
 * return TRUE if edit() returns because of a CTRL-O command
 */
	int
op_change(oap)
	OPARG		*oap;
{
	colnr_t 		   l;

	l = oap->start.col;
	if (oap->motion_type == MLINE)
	{
		l = 0;
#ifdef SMARTINDENT
		if (curbuf->b_p_si)
			can_si = TRUE;		/* It's like opening a new line, do si */
#endif
	}

	if (!oap->empty)
		op_delete(oap);			/* delete the text and take care of undo */

	if ((l > curwin->w_cursor.col) && !lineempty(curwin->w_cursor.lnum))
		inc_cursor();

#if defined(LISPINDENT) || defined(CINDENT)
	if (oap->motion_type == MLINE)
	{
# ifdef LISPINDENT
		if (curbuf->b_p_lisp && curbuf->b_p_ai)
			fixthisline(get_lisp_indent);
# endif
# if defined(LISPINDENT) && defined(CINDENT)
		else
# endif
# ifdef CINDENT
		if (curbuf->b_p_cin)
			fixthisline(get_c_indent);
# endif
	}
#endif

	return edit(NUL, FALSE, (linenr_t)1);
}

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

	for (i = 0; i < NUM_REGISTERS; ++i)
		y_regs[i].y_array = NULL;
}

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

		for (i = n; --i >= 0; )
		{
			if ((i & 1023) == 1023)					/* this may take a while */
			{
				/*
				 * This message should never cause a hit-return message.
				 * Overwrite this message with any next message.
				 */
				++no_wait_return;
				smsg((char_u *)"freeing %ld lines", i + 1);
				--no_wait_return;
				msg_didout = FALSE;
				msg_col = 0;
			}
			vim_free(y_current->y_array[i]);
		}
		vim_free(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 curwin->w_cursor and startpos into a yank register.
 * If we are to append (uppercase register), we first yank into a new yank
 * register and then concatenate the old and the new one (so we keep the old
 * one in case of out-of-memory).
 *
 * return FAIL for failure, OK otherwise
 */
	int
op_yank(oap, deleting, mess)
	OPARG	*oap;
	int 	deleting;
	int		mess;
{
	long 				y_idx;			/* index in y_array[] */
	struct yankreg		*curr;			/* copy of y_current */
	struct yankreg		newreg; 		/* new yank register when appending */
	char_u				**new_ptr;
	linenr_t			lnum;			/* current line number */
	long 				j;
	long				len;
	int					yanktype = oap->motion_type;
	long				yanklines = oap->line_count;
	linenr_t			yankendlnum = oap->end.lnum;
	char_u				*p;
	char_u				*pnew;
	struct block_def	bd;

									/* check for read-only register */
	if (oap->regname != 0 && !is_yank_register(oap->regname, TRUE))
	{
		beep_flush();
		return FAIL;
	}
	if (!deleting)					/* op_delete() already set y_current */
		get_yank_register(oap->regname, TRUE);

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

/*
 * If the cursor was in column 1 before and after the movement, and the
 * operator is not inclusive, the yank is always linewise.
 */
	if (	   oap->motion_type == MCHAR
			&& oap->start.col == 0
			&& !oap->inclusive
			&& oap->end.col == 0
			&& yanklines > 1)
	{
		yanktype = MLINE;
		--yankendlnum;
		--yanklines;
	}

	y_current->y_size = yanklines;
	y_current->y_type = yanktype;	/* set the yank register type */
	y_current->y_array = (char_u **)lalloc((long_u)(sizeof(char_u *) *
															yanklines), TRUE);

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

	y_idx = 0;
	lnum = oap->start.lnum;

/*
 * Visual block mode
 */
	if (oap->block_mode)
	{
		y_current->y_type = MBLOCK;			/* set the yank register type */
		for ( ; lnum <= yankendlnum; ++lnum)
		{
			block_prep(oap, &bd, lnum, FALSE);

			if ((pnew = alloc(bd.startspaces + bd.endspaces +
										  bd.textlen + 1)) == NULL)
				goto fail;
			y_current->y_array[y_idx++] = pnew;

			copy_spaces(pnew, (size_t)bd.startspaces);
			pnew += bd.startspaces;

			vim_memmove(pnew, bd.textstart, (size_t)bd.textlen);
			pnew += bd.textlen;

			copy_spaces(pnew, (size_t)bd.endspaces);
			pnew += bd.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 op_start and op_end, inclusive when
		 *    yanktype == MLINE
		 * 3. if yanktype != MLINE yank first part of the bot line
		 */
		if (yanktype != MLINE)
		{
			if (yanklines == 1)		/* op_start and op_end on same line */
			{
				j = oap->end.col - oap->start.col + 1 - !oap->inclusive;
				/* Watch out for very big endcol (MAXCOL) */
				p = ml_get(lnum) + oap->start.col;
				len = STRLEN(p);
				if (j > len)
					j = len;
				if ((y_current->y_array[0] = vim_strnsave(p, (int)j)) == NULL)
				{
fail:
					free_yank(y_idx);	/* free the allocated lines */
					y_current = curr;
					return FAIL;
				}
				goto success;
			}
			if ((y_current->y_array[0] =
						vim_strsave(ml_get(lnum++) + oap->start.col)) == NULL)
				goto fail;
			++y_idx;
		}

		while (yanktype == MLINE ? (lnum <= yankendlnum) : (lnum < yankendlnum))
		{
			if ((y_current->y_array[y_idx] =
										 vim_strsave(ml_get(lnum++))) == NULL)
				goto fail;
			++y_idx;
		}
		if (yanktype != MLINE)
		{
			if ((y_current->y_array[y_idx] = vim_strnsave(ml_get(yankendlnum),
								 oap->end.col + 1 - !oap->inclusive)) == NULL)
				goto fail;
		}
	}

success:
	if (curr != y_current)		/* append the new block to the old block */
	{
		new_ptr = (char_u **)lalloc((long_u)(sizeof(char_u *) *
								   (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];
		vim_free(curr->y_array);
		curr->y_array = new_ptr;

		if (yanktype == MLINE) 	/* MLINE overrides MCHAR and MBLOCK */
			curr->y_type = MLINE;

		/* concatenate the last line of the old block with the first line of
		 * the new block */
		if (curr->y_type == MCHAR)
		{
			pnew = lalloc((long_u)(STRLEN(curr->y_array[curr->y_size - 1])
							  + STRLEN(y_current->y_array[0]) + 1), TRUE);
			if (pnew == NULL)
			{
					y_idx = y_current->y_size - 1;
					goto fail;
			}
			STRCPY(pnew, curr->y_array[--j]);
			STRCAT(pnew, y_current->y_array[0]);
			vim_free(curr->y_array[j]);
			vim_free(y_current->y_array[0]);
			curr->y_array[j++] = pnew;
			y_idx = 1;
		}
		else
			y_idx = 0;
		while (y_idx < y_current->y_size)
			curr->y_array[j++] = y_current->y_array[y_idx++];
		curr->y_size = j;
		vim_free(y_current->y_array);
		y_current = curr;
	}
	if (mess)					/* Display message about yank? */
	{
		if (yanktype == MCHAR && !oap->block_mode && yanklines == 1)
			yanklines = 0;
		if (yanklines > p_report)
		{
			/* redisplay now, so message is not deleted */
			update_topline_redraw();
			smsg((char_u *)"%ld line%s yanked", yanklines, plural(yanklines));
		}
	}

	/*
	 * Set "'[" and "']" marks.
	 */
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;

	return OK;
}

/*
 * put contents of register "regname" into the text
 * For ":put" command count == -1.
 */
	void
do_put(regname, dir, count, fix_indent)
	int		regname;
	int		dir;				/* BACKWARD for 'P', FORWARD for 'p' */
	long	count;
	int		fix_indent;			/* make indent look nice */
{
	char_u		*ptr;
	char_u		*newp, *oldp;
	int 		yanklen;
	int			oldlen;
	int			totlen = 0;					/* init for gcc */
	linenr_t	lnum;
	colnr_t		col;
	long 		i;							/* index in y_array[] */
	int 		y_type;
	long 		y_size;
	char_u		**y_array;
	long 		nr_lines = 0;
	colnr_t		vcol;
	int			delcount;
	int			incr = 0;
	long		j;
	FPOS		new_cursor;
	int			indent;
	int			orig_indent = 0;			/* init for gcc */
	int			indent_diff = 0;			/* init for gcc */
	int			first_indent = TRUE;
	FPOS		old_pos;
	struct block_def bd;
	char_u		*insert_string = NULL;

#ifdef USE_CLIPBOARD
	if (regname == '*')
		clip_get_selection();
#endif

	if (fix_indent)
		orig_indent = get_indent();

	curbuf->b_op_start = curwin->w_cursor;		/* default for '[ mark */
	if (dir == FORWARD)
		curbuf->b_op_start.col++;
	curbuf->b_op_end = curwin->w_cursor;		/* default for '] mark */

	/*
	 * Using inserted text works differently, because the register includes
	 * special characters (newlines, etc.).
	 */
	if (regname == '.')
	{
		(void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') :
									(count == -1 ? 'O' : 'i')), count, FALSE);
		return;
	}

	/*
	 * For '%' (file name), '#' (alternate file name) and ':' (last command
	 * line) we have to create a fake yank register.
	 */
	if (regname == '%')				/* use file name */
	{
		if (check_fname() == FAIL)
			return;
		insert_string = curbuf->b_fname;
	}
	else if (regname == '#')			/* use alternate file name */
	{
		if ((insert_string = getaltfname()) == NULL)
			return;
	}
	else if (regname == '=')			/* use result of expression */
	{
		if ((insert_string = get_expr_line()) == NULL)
			return;
	}
	else if (regname == ':')			/* use last command line */
	{
		if (last_cmdline == NULL)
		{
			EMSG(e_nolastcmd);
			return;
		}
		insert_string = last_cmdline;
	}

	if (insert_string != NULL)
	{
		y_type = MCHAR;					/* use fake one-line yank register */
		y_size = 1;
		y_array = &insert_string;
	}
	else
	{
		get_yank_register(regname, 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)
	{
		EMSG2("Nothing in register %s",
				  regname == 0 ? (char_u *)"\"" : transchar(regname));
		return;
	}

	if (y_type == MBLOCK)
	{
		lnum = curwin->w_cursor.lnum + y_size + 1;
		if (lnum > curbuf->b_ml.ml_line_count)
			lnum = curbuf->b_ml.ml_line_count + 1;
		if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL)
			return;
	}
	else if (u_save_cursor() == FAIL)
		return;

	yanklen = STRLEN(y_array[0]);
	CHANGED;

	lnum = curwin->w_cursor.lnum;
	col = curwin->w_cursor.col;
	approximate_botline();			/* w_botline might not be valid now */
	changed_cline_bef_curs();		/* cursor posn on screen may change */

/*
 * block mode
 */
	if (y_type == MBLOCK)
	{
		if (dir == FORWARD && gchar_cursor() != NUL)
		{
			getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
			++col;
			++curwin->w_cursor.col;
		}
		else
			getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL);
		for (i = 0; i < y_size; ++i)
		{
			bd.startspaces = 0;
			bd.endspaces = 0;
			bd.textcol = 0;
			vcol = 0;
			delcount = 0;

		/* add a new line */
			if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
			{
				ml_append(curbuf->b_ml.ml_line_count, (char_u *)"",
														   (colnr_t)1, FALSE);
				++nr_lines;
			}
			oldp = ml_get_curline();
			oldlen = STRLEN(oldp);
			for (ptr = oldp; vcol < col && *ptr; ++ptr)
			{
				/* Count a tab for what it's worth (if list mode not on) */
				incr = lbr_chartabsize(ptr, (colnr_t)vcol);
				vcol += incr;
				++bd.textcol;
			}
			if (vcol < col)	/* line too short, padd with spaces */
			{
				bd.startspaces = col - vcol;
			}
			else if (vcol > col)
			{
				bd.endspaces = vcol - col;
				bd.startspaces = incr - bd.endspaces;
				--bd.textcol;
				delcount = 1;
			}
			yanklen = STRLEN(y_array[i]);
			totlen = count * yanklen + bd.startspaces + bd.endspaces;
			newp = alloc_check((unsigned)totlen + oldlen + 1);
			if (newp == NULL)
				break;
		/* copy part up to cursor to new line */
			ptr = newp;
			vim_memmove(ptr, oldp, (size_t)bd.textcol);
			ptr += bd.textcol;
		/* may insert some spaces before the new text */
			copy_spaces(ptr, (size_t)bd.startspaces);
			ptr += bd.startspaces;
		/* insert the new text */
			for (j = 0; j < count; ++j)
			{
				vim_memmove(ptr, y_array[i], (size_t)yanklen);
				ptr += yanklen;
			}
		/* may insert some spaces after the new text */
			copy_spaces(ptr, (size_t)bd.endspaces);
			ptr += bd.endspaces;
		/* move the text after the cursor to the end of the line. */
			vim_memmove(ptr, oldp + bd.textcol + delcount,
								(size_t)(oldlen - bd.textcol - delcount + 1));
			ml_replace(curwin->w_cursor.lnum, newp, FALSE);

			++curwin->w_cursor.lnum;
			if (i == 0)
				curwin->w_cursor.col += bd.startspaces;
		}
												/* adjust '] mark */
		curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1;
		curbuf->b_op_end.col = bd.textcol + totlen - 1;
		curwin->w_cursor.lnum = lnum;
		update_topline();
		update_screen(VALID_TO_CURSCHAR);
	}
	else		/* not block mode */
	{
		if (y_type == MCHAR)
		{
	/* if type is MCHAR, FORWARD is the same as BACKWARD on the next char */
			if (dir == FORWARD && gchar_cursor() != NUL)
			{
				++col;
				if (yanklen)
				{
					++curwin->w_cursor.col;
					++curbuf->b_op_end.col;
				}
			}
			new_cursor = curwin->w_cursor;
		}
		else if (dir == BACKWARD)
	/* if type is MLINE, BACKWARD is the same as FORWARD on the previous line */
			--lnum;

/*
 * simple case: insert into current line
 */
		if (y_type == MCHAR && y_size == 1)
		{
			totlen = count * yanklen;
			if (totlen)
			{
				oldp = ml_get(lnum);
				newp = alloc_check((unsigned)(STRLEN(oldp) + totlen + 1));
				if (newp == NULL)
					return; 			/* alloc() will give error message */
				vim_memmove(newp, oldp, (size_t)col);
				ptr = newp + col;
				for (i = 0; i < count; ++i)
				{
					vim_memmove(ptr, y_array[0], (size_t)yanklen);
					ptr += yanklen;
				}
				vim_memmove(ptr, oldp + col, STRLEN(oldp + col) + 1);
				ml_replace(lnum, newp, FALSE);
										  /* put cursor on last putted char */
				curwin->w_cursor.col += (colnr_t)(totlen - 1);
			}
			curbuf->b_op_end = curwin->w_cursor;
			update_screenline();
		}
		else
		{
			while (--count >= 0)
			{
				i = 0;
				if (y_type == MCHAR)
				{
					/*
					 * Split the current line in two at the insert position.
					 * First insert y_array[size - 1] in front of second line.
					 * Then append y_array[0] to first line.
					 */
					ptr = ml_get(lnum) + col;
					totlen = STRLEN(y_array[y_size - 1]);
					newp = alloc_check((unsigned)(STRLEN(ptr) + totlen + 1));
					if (newp == NULL)
						goto error;
					STRCPY(newp, y_array[y_size - 1]);
					STRCAT(newp, ptr);
						/* insert second line */
					ml_append(lnum, newp, (colnr_t)0, FALSE);
					vim_free(newp);

					oldp = ml_get(lnum);
					newp = alloc_check((unsigned)(col + yanklen + 1));
					if (newp == NULL)
						goto error;
											/* copy first part of line */
					vim_memmove(newp, oldp, (size_t)col);
											/* append to first line */
					vim_memmove(newp + col, y_array[0], (size_t)(yanklen + 1));
					ml_replace(lnum, newp, FALSE);

					curwin->w_cursor.lnum = lnum;
					i = 1;
				}

				while (i < y_size)
				{
					if ((y_type != MCHAR || i < y_size - 1) &&
						ml_append(lnum, y_array[i], (colnr_t)0, FALSE) == FAIL)
							goto error;
					lnum++;
					i++;
					if (fix_indent)
					{
						old_pos = curwin->w_cursor;
						curwin->w_cursor.lnum = lnum;
						ptr = ml_get(lnum);
#if defined(SMARTINDENT) || defined(CINDENT)
						if (*ptr == '#'
# ifdef SMARTINDENT
						   && curbuf->b_p_si
# endif
# ifdef CINDENT
						   && curbuf->b_p_cin && in_cinkeys('#', ' ', TRUE)
# endif
											)
							indent = 0;     /* Leave # lines at start */
						else
#endif
						     if (*ptr == NUL)
							indent = 0;     /* Ignore empty lines */
						else if (first_indent)
						{
							indent_diff = orig_indent - get_indent();
							indent = orig_indent;
							first_indent = FALSE;
						}
						else if ((indent = get_indent() + indent_diff) < 0)
							indent = 0;
						set_indent(indent, TRUE);
						curwin->w_cursor = old_pos;
					}
					++nr_lines;
				}
			}

			/* put '] mark at last inserted character */
			curbuf->b_op_end.lnum = lnum;
			col = STRLEN(y_array[y_size - 1]);
			if (col > 1)
				curbuf->b_op_end.col = col - 1;
			else
				curbuf->b_op_end.col = 0;

			if (y_type == MLINE)
			{
				/* put cursor onfirst non-blank in first inserted line */
				curwin->w_cursor.col = 0;
				if (dir == FORWARD)
					++curwin->w_cursor.lnum;
				beginline(TRUE);
			}
			else		/* put cursor on first inserted character */
			{
				curwin->w_cursor = new_cursor;
			}

error:
			if (y_type == MLINE)		/* adjust '[ mark */
			{
				curbuf->b_op_start.col = 0;
				if (dir == FORWARD)
					curbuf->b_op_start.lnum++;
			}
			mark_adjust(curbuf->b_op_start.lnum + (y_type == MCHAR),
													   MAXLNUM, nr_lines, 0L);
			update_topline();
			update_screen(NOT_VALID);
		}
	}

	msgmore(nr_lines);
	curwin->w_set_curswant = TRUE;
}

/* Return the character name of the register with the given number */
	int
get_register_name(num)
	int num;
{
	if (num == -1)
		return '"';
	else if (num < 10)
		return num + '0';
	else if (num == DELETION_REGISTER)
		return '-';
#ifdef USE_CLIPBOARD
	else if (num == CLIPBOARD_REGISTER)
		return '*';
#endif
	else
		return num + 'a' - 10;
}

/*
 * display the contents of the yank registers
 */
	void
do_dis(arg)
	char_u *arg;
{
	int				i, n;
	long			j;
	char_u			*p;
	struct yankreg	*yb;
	char_u 			name;
	int				attr;

	if (arg != NULL && *arg == NUL)
		arg = NULL;
	attr = highlight_attr[HLF_8];

	/* Highlight title */
	MSG_PUTS_TITLE("\n--- Registers ---");
	for (i = -1; i < NUM_REGISTERS; ++i)
	{
		if (i == -1)
		{
			if (y_previous != NULL)
				yb = y_previous;
			else
				yb = &(y_regs[0]);
		}
		else
			yb = &(y_regs[i]);
		name = get_register_name(i);
		if (yb->y_array != NULL && (arg == NULL ||
											   vim_strchr(arg, name) != NULL))
		{
			msg_putchar('\n');
			msg_putchar('"');
			msg_putchar(name);
			MSG_PUTS("   ");

			n = (int)Columns - 6;
			for (j = 0; j < yb->y_size && n > 1; ++j)
			{
				if (j)
				{
					MSG_PUTS_ATTR("^J", attr);
					n -= 2;
				}
				for (p = yb->y_array[j]; *p && (n -= charsize(*p)) >= 0; ++p)
					msg_outtrans_len(p, 1);
			}
			if (n > 1 && yb->y_type == MLINE)
				MSG_PUTS_ATTR("^J", attr);
			flushbuf();				/* show one line at a time */
		}
	}

	/*
	 * display last inserted text
	 */
	if ((p = get_last_insert()) != NULL &&
		(arg == NULL || vim_strchr(arg, '.') != NULL))
	{
		MSG_PUTS("\n\".   ");
		dis_msg(p, TRUE);
	}

	/*
	 * display last command line
	 */
	if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL))
	{
		MSG_PUTS("\n\":   ");
		dis_msg(last_cmdline, FALSE);
	}

	/*
	 * display current file name
	 */
	if (curbuf->b_fname != NULL &&
								(arg == NULL || vim_strchr(arg, '%') != NULL))
	{
		MSG_PUTS("\n\"%   ");
		dis_msg(curbuf->b_fname, FALSE);
	}

	/*
	 * display alternate file name
	 */
	if (arg == NULL || vim_strchr(arg, '%') != NULL)
	{
		char_u		*fname;
		linenr_t	dummy;

		if (buflist_name_nr(0, &fname, &dummy) != FAIL)
		{
			MSG_PUTS("\n\"#   ");
			dis_msg(fname, FALSE);
		}
	}

	/*
	 * display last used expression
	 */
	if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL))
	{
		MSG_PUTS("\n\"=   ");
		dis_msg(expr_line, FALSE);
	}
}

/*
 * display a string for do_dis()
 * truncate at end of screen line
 */
	void
dis_msg(p, skip_esc)
	char_u		*p;
	int			skip_esc;			/* if TRUE, ignore trailing ESC */
{
	int		n;

	n = (int)Columns - 6;
	while (*p && !(*p == ESC && skip_esc && *(p + 1) == NUL) &&
						(n -= charsize(*p)) >= 0)
		msg_outtrans_len(p++, 1);
}

/*
 * join 'count' lines (minimal 2), including u_save()
 */
	void
do_do_join(count, insert_space, redraw)
	long	count;
	int		insert_space;
	int		redraw;					/* can redraw, curwin->w_wcol valid */
{
	if (u_save((linenr_t)(curwin->w_cursor.lnum - 1),
					(linenr_t)(curwin->w_cursor.lnum + count)) == FAIL)
		return;

	if (count > 10)
		redraw = FALSE;				/* don't redraw each small change */
	while (--count > 0)
	{
		line_breakcheck();
		if (got_int || do_join(insert_space, redraw) == FAIL)
		{
			beep_flush();
			break;
		}
	}
	redraw_later(VALID_TO_CURSCHAR);

	/*
	 * Need to update the screen if the line where the cursor is became too
	 * long to fit on the screen.
	 */
	update_topline_redraw();
}

/*
 * Join two lines at the cursor position.
 * "redraw" is TRUE when the screen should be updated.
 *
 * return FAIL for failure, OK ohterwise
 */
	int
do_join(insert_space, redraw)
	int			insert_space;
	int			redraw;
{
	char_u		*curr;
	char_u		*next;
	char_u		*newp;
	int			endcurr1, endcurr2;
	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 = 0;/* number of rows on screen to delete */
	linenr_t	t;

	if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count)
		return FAIL;			/* can't join on last line */

	if (redraw)
	{
		/*
		 * Check if we can really redraw:  w_cline_row and w_cline_height need
		 * to be valid.  Try to make them valid by calling may_validate_crow()
		 */
		if (may_validate_crow() == OK)
			rows_to_del = plines_m(curwin->w_cursor.lnum,
												   curwin->w_cursor.lnum + 1);
		else
			redraw = FALSE;
	}

	curr = ml_get_curline();
	currsize = STRLEN(curr);
	endcurr1 = endcurr2 = NUL;
	if (currsize > 0)
	{
		endcurr1 = *(curr + currsize - 1);
		if (currsize > 1)
			endcurr2 = *(curr + currsize - 2);
	}

	next = ml_get((linenr_t)(curwin->w_cursor.lnum + 1));
	spaces = 0;
	if (insert_space)
	{
		next = skipwhite(next);
		spaces = 1;
		if (*next == ')' || currsize == 0)
			spaces = 0;
		else
		{
			if (endcurr1 == ' ' || endcurr1 == TAB)
			{
				spaces = 0;
				if (currsize > 1)
					endcurr1 = endcurr2;
			}
			if (p_js && vim_strchr((char_u *)".!?", endcurr1) != NULL)
				spaces = 2;
		}
	}
	nextsize = STRLEN(next);

	newp = alloc_check((unsigned)(currsize + nextsize + spaces + 1));
	if (newp == NULL)
		return FAIL;

	/*
	 * Insert the next line first, because we already have that pointer.
	 * Curr has to be obtained again, because getting next will have
	 * invalidated it.
	 */
	vim_memmove(newp + currsize + spaces, next, (size_t)(nextsize + 1));

	curr = ml_get_curline();
	vim_memmove(newp, curr, (size_t)currsize);

	copy_spaces(newp + currsize, (size_t)spaces);

	ml_replace(curwin->w_cursor.lnum, newp, FALSE);

	/*
	 * Delete the following line. To do this we move the cursor there
	 * briefly, and then move it back. After del_lines() the cursor may
	 * have moved up (last line deleted), so the current lnum is kept in t.
	 */
	t = curwin->w_cursor.lnum;
	++curwin->w_cursor.lnum;
	del_lines(1L, FALSE, FALSE);
	curwin->w_cursor.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(curwin->w_cursor.lnum);
		if (rows_to_del > 0)
			win_del_lines(curwin, curwin->w_cline_row + curwin->w_cline_height,
													 rows_to_del, TRUE, TRUE);
	}

 	/*
	 * go to first character of the joined line
	 */
	if (currsize == 0)
		curwin->w_cursor.col = 0;
	else
	{
		curwin->w_cursor.col = currsize - 1;
		(void)oneright();
	}
	CHANGED;

	return OK;
}

/*
 * Return TRUE if the two comment leaders given are the same.  The cursor is
 * in the first line.  White-space is ignored.  Note that the whole of
 * 'leader1' must match 'leader2_len' characters from 'leader2' -- webb
 */
	static int
same_leader(leader1_len, leader1_flags, leader2_len, leader2_flags)
	int		leader1_len;
	char_u	*leader1_flags;
	int		leader2_len;
	char_u	*leader2_flags;
{
	int		idx1 = 0, idx2 = 0;
	char_u	*p;
	char_u	*line1;
	char_u	*line2;

	if (leader1_len == 0)
		return (leader2_len == 0);

	/*
	 * If first leader has 'f' flag, the lines can be joined only if the
	 * second line does not have a leader.
	 * If first leader has 'e' flag, the lines can never be joined.
	 * If fist leader has 's' flag, the lines can only be joined if there is
	 * some text after it and the second line has the 'm' flag.
	 */
	if (leader1_flags != NULL)
	{
		for (p = leader1_flags; *p && *p != ':'; ++p)
		{
			if (*p == COM_FIRST)
				return (leader2_len == 0);
			if (*p == COM_END)
				return FALSE;
			if (*p == COM_START)
			{
				if (*(ml_get_curline() + leader1_len) == NUL)
					return FALSE;
				if (leader2_flags == NULL || leader2_len == 0)
					return FALSE;
				for (p = leader2_flags; *p && *p != ':'; ++p)
					if (*p == COM_MIDDLE)
						return TRUE;
				return FALSE;
			}
		}
	}

	/*
	 * Get current line and next line, compare the leaders.
	 * The first line has to be saved, only one line can be locked at a time.
	 */
	line1 = vim_strsave(ml_get_curline());
	if (line1 != NULL)
	{
		for (idx1 = 0; vim_iswhite(line1[idx1]); ++idx1)
			;
		line2 = ml_get(curwin->w_cursor.lnum + 1);
		for (idx2 = 0; idx2 < leader2_len; ++idx2)
		{
			if (!vim_iswhite(line2[idx2]))
			{
				if (line1[idx1++] != line2[idx2])
					break;
			}
			else
				while (vim_iswhite(line1[idx1]))
					++idx1;
		}
		vim_free(line1);
	}
	return (idx2 == leader2_len && idx1 == leader1_len);
}

/*
 * implementation of the format operator 'Q'
 */
	void
op_format(oap)
	OPARG		*oap;
{
	long		old_line_count = curbuf->b_ml.ml_line_count;
	int			prev_is_blank = FALSE;
	int			is_end_block = TRUE;
	int			next_is_end_block;
	int			leader_len = 0;		/* init for gcc */
	int			next_leader_len;
	char_u		*leader_flags = NULL;
	char_u		*next_leader_flags;
	int			advance = TRUE;
	int			second_indent = -1;
	int			do_second_indent;
	int			first_par_line = TRUE;
	int			smd_save;
	long		count;

	if (u_save((linenr_t)(oap->start.lnum - 1),
									   (linenr_t)(oap->end.lnum + 1)) == FAIL)
		return;

	/* Set '[ mark at the start of the formatted area */
	curbuf->b_op_start = oap->start;

	/* check for 'q' and '2' in 'formatoptions' */
	fo_do_comments = has_format_option(FO_Q_COMS);
	do_second_indent = has_format_option(FO_Q_SECOND);

	/*
	 * get info about the previous and current line.
	 */
	if (curwin->w_cursor.lnum > 1)
		is_end_block = fmt_end_block(curwin->w_cursor.lnum - 1,
										&next_leader_len, &next_leader_flags);
	next_is_end_block = fmt_end_block(curwin->w_cursor.lnum,
										&next_leader_len, &next_leader_flags);

	curwin->w_cursor.lnum--;
	for (count = oap->line_count; count > 0; --count)
	{
		/*
		 * Advance to next block.
		 */
		if (advance)
		{
			curwin->w_cursor.lnum++;
			prev_is_blank = is_end_block;
			is_end_block = next_is_end_block;
			leader_len = next_leader_len;
			leader_flags = next_leader_flags;
		}

		/*
		 * The last line to be formatted.
		 */
		if (count == 1)
		{
			next_is_end_block = TRUE;
			next_leader_len = 0;
			next_leader_flags = NULL;
		}
		else
			next_is_end_block = fmt_end_block(curwin->w_cursor.lnum + 1,
										&next_leader_len, &next_leader_flags);
		advance = TRUE;

		/*
		 * For the first line of a paragraph, check indent of second line.
		 * Don't do this for comments and empty lines.
		 */
		if (first_par_line && do_second_indent &&
				prev_is_blank && !is_end_block &&
				curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count &&
				leader_len == 0 && next_leader_len == 0 &&
				!lineempty(curwin->w_cursor.lnum + 1))
			second_indent = get_indent_lnum(curwin->w_cursor.lnum + 1);

		/*
		 * Skip end-of-block (blank) lines
		 */
		if (is_end_block)
		{
		}
		/*
		 * If we have got to the end of a paragraph, format it.
		 */
		else if (next_is_end_block || !same_leader(leader_len, leader_flags,
										  next_leader_len, next_leader_flags))
		{
			/* replace indent in first line with minimal number of tabs and
			 * spaces, according to current options */
			set_indent(get_indent(), TRUE);

			/* put cursor on last non-space */
			coladvance(MAXCOL);
			while (curwin->w_cursor.col && vim_isspace(gchar_cursor()))
				dec_cursor();

			/* do the formatting, without 'showmode' */
			State = INSERT;		/* for open_line() */
			smd_save = p_smd;
			p_smd = FALSE;
			insertchar(NUL, TRUE, second_indent);
			State = NORMAL;
			p_smd = smd_save;
			first_par_line = TRUE;
			second_indent = -1;
		}
		else
		{
			/*
			 * Still in same paragraph, so join the lines together.
			 * But first delete the comment leader from the second line.
			 */
			advance = FALSE;
			curwin->w_cursor.lnum++;
			curwin->w_cursor.col = 0;
			(void)del_chars((long)next_leader_len, FALSE);
			curwin->w_cursor.lnum--;
			if (do_join(TRUE, FALSE) == FAIL)
			{
				beep_flush();
				break;
			}
			first_par_line = FALSE;
		}
	}
	fo_do_comments = FALSE;
	/*
	 * Leave the cursor at the first non-blank of the last formatted line.
	 * If the cursor was move one line back (e.g. with "Q}") go to the next
	 * line, so "." will do the next lines.
	 */
	if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
		++curwin->w_cursor.lnum;
	beginline(TRUE);
	update_screen(NOT_VALID);
	msgmore(curbuf->b_ml.ml_line_count - old_line_count);

	/* put '] mark on the end of the formatted area */
	curbuf->b_op_end = curwin->w_cursor;
}

/*
 * Blank lines, and lines containing only the comment leader, are left
 * untouched by the formatting.  The function returns TRUE in this
 * case.  It also returns TRUE when a line starts with the end of a comment
 * ('e' in comment flags), so that this line is skipped, and not joined to the
 * previous line.  A new paragraph starts after a blank line, or when the
 * comment leader changes -- webb.
 */
	static int
fmt_end_block(lnum, leader_len, leader_flags)
	linenr_t	lnum;
	int			*leader_len;
	char_u		**leader_flags;
{
	char_u		*flags = NULL;		/* init for GCC */
	char_u		*ptr;

	ptr = ml_get(lnum);
	*leader_len = get_leader_len(ptr, leader_flags);

	if (*leader_len > 0)
	{
		/*
		 * Search for 'e' flag in comment leader flags.
		 */
		flags = *leader_flags;
		while (*flags && *flags != ':' && *flags != COM_END)
			++flags;
	}

	return (ptr[*leader_len] == NUL ||
			(*leader_len > 0 && *flags == COM_END) ||
			 startPS(lnum, NUL, FALSE));
}

/*
 * 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(oap, bd, lnum, is_del)
	OPARG				*oap;
	struct block_def	*bd;
	linenr_t			lnum;
	int					is_del;
{
	colnr_t		vcol;
	int			incr = 0;
	char_u		*pend;
	char_u		*pstart;

	bd->startspaces = 0;
	bd->endspaces = 0;
	bd->textlen = 0;
	bd->textcol = 0;
	vcol = 0;
	pstart = ml_get(lnum);
	while (vcol < oap->start_vcol && *pstart)
	{
		/* Count a tab for what it's worth (if list mode not on) */
		incr = lbr_chartabsize(pstart, (colnr_t)vcol);
		vcol += incr;
		++pstart;
		++bd->textcol;
	}
	if (vcol < oap->start_vcol)	/* line too short */
	{
		if (!is_del)
			bd->endspaces = oap->end_vcol - oap->start_vcol + 1;
	}
	else /* vcol >= oap->start_vcol */
	{
		bd->startspaces = vcol - oap->start_vcol;
		if (is_del && vcol > oap->start_vcol)
			bd->startspaces = incr - bd->startspaces;
		pend = pstart;
		if (vcol > oap->end_vcol)		/* it's all in one character */
		{
			bd->startspaces = oap->end_vcol - oap->start_vcol + 1;
			if (is_del)
				bd->startspaces = incr - bd->startspaces;
		}
		else
		{
			while (vcol <= oap->end_vcol && *pend)
			{
				/* Count a tab for what it's worth (if list mode not on) */
				incr = lbr_chartabsize(pend, (colnr_t)vcol);
				vcol += incr;
				++pend;
			}
			if (vcol < oap->end_vcol && !is_del)	/* line too short */
			{
				bd->endspaces = oap->end_vcol - vcol;
			}
			else if (vcol > oap->end_vcol)
			{
				bd->endspaces = vcol - oap->end_vcol - 1;
				if (!is_del && pend != pstart && bd->endspaces)
					--pend;
			}
		}
		if (is_del && bd->startspaces)
		{
			--pstart;
			--bd->textcol;
		}
		bd->textlen = (int)(pend - pstart);
	}
	bd->textstart = pstart;
}

/*
 * add or subtract 'Prenum1' from a number in a line
 * 'command' is CTRL-A for add, CTRL-X for subtract
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_addsub(command, Prenum1)
	int			command;
	linenr_t	Prenum1;
{
	int 			col;
	char_u			buf1[NUMBUFLEN];
	char_u			buf2[NUMBUFLEN];
	int				hex;				/* 'X' or 'x': hex; '0': octal */
	static int		hexupper = FALSE;	/* 0xABC */
	long			n;
	char_u			*ptr;
	int				c;
	int				length = 0;			/* character length of the number */
	int				todel;
	int				dohex;
	int				dooct;
	int				firstdigit;

	dohex = (vim_strchr(curbuf->b_p_nf, 'x') != NULL);
	dooct = (vim_strchr(curbuf->b_p_nf, 'o') != NULL);

	ptr = ml_get_curline();

	/*
	 * First check if we are on a hexadecimal number, after the "0x".
	 */
	col = curwin->w_cursor.col;
	if (dohex)
		while (col > 0 && isxdigit(ptr[col]))
			--col;
	if (	   dohex
			&& col > 0
			&& (ptr[col] == 'X'
				|| ptr[col] == 'x')
			&& ptr[col - 1] == '0'
			&& isxdigit(ptr[col + 1]))
	{
		/*
		 * Found hexadecimal number, move to its start.
		 */
		--col;
	}
	else
	{
		/*
		 * Search forward and then backward to find the start of number.
		 */
		col = curwin->w_cursor.col;

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

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

	/*
	 * If a number was found, and saving for undo works, replace the number.
	 */
	firstdigit = ptr[col];
	if (!isdigit(firstdigit) || u_save_cursor() != OK)
	{
		beep_flush();
		return FAIL;
	}

	/* get ptr again, because u_save() may have changed it */
	ptr = ml_get_curline();

	if (col > 0 && ptr[col - 1] == '-')		/* negative number */
		--col;
											/* get the number value */
	n = str2nr(ptr + col, &hex, &length, dooct, dohex);

	if (command == Ctrl('A'))				/* add or subtract */
		n += Prenum1;
	else
		n -= Prenum1;

	/*
	 * Delete the old number.
	 */
	curwin->w_cursor.col = col;
	todel = length;
	c = gchar_cursor();
	/*
	 * Don't include the '-' in the length, only the length of the part
	 * after it is kept the same.
	 */
	if (c == '-')
		--length;
	while (todel-- > 0)
	{
		if (isalpha(c))
		{
			if (isupper(c))
				hexupper = TRUE;
			else
				hexupper = FALSE;
		}
		(void)del_char(FALSE);
		c = gchar_cursor();
	}

	/*
	 * Prepare the leading characters in buf1[].
	 */
	ptr = buf1;
	if (n < 0)
	{
		*ptr++ = '-';
		n = -n;
	}
	if (hex)
	{
		*ptr++ = '0';
		--length;
	}
	if (hex == 'x' || hex == 'X')
	{
		*ptr++ = hex;
		--length;
	}

	/*
	 * Put the number characters in buf2[].
	 */
	if (hex == 0)
		sprintf((char *)buf2, "%ld", n);
	else if (hex == '0')
		sprintf((char *)buf2, "%lo", n);
	else if (hex && hexupper)
		sprintf((char *)buf2, "%lX", n);
	else
		sprintf((char *)buf2, "%lx", n);
	length -= STRLEN(buf2);

	/*
	 * adjust number of zeros to the new number of digits, so the
	 * total length of the number remains the same
	 */
	if (firstdigit == '0')
		while (length-- > 0)
			*ptr++ = '0';
	*ptr = NUL;
	STRCAT(buf1, buf2);
	ins_str(buf1);					/* insert the new number */
	--curwin->w_cursor.col;
	curwin->w_set_curswant = TRUE;
	update_screenline();
	return OK;
}

#ifdef VIMINFO
	int
read_viminfo_register(line, fp, force)
	char_u	*line;
	FILE	*fp;
	int		force;
{
	int		eof;
	int		do_it = TRUE;
	int		size;
	int		limit;
	int		i;
	int		set_prev = FALSE;
	char_u	*str;
	char_u	**array = NULL;

	/* We only get here (hopefully) if line[0] == '"' */
	str = line + 1;
	if (*str == '"')
	{
		set_prev = TRUE;
		str++;
	}
	if (!isalnum(*str) && *str != '-')
	{
		if (viminfo_error("Illegal register name", line))
			return TRUE;		/* too many errors, pretend end-of-file */
		do_it = FALSE;
	}
	get_yank_register(*str++, FALSE);
	if (!force && y_current->y_array != NULL)
		do_it = FALSE;
	size = 0;
	limit = 100;		/* Optimized for registers containing <= 100 lines */
	if (do_it)
	{
		if (set_prev)
			y_previous = y_current;
		vim_free(y_current->y_array);
		array = y_current->y_array =
					   (char_u **)alloc((unsigned)(limit * sizeof(char_u *)));
		str = skipwhite(str);
		if (STRNCMP(str, "CHAR", 4) == 0)
			y_current->y_type = MCHAR;
		else if (STRNCMP(str, "BLOCK", 5) == 0)
			y_current->y_type = MBLOCK;
		else
			y_current->y_type = MLINE;
	}
	while (!(eof = vim_fgets(line, LSIZE, fp)) && line[0] == TAB)
	{
		if (do_it)
		{
			if (size >= limit)
			{
				y_current->y_array = (char_u **)
							  alloc((unsigned)(limit * 2 * sizeof(char_u *)));
				for (i = 0; i < limit; i++)
					y_current->y_array[i] = array[i];
				vim_free(array);
				limit *= 2;
				array = y_current->y_array;
			}
			viminfo_readstring(line);
			str = vim_strsave(line + 1);
			if (str != NULL)
				array[size++] = str;
			else
				do_it = FALSE;
		}
	}
	if (do_it)
	{
		if (size == 0)
		{
			vim_free(array);
			y_current->y_array = NULL;
		}
		else if (size < limit)
		{
			y_current->y_array =
						(char_u **)alloc((unsigned)(size * sizeof(char_u *)));
			for (i = 0; i < size; i++)
				y_current->y_array[i] = array[i];
			vim_free(array);
		}
		y_current->y_size = size;
	}
	return eof;
}

	void
write_viminfo_registers(fp)
	FILE	*fp;
{
	int		i, j;
	char_u	*type;
	char_u	c;
	int		num_lines;
	int		max_num_lines;

	fprintf(fp, "\n# Registers:\n");

	max_num_lines = get_viminfo_parameter('"');
	if (max_num_lines == 0)
		return;
	for (i = 0; i < NUM_REGISTERS; i++)
	{
		if (y_regs[i].y_array == NULL)
			continue;
#ifdef USE_CLIPBOARD
		/* Skip '*' register, we don't want it back next time */
		if (i == CLIPBOARD_REGISTER)
			continue;
#endif
		switch (y_regs[i].y_type)
		{
			case MLINE:
				type = (char_u *)"LINE";
				break;
			case MCHAR:
				type = (char_u *)"CHAR";
				break;
			case MBLOCK:
				type = (char_u *)"BLOCK";
				break;
			default:
				sprintf((char *)IObuff, "Unknown register type %d",
					y_regs[i].y_type);
				emsg(IObuff);
				type = (char_u *)"LINE";
				break;
		}
		if (y_previous == &y_regs[i])
			fprintf(fp, "\"");
		if (i == DELETION_REGISTER)
			c = '-';
		else if (i < 10)
			c = '0' + i;
		else
			c = 'a' + i - 10;
		fprintf(fp, "\"%c\t%s\n", c, type);
		num_lines = y_regs[i].y_size;

		/* If max_num_lines < 0, then we save ALL the lines in the register */
		if (max_num_lines > 0 && num_lines > max_num_lines)
			num_lines = max_num_lines;
		for (j = 0; j < num_lines; j++)
		{
			putc('\t', fp);
			viminfo_writestring(fp, y_regs[i].y_array[j]);
		}
	}
}
#endif /* VIMINFO */

#if defined(USE_CLIPBOARD) || defined(PROTO)
/*
 * Text selection stuff that uses the GUI selection register '*'.  When using a
 * GUI this may be text from another window, otherwise it is the last text we
 * had highlighted with VIsual mode.  With mouse support, clicking the middle
 * button performs the paste, otherwise you will need to do <"*p>.
 */

	void
clip_free_selection()
{
	struct yankreg *y_ptr = y_current;

	y_current = &y_regs[CLIPBOARD_REGISTER];		/* '*' register */
	free_yank_all();
	y_current->y_size = 0;
	y_current = y_ptr;
}

/*
 * Get the selected text and put it in the gui text register '*'.
 */
	void
clip_get_selection()
{
	struct yankreg *old_y_previous, *old_y_current;
	FPOS	old_cursor, old_visual;
	OPARG	oa;
	CMDARG	ca;

	if (clipboard.owned)
	{
		if (y_regs[CLIPBOARD_REGISTER].y_array != NULL)
			return;

		/* Get the text between clipboard.start & clipboard.end */
		old_y_previous = y_previous;
		old_y_current = y_current;
		old_cursor = curwin->w_cursor;
		old_visual = VIsual;
		clear_oparg(&oa);
		oa.regname = '*';
		oa.op_type = OP_YANK;
		vim_memset(&ca, 0, sizeof(ca));
		ca.oap = &oa;
		ca.cmdchar = 'y';
		ca.count1 = 1;
		do_pending_operator(&ca, FALSE, NULL, NULL, 0, TRUE, TRUE);
		y_previous = old_y_previous;
		y_current = old_y_current;
		curwin->w_cursor = old_cursor;
		VIsual = old_visual;
	}
	else
	{
		clip_free_selection();

		/* Try to get selected text from another window */
		clip_mch_request_selection();
	}
}

/* Convert from the GUI selection string into the '*' register */
	void
clip_yank_selection(type, str, len)
	int		type;
	char_u	*str;
	long_u	len;
{
	struct yankreg *y_ptr = &y_regs[CLIPBOARD_REGISTER];	/* '*' register */
	int		lnum;
	int		start;
	long_u	i;

	clip_free_selection();

	/* Count the number of lines within the string */
	y_ptr->y_size = 1;
	for (i = 0; i < len; i++)
		if (str[i] == '\n')
			y_ptr->y_size++;

	if (type != MCHAR && i > 0 && str[i - 1] == '\n')
		y_ptr->y_size--;

	y_ptr->y_array = (char_u **)lalloc(y_ptr->y_size * sizeof(char_u *), TRUE);
	if (y_ptr->y_array == NULL)
		return;
	y_ptr->y_type = type;
	lnum = 0;
	start = 0;
	for (i = 0; i < len; i++)
	{
		if (str[i] == NUL)
			str[i] = '\n';
		else if (str[i] == '\n')
		{
			str[i] = NUL;
			if (type == MCHAR || i != len - 1)
			{
				if ((y_ptr->y_array[lnum] = vim_strsave(str + start)) == NULL)
				{
					y_ptr->y_size = lnum;
					return;
				}
				lnum++;
				start = i + 1;
			}
		}
	}
	if ((y_ptr->y_array[lnum] = alloc(i - start + 1)) == NULL)
		return;
	if (i - start > 0)
		STRNCPY(y_ptr->y_array[lnum], str + start, i - start);
	y_ptr->y_array[lnum][i - start] = NUL;
	y_ptr->y_size = lnum + 1;
}

/*
 * Convert the '*' register into a GUI selection string returned in *str with
 * length *len.
 */
	int
clip_convert_selection(str, len)
	char_u	**str;
	long_u	*len;
{
	struct yankreg *y_ptr = &y_regs[CLIPBOARD_REGISTER];	/* '*' register */
	char_u	*p;
	int		lnum;
	int		i, j;
	int_u	eolsize;

#ifdef USE_CRNL
	eolsize = 2;
#else
	eolsize = 1;
#endif

	*str = NULL;
	*len = 0;
	if (y_ptr->y_array == NULL)
		return -1;

	for (i = 0; i < y_ptr->y_size; i++)
		*len += STRLEN(y_ptr->y_array[i]) + eolsize;

	/*
	 * Don't want newline character at end of last line if we're in MCHAR mode.
	 */
	if (y_ptr->y_type == MCHAR && *len > eolsize)
		*len -= eolsize;

	p = *str = lalloc(*len, TRUE);
	if (p == NULL)
		return -1;
	lnum = 0;
	for (i = 0, j = 0; i < (int)*len; i++, j++)
	{
		if (y_ptr->y_array[lnum][j] == '\n')
			p[i] = NUL;
		else if (y_ptr->y_array[lnum][j] == NUL)
		{
#ifdef USE_CRNL
			p[i++] = '\r';
#endif
			p[i] = '\n';
			lnum++;
			j = -1;
		}
		else
			p[i] = y_ptr->y_array[lnum][j];
	}
	return y_ptr->y_type;
}
#endif /* USE_CLIPBOARD || PROTO */

	void
clear_oparg(oap)
	OPARG		*oap;
{
	vim_memset(oap, 0, sizeof(OPARG));
}

#if defined(HAVE_PYTHON) || defined(PROTO)

/*
 * Access functions to transfer data to and from registers
 */

#ifdef USE_CLIPBOARD
static char_u reg_names[] = (char_u *)"0123456789abcdefghijklmnopqrstuvwxyz-*";
#else
static char_u reg_names[] = (char_u *)"0123456789abcdefghijklmnopqrstuvwxyz-";
#endif

/* Get the data from a register.
 * Returns -1 if an error occurred (invalid register, usually)
 * Otherwise, returns the number of lines in the register.
 *
 * Parameters:
 *     ch       Register name
 *     lines    (OUT) Array of (char_u *) lines
 *     linewise (OUT) 0 = MCHAR, 1 = MLINE/MBLOCK
 *
 * Typical call:
 *     char reg = 'a';
 *     char **lines;
 *     int linewise;
 *     int n = get_register (reg, &lines, &linewise);
 */

	int
get_register(int ch, char_u ***lines, int *linewise)
{
    char_u *p;
    struct yankreg *reg;

    /* Caller must supply somewhere for us to return the results! */
    if (lines == NULL || linewise == NULL)
        return -1;

    /* Check the register name */
    p = vim_strchr(reg_names, ch);
    if (p == NULL)
        return -1;

    reg = &y_regs[p - reg_names];

    *lines = reg->y_array;
    *linewise = (reg->y_type != MCHAR);

    return reg->y_size;
}

/* Set the contents of a register.
 * Returns FAIL if an error occurred, otherwise OK.
 * Parameters:
 *     ch       Register name
 *     lines    Array of (char *) lines
 *     n        Number of lines in the array
 *     linewise 0 = MCHAR, 1 = MLINE
 */
	int
set_register(int ch, const char_u **lines, int n, int linewise)
{
    struct yankreg *reg;
    char_u **new_array;
    char_u *p;
    int i;

    /* Check the register name */
    p = vim_strchr(reg_names, ch);
    if (p == NULL)
        return FAIL;

    reg = &y_regs[p - reg_names];

    /* Allocate the line array */
    new_array = (char_u **)alloc((unsigned)(n * sizeof(char_u *)));
    if (new_array == NULL)
        return FAIL;

    /* Insert the lines */
    for (i = 0; i < n; ++i)
    {
        char_u *str = vim_strsave((char_u *)(lines[i]));

        if (str == NULL)
        {
            while (i)
                vim_free(new_array[--i]);

            vim_free (new_array);
            return FAIL;
        }

        new_array[i] = str;
    }

    /* Free any existing contents of the register */
    i = reg->y_size;
    while (i)
        vim_free (reg->y_array[--i]);

    if (reg->y_array)
        vim_free (reg->y_array);

    /* Set up the new register value */
    reg->y_array = new_array;
    reg->y_size = n;
    reg->y_type = (linewise ? MLINE : MCHAR);

    return OK;
}

#endif /* HAVE_PYTHON || PROTO */

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.