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

This is ex_cmds.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.
 */

/*
 * ex_cmds.c: functions for command line commands
 */

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

static int linelen __ARGS((int *has_tab));
static void do_filter __ARGS((linenr_t line1, linenr_t line2,
									char_u *buff, int do_in, int do_out));
#ifdef VIMINFO
static char_u *viminfo_filename __ARGS((char_u 	*));
static void do_viminfo __ARGS((FILE *fp_in, FILE *fp_out, int want_info,
											 int want_marks, int force_read));
static int read_viminfo_up_to_marks __ARGS((char_u *line, FILE *fp,
																int forceit));
#endif /* VIMINFO */

static int do_sub_msg __ARGS((void));
static int help_compare __ARGS((const void *s1, const void *s2));

	void
do_ascii()
{
	int		c;
	char	buf1[20];
	char	buf2[20];
	char_u	buf3[3];

	c = gchar_cursor();
	if (c == NUL)
	{
		MSG("empty line");
		return;
	}
	if (c == NL)			/* NUL is stored as NL */
		c = NUL;
	if (isprintchar(c) && (c < ' ' || c > '~'))
	{
		transchar_nonprint(buf3, c);
		sprintf(buf1, "  <%s>", (char *)buf3);
	}
	else
		buf1[0] = NUL;
	if (c >= 0x80)
		sprintf(buf2, "  <M-%s>", transchar(c & 0x7f));
	else
		buf2[0] = NUL;
	sprintf((char *)IObuff, "<%s>%s%s  %d,  Hex %02x,  Octal %03o",
										   transchar(c), buf1, buf2, c, c, c);
	msg(IObuff);
}

/*
 * Handle ":left", ":center" and ":right" commands: align text.
 */
	void
do_align(eap)
	EXARG		*eap;
{
	FPOS	save_curpos;
	int		len;
	int		indent = 0;
	int		new_indent = 0;			/* init for GCC */
	int		has_tab;
	int		width;

#ifdef RIGHTLEFT
	if (curwin->w_p_rl)
	{
		/* switch left and right aligning */
		if (eap->cmdidx == CMD_right)
			eap->cmdidx = CMD_left;
		else if (eap->cmdidx == CMD_left)
			eap->cmdidx = CMD_right;
	}
#endif

	width = atoi((char *)eap->arg);
	save_curpos = curwin->w_cursor;
	if (eap->cmdidx == CMD_left)	/* width is used for new indent */
	{
		if (width >= 0)
			indent = width;
	}
	else
	{
		/*
		 * if 'textwidth' set, use it
		 * else if 'wrapmargin' set, use it
		 * if invalid value, use 80
		 */
		if (width <= 0)
			width = curbuf->b_p_tw;
		if (width == 0 && curbuf->b_p_wm > 0)
			width = Columns - curbuf->b_p_wm;
		if (width <= 0)
			width = 80;
	}

	if (u_save((linenr_t)(eap->line1 - 1), (linenr_t)(eap->line2 + 1)) == FAIL)
		return;
	for (curwin->w_cursor.lnum = eap->line1;
				 curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum)
	{
		if (eap->cmdidx == CMD_left)			/* left align */
			new_indent = indent;
		else
		{
			len = linelen(eap->cmdidx == CMD_right ? &has_tab
												   : NULL) - get_indent();

			if (len <= 0)						/* skip blank lines */
				continue;

			if (eap->cmdidx == CMD_center)
				new_indent = (width - len) / 2;
			else
			{
				new_indent = width - len;		/* right align */

				/*
				 * Make sure that embedded TABs don't make the text go too far
				 * to the right.
				 */
				if (has_tab)
					while (new_indent > 0)
					{
						set_indent(new_indent, TRUE);	/* set indent */
						if (linelen(NULL) <= width)
						{
							/*
							 * Now try to move the line as much as possible to
							 * the right.  Stop when it moves too far.
							 */
							do
								set_indent(++new_indent, TRUE);	/* set indent */
							while (linelen(NULL) <= width);
							--new_indent;
							break;
						}
						--new_indent;
					}
			}
		}
		if (new_indent < 0)
			new_indent = 0;
		set_indent(new_indent, TRUE);			/* set indent */
	}
	curwin->w_cursor = save_curpos;
	beginline(TRUE);

	/*
	 * If the cursor is after the first changed line, its position needs to be
	 * updated.
	 */
	if (curwin->w_cursor.lnum > eap->line1)
	{
		changed_line_abv_curs();
		invalidate_botline();
	}
	else if (curwin->w_cursor.lnum == eap->line1)
		changed_cline_bef_curs();

	/*
	 * If the start of the aligned lines is before botline, it may have become
	 * approximated (lines got longer or shorter).
	 */
	if (botline_approximated() && eap->line1 < curwin->w_botline)
		approximate_botline();
	update_screen(NOT_VALID);
}

/*
 * Get the length of the current line, excluding trailing white space.
 */
	static int
linelen(has_tab)
	int		*has_tab;
{
	char_u	*line;
	char_u	*first;
	char_u	*last;
	int		save;
	int		len;

	/* find the first non-blank character */
	line = ml_get_curline();
	first = skipwhite(line);

	/* find the character after the last non-blank character */
	for (last = first + STRLEN(first);
								last > first && vim_iswhite(last[-1]); --last)
		;
	save = *last;
	*last = NUL;
	len = linetabsize(line);			/* get line length */
	if (has_tab != NULL)				/* check for embedded TAB */
		*has_tab = (vim_strrchr(first, TAB) != NULL);
	*last = save;

	return len;
}

/*
 * Handle ":retab" command.
 */
	void
do_retab(eap)
	EXARG		*eap;
{
	linenr_t	lnum;
	int			got_tab = FALSE;
	long		num_spaces = 0;
	long		num_tabs;
	long		len;
	long		col;
	long		vcol;
	long		start_col = 0;			/* For start of white-space string */
	long		start_vcol = 0;			/* For start of white-space string */
	int			temp;
	long		old_len;
	char_u		*ptr;
	char_u		*new_line = (char_u *)1;	/* init to non-NULL */
	int			did_something = FALSE;
	int			did_undo;				/* called u_save for current line */
	int			new_ts;

	new_ts = getdigits(&(eap->arg));
	if (new_ts == 0)
		new_ts = curbuf->b_p_ts;
	for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
	{
		ptr = ml_get(lnum);
		col = 0;
		vcol = 0;
		did_undo = FALSE;
		for (;;)
		{
			if (vim_iswhite(ptr[col]))
			{
				if (!got_tab && num_spaces == 0)
				{
					/* First consecutive white-space */
					start_vcol = vcol;
					start_col = col;
				}
				if (ptr[col] == ' ')
					num_spaces++;
				else
					got_tab = TRUE;
			}
			else
			{
				if (got_tab || (eap->forceit && num_spaces > 1))
				{
					/* Retabulate this string of white-space */

					/* len is virtual length of white string */
					len = num_spaces = vcol - start_vcol;
					num_tabs = 0;
					if (!curbuf->b_p_et)
					{
						temp = new_ts - (start_vcol % new_ts);
						if (num_spaces >= temp)
						{
							num_spaces -= temp;
							num_tabs++;
						}
						num_tabs += num_spaces / new_ts;
						num_spaces -= (num_spaces / new_ts) * new_ts;
					}
					if (curbuf->b_p_et || got_tab ||
										(num_spaces + num_tabs < len))
					{
						if (did_undo == FALSE)
						{
							did_undo = TRUE;
							if (u_save((linenr_t)(lnum - 1),
												(linenr_t)(lnum + 1)) == FAIL)
							{
								new_line = NULL;		/* flag out-of-memory */
								break;
							}
						}

						/* len is actual number of white characters used */
						len = num_spaces + num_tabs;
						old_len = STRLEN(ptr);
						new_line = lalloc(old_len - col + start_col + len + 1,
																		TRUE);
						if (new_line == NULL)
							break;
						if (start_col > 0)
							vim_memmove(new_line, ptr, (size_t)start_col);
						vim_memmove(new_line + start_col + len,
									  ptr + col, (size_t)(old_len - col + 1));
						ptr = new_line + start_col;
						for (col = 0; col < len; col++)
							ptr[col] = (col < num_tabs) ? '\t' : ' ';
						ml_replace(lnum, new_line, FALSE);
						did_something = TRUE;
						ptr = new_line;
						col = start_col + len;
					}
				}
				got_tab = FALSE;
				num_spaces = 0;
			}
			if (ptr[col] == NUL)
				break;
			vcol += chartabsize(ptr[col++], (colnr_t)vcol);
		}
		if (new_line == NULL)				/* out of memory */
			break;
		line_breakcheck();
	}
	if (got_int)
		emsg(e_interr);
	if (did_something)
		CHANGED;

	if (curbuf->b_p_ts != new_ts || did_something)
	{
		/*
		 * Cursor may need updating when change is before or at the cursor
		 * line.  w_botline may be wrong a bit now.
		 */
		if (curbuf->b_p_ts != new_ts || eap->line1 < curwin->w_cursor.lnum)
			changed_line_abv_curs();		/* recompute cursor pos compl. */
		else if (eap->line1 == curwin->w_cursor.lnum)
			changed_cline_bef_curs();		/* recompute curosr pos partly */
		approximate_botline();
	}
	curbuf->b_p_ts = new_ts;
	coladvance(curwin->w_curswant);

	u_clearline();
	update_screen(NOT_VALID);
}

/*
 * :move command - move lines line1-line2 to line dest
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_move(line1, line2, dest)
	linenr_t	line1;
	linenr_t	line2;
	linenr_t	dest;
{
	char_u		*str;
	linenr_t	l;
	linenr_t	extra;		/* Num lines added before line1 */
	linenr_t	num_lines;	/* Num lines moved */
	linenr_t	last_line;	/* Last line in file after adding new text */
	int			has_mark;

	if (dest >= line1 && dest < line2)
	{
		EMSG("Move lines into themselves");
		return FAIL;
	}

	num_lines = line2 - line1 + 1;

	/*
	 * First we copy the old text to its new location -- webb
	 * Also copy the flag that ":global" command uses.
	 */
	if (u_save(dest, dest + 1) == FAIL)
		return FAIL;
	for (extra = 0, l = line1; l <= line2; l++)
	{
		str = vim_strsave(ml_get(l + extra));
		if (str != NULL)
		{
			has_mark = ml_has_mark(l + extra);
			ml_append(dest + l - line1, str, (colnr_t)0, FALSE);
			vim_free(str);
			if (has_mark)
				ml_setmarked(dest + l - line1 + 1);
			if (dest < line1)
				extra++;
		}
	}

	/*
	 * Now we must be careful adjusting our marks so that we don't overlap our
	 * mark_adjust() calls.
	 *
	 * We adjust the marks within the old text so that they refer to the
	 * last lines of the file (temporarily), because we know no other marks
	 * will be set there since these line numbers did not exist until we added
	 * our new lines.
	 *
	 * Then we adjust the marks on lines between the old and new text positions
	 * (either forwards or backwards).
	 *
	 * And Finally we adjust the marks we put at the end of the file back to
	 * their final destination at the new text position -- webb
	 */
	last_line = curbuf->b_ml.ml_line_count;
	mark_adjust(line1, line2, last_line - line2, 0L);
	if (dest >= line2)
		mark_adjust(line2 + 1, dest, -num_lines, 0L);
	else
		mark_adjust(dest + 1, line1 - 1, num_lines, 0L);
	mark_adjust(last_line - num_lines + 1, last_line,
											 -(last_line - dest - extra), 0L);

	/*
	 * Now we delete the original text -- webb
	 */
	if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL)
		return FAIL;

	for (l = line1; l <= line2; l++)
		ml_delete(line1 + extra, TRUE);

	CHANGED;
	if (!global_busy && num_lines > p_report)
		smsg((char_u *)"%ld line%s moved", num_lines, plural(num_lines));

	/*
	 * Leave the cursor on the last of the moved lines.
	 */
	if (dest >= line1)
		curwin->w_cursor.lnum = dest;
	else
		curwin->w_cursor.lnum = dest + (line2 - line1) + 1;
	changed_line_abv_curs();
	/*
	 * TODO: should recompute w_botline for simple situations.
	 */
	invalidate_botline();
	return OK;
}

/*
 * :copy command - copy lines line1-line2 to line n
 */
	void
do_copy(line1, line2, n)
	linenr_t	line1;
	linenr_t	line2;
	linenr_t	n;
{
	linenr_t		lnum;
	char_u			*p;

	mark_adjust(n + 1, MAXLNUM, line2 - line1 + 1, 0L);

	/*
	 * there are three situations:
	 * 1. destination is above line1
	 * 2. destination is between line1 and line2
	 * 3. destination is below line2
	 *
	 * n = destination (when starting)
	 * curwin->w_cursor.lnum = destination (while copying)
	 * line1 = start of source (while copying)
	 * line2 = end of source (while copying)
	 */
	if (u_save(n, n + 1) == FAIL)
		return;
	curwin->w_cursor.lnum = n;
	lnum = line2 - line1 + 1;
	while (line1 <= line2)
	{
		/* need to use vim_strsave() because the line will be unlocked
			within ml_append */
		p = vim_strsave(ml_get(line1));
		if (p != NULL)
		{
			ml_append(curwin->w_cursor.lnum, p, (colnr_t)0, FALSE);
			vim_free(p);
		}
				/* situation 2: skip already copied lines */
		if (line1 == n)
			line1 = curwin->w_cursor.lnum;
		++line1;
		if (curwin->w_cursor.lnum < line1)
			++line1;
		if (curwin->w_cursor.lnum < line2)
			++line2;
		++curwin->w_cursor.lnum;
	}
	CHANGED;
	changed_line_abv_curs();
	/*
	 * TODO: should recompute w_botline for simple situations.
	 */
	invalidate_botline();
	msgmore((long)lnum);
}

/*
 * Handle the ":!cmd" command.  Also for ":r !cmd" and ":w !cmd"
 * Bangs in the argument are replaced with the previously entered command.
 * Remember the argument.
 */
	void
do_bang(addr_count, line1, line2, forceit, arg, do_in, do_out)
	int			addr_count;
	linenr_t	line1, line2;
	int			forceit;
	char_u		*arg;
	int			do_in, do_out;
{
	static	char_u	*prevcmd = NULL;		/* the previous command */
	char_u			*newcmd = NULL;			/* the new command */
	int				ins_prevcmd;
	char_u			*t;
	char_u			*p;
	char_u			*trailarg;
	int 			len;
	int				scroll_save = msg_scroll;

	/*
	 * Disallow shell commands for "rvim".
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (check_restricted() || check_secure())
		return;

	if (addr_count == 0)				/* :! */
	{
		msg_scroll = FALSE;			/* don't scroll here */
		autowrite_all();
		msg_scroll = scroll_save;
	}

	/*
	 * Try to find an embedded bang, like in :!<cmd> ! [args]
	 * (:!! is indicated by the 'forceit' variable)
	 */
	ins_prevcmd = forceit;
	trailarg = arg;
	do
	{
		len = STRLEN(trailarg) + 1;
		if (newcmd != NULL)
			len += STRLEN(newcmd);
		if (ins_prevcmd)
		{
			if (prevcmd == NULL)
			{
				emsg(e_noprev);
				vim_free(newcmd);
				return;
			}
			len += STRLEN(prevcmd);
		}
		if ((t = alloc(len)) == NULL)
		{
			vim_free(newcmd);
			return;
		}
		*t = NUL;
		if (newcmd != NULL)
			STRCAT(t, newcmd);
		if (ins_prevcmd)
			STRCAT(t, prevcmd);
		p = t + STRLEN(t);
		STRCAT(t, trailarg);
		vim_free(newcmd);
		newcmd = t;

		/*
		 * Scan the rest of the argument for '!', which is replaced by the
		 * previous command.  "\!" is replaced by "!" (this is vi compatible).
		 */
		trailarg = NULL;
		while (*p)
		{
			if (*p == '!')
			{
				if (p > newcmd && p[-1] == '\\')
					vim_memmove(p - 1, p, (size_t)(STRLEN(p) + 1));
				else
				{
					trailarg = p;
					*trailarg++ = NUL;
					ins_prevcmd = TRUE;
					break;
				}
			}
			++p;
		}
	} while (trailarg != NULL);

	vim_free(prevcmd);
	prevcmd = newcmd;

	if (bangredo)			/* put cmd in redo buffer for ! command */
	{
		AppendToRedobuff(prevcmd);
		AppendToRedobuff((char_u *)"\n");
		bangredo = FALSE;
	}
	/*
	 * Add quotes around the command, for shells that need them.
	 */
	if (*p_shq != NUL)
	{
		newcmd = alloc((unsigned)(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1));
		if (newcmd == NULL)
			return;
		STRCPY(newcmd, p_shq);
		STRCAT(newcmd, prevcmd);
		STRCAT(newcmd, p_shq);
	}
	if (addr_count == 0)				/* :! */
	{
			/* echo the command */
		msg_start();
		msg_putchar(':');
		msg_putchar('!');
		msg_outtrans(newcmd);
		msg_clr_eos();
		windgoto(msg_row, msg_col);

		do_shell(newcmd);
	}
	else								/* :range! */
		do_filter(line1, line2, newcmd, do_in, do_out);
	if (newcmd != prevcmd)
		vim_free(newcmd);
}

/*
 * call a shell to execute a command
 */
	void
do_shell(cmd)
	char_u	*cmd;
{
	BUF		*buf;
	int		save_nwr;

	/*
	 * Disallow shell commands for "rvim".
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (check_restricted() || check_secure())
	{
		msg_end();
		return;
	}

#ifdef WIN32
	/*
	 * Check if external commands are allowed now.
	 */
	if (can_end_termcap_mode(TRUE) == FALSE)
		return;
#endif

	/*
	 * For autocommands we want to get the output on the current screen, to
	 * avoid having to type return below.
	 */
	msg_putchar('\r');					/* put cursor at start of line */
#ifdef AUTOCMD
	if (!autocmd_busy)
#endif
		stoptermcap();
	msg_putchar('\n');					/* may shift screen one line up */

		/* warning message before calling the shell */
	if (p_warn
#ifdef AUTOCMD
				&& !autocmd_busy
#endif
								   )
		for (buf = firstbuf; buf; buf = buf->b_next)
			if (buf->b_changed)
			{
				MSG_PUTS("[No write since last change]\n");
				break;
			}

/* This windgoto is required for when the '\n' resulted in a "delete line 1"
 * command to the terminal. */

	windgoto(msg_row, msg_col);
	cursor_on();
	(void)mch_call_shell(cmd, SHELL_COOKED);
	need_check_timestamps = TRUE;

/*
 * put the message cursor at the end of the screen, avoids wait_return() to
 * overwrite the text that the external command showed
 */
	msg_pos((int)Rows - 1, 0);

#ifdef AUTOCMD
	if (autocmd_busy)
		must_redraw = CLEAR;
	else
#endif
	{
		/*
		 * For ":sh" there is no need to call wait_return(), just redraw.
		 * Otherwise there is probably text on the screen that the user wants
		 * to read before redrawing, so call wait_return().
		 */
		if (cmd == NULL)
		{
			must_redraw = CLEAR;
			need_wait_return = FALSE;
			dont_wait_return = TRUE;
		}
		else
		{
			/*
			 * If K_TI is defined, we assume that we switch screens when
			 * starttermcap() is called. In that case we really want to wait
			 * for "hit return to continue".
			 */
			save_nwr = no_wait_return;
			if (*T_TI != NUL)
				no_wait_return = FALSE;
#ifdef AMIGA
			wait_return(term_console ? -1 : TRUE);		/* see below */
#else
			wait_return(TRUE);
#endif
			no_wait_return = save_nwr;
		}
		starttermcap();		/* start termcap if not done by wait_return() */

		/*
		 * In an Amiga window redrawing is caused by asking the window size.
		 * If we got an interrupt this will not work. The chance that the
		 * window size is wrong is very small, but we need to redraw the
		 * screen.  Don't do this if ':' hit in wait_return().  THIS IS UGLY
		 * but it saves an extra redraw.
		 */
#ifdef AMIGA
		if (skip_redraw)				/* ':' hit in wait_return() */
			must_redraw = CLEAR;
		else if (term_console)
		{
			OUTSTR("\033[0 q"); 		/* get window size */
			if (got_int)
				must_redraw = CLEAR;	/* if got_int is TRUE, redraw needed */
			else
				must_redraw = 0;		/* no extra redraw needed */
		}
#endif /* AMIGA */
	}
}

/*
 * do_filter: filter lines through a command given by the user
 *
 * We use temp files and the mch_call_shell() routine here. This would normally
 * be done using pipes on a UNIX machine, but this is more portable to
 * non-unix machines. The mch_call_shell() routine needs to be able
 * to deal with redirection somehow, and should handle things like looking
 * at the PATH env. variable, and adding reasonable extensions to the
 * command name given by the user. All reasonable versions of mch_call_shell()
 * do this.
 * We use input redirection if do_in is TRUE.
 * We use output redirection if do_out is TRUE.
 */
	static void
do_filter(line1, line2, buff, do_in, do_out)
	linenr_t	line1, line2;
	char_u		*buff;
	int			do_in, do_out;
{
	char_u		*itmp = NULL;
	char_u		*otmp = NULL;
	linenr_t 	linecount;
	FPOS		cursor_save;
#ifdef AUTOCMD
	BUF			*old_curbuf = curbuf;
#endif

	if (*buff == NUL)		/* no filter command */
		return;

#ifdef WIN32
	/*
	 * Check if external commands are allowed now.
	 */
	if (can_end_termcap_mode(TRUE) == FALSE)
		return;
#endif

	cursor_save = curwin->w_cursor;
	linecount = line2 - line1 + 1;
	curwin->w_cursor.lnum = line1;
	curwin->w_cursor.col = 0;
	changed_line_abv_curs();
	invalidate_botline();

	/*
	 * 1. Form temp file names
	 * 2. Write the lines to a temp file
	 * 3. Run the filter command on the temp file
	 * 4. Read the output of the command into the buffer
	 * 5. Delete the original lines to be filtered
	 * 6. Remove the temp files
	 */

	if ((do_in && (itmp = vim_tempname('i')) == NULL) ||
							   (do_out && (otmp = vim_tempname('o')) == NULL))
	{
		emsg(e_notmp);
		goto filterend;
	}

/*
 * The writing and reading of temp files will not be shown.
 * Vi also doesn't do this and the messages are not very informative.
 */
	++no_wait_return;			/* don't call wait_return() while busy */
	if (do_in && buf_write(curbuf, itmp, NULL, line1, line2,
										   FALSE, FALSE, FALSE, TRUE) == FAIL)
	{
		msg_putchar('\n');					/* keep message from buf_write() */
		--no_wait_return;
		(void)emsg2(e_notcreate, itmp);		/* will call wait_return */
		goto filterend;
	}
#ifdef AUTOCMD
	if (curbuf != old_curbuf)
		goto filterend;
#endif

	if (!do_out)
		msg_putchar('\n');

#if (defined(UNIX) && !defined(ARCHIE)) || defined(OS2)
/*
 * put braces around the command (for concatenated commands)
 */
 	sprintf((char *)IObuff, "(%s)", (char *)buff);
	if (do_in)
	{
		STRCAT(IObuff, " < ");
		STRCAT(IObuff, itmp);
	}
#else
/*
 * for shells that don't understand braces around commands, at least allow
 * the use of commands in a pipe.
 */
	STRCPY(IObuff, buff);
	if (do_in)
	{
		char_u		*p;
	/*
	 * If there is a pipe, we have to put the '<' in front of it.
	 * Don't do this when 'shellquote' is not empty, otherwise the redirection
	 * would be inside the quotes.
	 */
		p = vim_strchr(IObuff, '|');
		if (p && *p_shq == NUL)
			*p = NUL;
		STRCAT(IObuff, " < ");
		STRCAT(IObuff, itmp);
		p = vim_strchr(buff, '|');
		if (p && *p_shq == NUL)
			STRCAT(IObuff, p);
	}
#endif
	if (do_out)
	{
		char_u *p;

		if ((p = vim_strchr(p_srr, '%')) != NULL && p[1] == 's')
		{
			p = IObuff + STRLEN(IObuff);
			*p++ = ' '; /* not really needed? Not with sh, ksh or bash */
			sprintf((char *)p, (char *)p_srr, (char *)otmp);
		}
		else
			sprintf((char *)IObuff + STRLEN(IObuff), " %s %s",
												 (char *)p_srr, (char *)otmp);
	}

	windgoto((int)Rows - 1, 0);
	cursor_on();

	/*
	 * When not redirecting the output the command can write anything to the
	 * screen. If 'shellredir' is equal to ">", screen may be messed up by
	 * stderr output of external command. Clear the screen later.
	 * If do_in is FALSE, this could be something like ":r !cat", which may
	 * also mess up the screen, clear it later.
	 */
	if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in)
		must_redraw = CLEAR;
	else
		redraw_later(NOT_VALID);

	/*
	 * When mch_call_shell() fails wait_return() is called to give the user a
	 * chance to read the error messages. Otherwise errors are ignored, so you
	 * can see the error messages from the command that appear on stdout; use
	 * 'u' to fix the text
	 * Switch to cooked mode when not redirecting stdin, avoids that something
	 * like ":r !cat" hangs.
	 */
	if (mch_call_shell(IObuff, SHELL_FILTER | SHELL_COOKED) == FAIL)
	{
		must_redraw = CLEAR;
		wait_return(FALSE);
	}
	need_check_timestamps = TRUE;

	if (do_out)
	{
		if (u_save((linenr_t)(line2), (linenr_t)(line2 + 1)) == FAIL)
		{
			goto error;
		}
		if (readfile(otmp, NULL, line2, (linenr_t)0, MAXLNUM, READ_FILTER)
																	  == FAIL)
		{
			msg_putchar('\n');
			emsg2(e_notread, otmp);
			goto error;
		}
#ifdef AUTOCMD
		if (curbuf != old_curbuf)
			goto filterend;
#endif

		if (do_in)
		{
			/*
			 * Put cursor on first filtered line for ":range!cmd".
			 * Adjust '[ and '] (set by buf_write()).
			 */
			curwin->w_cursor.lnum = line1;
			del_lines(linecount, TRUE, TRUE);
			curbuf->b_op_start.lnum -= linecount;		/* adjust '[ */
			curbuf->b_op_end.lnum -= linecount;			/* adjust '] */
			write_lnum_adjust(-linecount);				/* adjust last line
														   for next write */
		}
		else
		{
			/*
			 * Put cursor on last new line for ":r !cmd".
			 */
			curwin->w_cursor.lnum = curbuf->b_op_end.lnum;
			linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1;
		}
		beginline(TRUE);				/* cursor on first non-blank */
		--no_wait_return;

		if (linecount > p_report)
		{
			if (do_in)
			{
				sprintf((char *)msg_buf, "%ld lines filtered", (long)linecount);
				if (msg(msg_buf) && !msg_scroll)
				{
					keep_msg = msg_buf;		/* display message after redraw */
					keep_msg_attr = 0;
				}
			}
			else
				msgmore((long)linecount);
		}
	}
	else
	{
error:
		/* put cursor back in same position for ":w !cmd" */
		curwin->w_cursor = cursor_save;
		--no_wait_return;
		wait_return(FALSE);
	}

filterend:

#ifdef AUTOCMD
	if (curbuf != old_curbuf)
	{
		--no_wait_return;
		EMSG("*Filter* Autocommands must not change current buffer");
	}
#endif
	if (itmp != NULL)
		vim_remove(itmp);
	if (otmp != NULL)
		vim_remove(otmp);
	vim_free(itmp);
	vim_free(otmp);
}

#ifdef VIMINFO

static int no_viminfo __ARGS((void));
static int	viminfo_errcnt;

	static int
no_viminfo()
{
	/* "vim -i NONE" does not read or write a viminfo file */
	return (use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0);
}

/*
 * Report an error for reading a viminfo file.
 * Count the number of errors.  When there are more than 10, return TRUE.
 */
	int
viminfo_error(message, line)
	char	*message;
	char_u	*line;
{
	sprintf((char *)IObuff, "viminfo: %s in line: ", message);
	STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff));
	emsg(IObuff);
	if (++viminfo_errcnt >= 10)
	{
		EMSG("viminfo: Too many errors, skipping rest of file");
		return TRUE;
	}
	return FALSE;
}

/*
 * read_viminfo() -- Read the viminfo file.  Registers etc. which are already
 * set are not over-written unless force is TRUE. -- webb
 */
	int
read_viminfo(file, want_info, want_marks, forceit)
	char_u	*file;
	int		want_info;
	int		want_marks;
	int		forceit;
{
	FILE	*fp;

	if (no_viminfo())
		return FAIL;

	file = viminfo_filename(file);			/* may set to default if NULL */
	if ((fp = fopen((char *)file, READBIN)) == NULL)
		return FAIL;

	viminfo_errcnt = 0;
	do_viminfo(fp, NULL, want_info, want_marks, forceit);

	fclose(fp);

	return OK;
}

/*
 * write_viminfo() -- Write the viminfo file.  The old one is read in first so
 * that effectively a merge of current info and old info is done.  This allows
 * multiple vims to run simultaneously, without losing any marks etc.  If
 * forceit is TRUE, then the old file is not read in, and only internal info is
 * written to the file. -- webb
 */
	void
write_viminfo(file, forceit)
	char_u	*file;
	int		forceit;
{
	FILE			*fp_in = NULL;		/* input viminfo file, if any */
	FILE			*fp_out = NULL;		/* output viminfo file */
	char_u			*tempname = NULL;	/* name of temp viminfo file */
	struct stat		st_new;				/* stat() of potential new file */
	char_u			*wp;
#ifdef UNIX
	int				shortname = FALSE;	/* use 8.3 filename */
	mode_t			umask_save;
	struct stat 	st_old;				/* stat() of existing viminfo file */
#endif

	if (no_viminfo())
		return;

	file = viminfo_filename(file);		/* may set to default if NULL */
	file = vim_strsave(file);			/* make a copy, don't want NameBuff */

	if (file != NULL)
	{
		fp_in = fopen((char *)file, READBIN);
		if (fp_in == NULL)
		{
#ifdef UNIX
			/*
			 * For Unix we create the .viminfo non-accessible for others,
			 * because it may contain text from non-accessible documents.
			 */
			umask_save = umask(077);
#endif
			fp_out = fopen((char *)file, WRITEBIN);
#ifdef UNIX
			(void)umask(umask_save);
#endif
		}
		else
		{
			/*
			 * There is an existing viminfo file.  Create a temporary file to
			 * write the new viminfo into, in the same directory as the
			 * existing viminfo file, which will be renamed later.
			 */
#ifdef UNIX
			/*
			 * For Unix we check the owner of the file.  It's not very nice to
			 * overwrite a user's viminfo file after a "su root", with a
			 * viminfo file that the user can't read.
			 */
			st_old.st_dev = st_old.st_ino = 0;
			st_old.st_mode = 0600;
			if (stat((char *)file, &st_old) == 0 &&
					!(st_old.st_uid == getuid()
							? (st_old.st_mode & 0200)
							: (st_old.st_gid == getgid()
									? (st_old.st_mode & 0020)
					 				: (st_old.st_mode & 0002))))
			{
				EMSG2("Viminfo file is not writable: %s", file);
				goto end;
			}
#endif

			/*
			 * Make tempfile name.
			 * May try twice: Once normal and once with shortname set, just in
			 * case somebody puts his viminfo file in an 8.3 filesystem.
			 */
			for (;;)
			{
				tempname = buf_modname(
#ifdef UNIX
										shortname,
#else
# ifdef SHORT_FNAME
										TRUE,
# else
										FALSE,
# endif
#endif
													file, (char_u *)".tmp");
				if (tempname == NULL)			/* out of memory */
					break;

				/*
				 * Check if tempfile already exists.  Never overwrite an
				 * existing file!
				 */
				if (stat((char *)tempname, &st_new) == 0)
				{
#ifdef UNIX
					/*
					 * Check if tempfile is same as original file.  May happen
					 * when modname gave the same file back.  E.g.  silly
					 * link, or filename-length reached.  Try again with
					 * shortname set.
					 */
					if (!shortname && st_new.st_dev == st_old.st_dev &&
							st_new.st_ino == st_old.st_ino)
					{
						vim_free(tempname);
						tempname = NULL;
						shortname = TRUE;
						continue;
					}
#endif
					/*
					 * Try another name.  Change one character, just before
					 * the extension.  This should also work for an 8.3
					 * filename, when after adding the extension it still is
					 * the same file as the original.
					 */
					wp = tempname + STRLEN(tempname) - 5;
					if (wp < gettail(tempname))		/* empty file name? */
						wp = gettail(tempname);
					for (*wp = 'z'; stat((char *)tempname, &st_new) == 0; --*wp)
					{
						/*
						 * They all exist?  Must be something wrong! Don't
						 * write the viminfo file then.
						 */
						if (*wp == 'a')
						{
							vim_free(tempname);
							tempname = NULL;
							break;
						}
					}
				}
				break;
			}

			if (tempname != NULL)
			{
				fp_out = fopen((char *)tempname, WRITEBIN);

				/*
				 * If we can't create in the same directory, try creating a
				 * "normal" temp file.
				 */
				if (fp_out == NULL)
				{
					vim_free(tempname);
					if ((tempname = vim_tempname('o')) != NULL)
						fp_out = fopen((char *)tempname, WRITEBIN);
				}
#ifdef UNIX
				/*
				 * Set file protection same as original file, but strip s-bit
				 * and make sure the owner can read/write it.
				 */
				if (fp_out != NULL)
					(void)setperm(tempname, (st_old.st_mode & 0777) | 0600);
#endif
			}
		}
	}

	/*
	 * Check if the new viminfo file can be written to.
	 */
	if (file == NULL || fp_out == NULL)
	{
		EMSG2("Can't write viminfo file %s!", file == NULL ? (char_u *)"" :
											  fp_in == NULL ? file : tempname);
		if (fp_in != NULL)
			fclose(fp_in);
		goto end;
	}

	viminfo_errcnt = 0;
	do_viminfo(fp_in, fp_out, !forceit, !forceit, FALSE);

	fclose(fp_out);			/* errors are ignored !? */
	if (fp_in != NULL)
	{
		fclose(fp_in);
		/*
		 * In case of an error, don't overwrite the original viminfo file.
		 */
		if (viminfo_errcnt || vim_rename(tempname, file) == -1)
			vim_remove(tempname);
	}
end:
	vim_free(file);
	vim_free(tempname);
}

/*
 * Get the viminfo filename to use.
 * If "file" is given and not empty, use it (has already been expanded by
 * cmdline functions).
 * Otherwise use "-i filename", value from 'viminfo' or the default, and
 * expand environment variables.
 */
	static char_u *
viminfo_filename(file)
	char_u		*file;
{
	if (file == NULL || *file == NUL)
	{
		if (use_viminfo != NULL)
			file = use_viminfo;
		else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL)
			file = (char_u *)VIMINFO_FILE;
		expand_env(file, NameBuff, MAXPATHL);
		return NameBuff;
	}
	return file;
}

/*
 * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo().
 */
	static void
do_viminfo(fp_in, fp_out, want_info, want_marks, force_read)
	FILE	*fp_in;
	FILE	*fp_out;
	int		want_info;
	int		want_marks;
	int		force_read;
{
	int		count = 0;
	int		eof = FALSE;
	char_u	*line;

	if ((line = alloc(LSIZE)) == NULL)
		return;

	if (fp_in != NULL)
	{
		if (want_info)
			eof = read_viminfo_up_to_marks(line, fp_in, force_read);
		else
			/* Skip info, find start of marks */
			while (!(eof = vim_fgets(line, LSIZE, fp_in)) && line[0] != '>')
				;
	}
	if (fp_out != NULL)
	{
		/* Write the info: */
		fprintf(fp_out, "# This viminfo file was generated by vim\n");
		fprintf(fp_out, "# You may edit it if you're careful!\n\n");
		write_viminfo_search_pattern(fp_out);
		write_viminfo_sub_string(fp_out);
		write_viminfo_history(fp_out);
		write_viminfo_registers(fp_out);
		write_viminfo_filemarks(fp_out);
		count = write_viminfo_marks(fp_out);
	}
	if (fp_in != NULL && want_marks)
		copy_viminfo_marks(line, fp_in, fp_out, count, eof);
	vim_free(line);
}

/*
 * read_viminfo_up_to_marks() -- Only called from do_viminfo().  Reads in the
 * first part of the viminfo file which contains everything but the marks that
 * are local to a file.  Returns TRUE when end-of-file is reached. -- webb
 */
	static int
read_viminfo_up_to_marks(line, fp, forceit)
	char_u	*line;
	FILE	*fp;
	int		forceit;
{
	int		eof;

	prepare_viminfo_history(forceit ? 9999 : 0);
	eof = vim_fgets(line, LSIZE, fp);
	while (!eof && line[0] != '>')
	{
		switch (line[0])
		{
				/* Characters reserved for future expansion, ignored now */
			case '+': /* "+40 /path/dir file", for running vim without args */
			case '=': /* to be defined */
			case '-': /* to be defined */
			case '!': /* to be defined */
			case '@': /* to be defined */
			case '%': /* to be defined */
			case '^': /* to be defined */
			case '*': /* to be defined */
				/* A comment */
			case NUL:
			case '\r':
			case '\n':
			case '#':
				eof = vim_fgets(line, LSIZE, fp);
				break;
			case '"':
				eof = read_viminfo_register(line, fp, forceit);
				break;
			case '/':		/* Search string */
			case '&':		/* Substitute search string */
			case '~':		/* Last search string, followed by '/' or '&' */
				eof = read_viminfo_search_pattern(line, fp, forceit);
				break;
			case '$':
				eof = read_viminfo_sub_string(line, fp, forceit);
				break;
			case ':':
			case '?':
			case '|':
				eof = read_viminfo_history(line, fp);
				break;
			case '\'':
				/* How do we have a file mark when the file is not in the
				 * buffer list?
				 */
				eof = read_viminfo_filemark(line, fp, forceit);
				break;
			default:
				if (viminfo_error("Illegal starting char", line))
					eof = TRUE;
				else
					eof = vim_fgets(line, LSIZE, fp);
				break;
		}
	}
	finish_viminfo_history();
	return eof;
}

/*
 * check string read from viminfo file
 * remove '\n' at the end of the line
 * - replace CTRL-V CTRL-V with CTRL-V
 * - replace CTRL-V 'n'    with '\n'
 */
	void
viminfo_readstring(p)
	char_u		*p;
{
	while (*p != NUL && *p != '\n')
	{
		if (*p == Ctrl('V'))
		{
			if (p[1] == 'n')
				p[0] = '\n';
			vim_memmove(p + 1, p + 2, STRLEN(p));
		}
		++p;
	}
	*p = NUL;
}

/*
 * write string to viminfo file
 * - replace CTRL-V with CTRL-V CTRL-V
 * - replace '\n'   with CTRL-V 'n'
 * - add a '\n' at the end
 */
	void
viminfo_writestring(fd, p)
	FILE	*fd;
	char_u	*p;
{
	int		c;

	while ((c = *p++) != NUL)
	{
		if (c == Ctrl('V') || c == '\n')
		{
			putc(Ctrl('V'), fd);
			if (c == '\n')
				c = 'n';
		}
		putc(c, fd);
	}
	putc('\n', fd);
}
#endif /* VIMINFO */

/*
 * Implementation of ":fixdel", also used by get_stty().
 *  <BS>    resulting <Del>
 *   ^?        ^H
 * not ^?      ^?
 */
	void
do_fixdel()
{
	char_u	*p;

	p = find_termcode((char_u *)"kb");
	add_termcode((char_u *)"kD", p != NULL && *p == 0x7f ?
										 (char_u *)"\010" : (char_u *)"\177");
}

	void
print_line_no_prefix(lnum, use_number)
	linenr_t	lnum;
	int			use_number;
{
	char_u		numbuf[20];

	if (curwin->w_p_nu || use_number)
	{
		sprintf((char *)numbuf, "%7ld ", (long)lnum);
		msg_puts_attr(numbuf, highlight_attr[HLF_N]);	/* Highlight line nrs */
	}
	msg_prt_line(ml_get(lnum));
}
	void
print_line(lnum, use_number)
	linenr_t	lnum;
	int			use_number;
{
	msg_start();
	print_line_no_prefix(lnum, use_number);
}

/*
 * Implementation of ":file[!] [fname]".
 */
	void
do_file(arg, forceit)
	char_u	*arg;
	int		forceit;
{
	char_u		*fname, *sfname, *xfname;
	BUF			*buf;

	if (*arg != NUL)
	{
		/*
		 * The name of the current buffer will be changed.
		 * A new buffer entry needs to be made to hold the old
		 * file name, which will become the alternate file name.
		 */
		fname = curbuf->b_ffname;
		sfname = curbuf->b_sfname;
		xfname = curbuf->b_fname;
		curbuf->b_ffname = NULL;
		curbuf->b_sfname = NULL;
		if (setfname(arg, NULL, TRUE) == FAIL)
		{
			curbuf->b_ffname = fname;
			curbuf->b_sfname = sfname;
			return;
		}
		curbuf->b_notedited = TRUE;
		buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, FALSE);
		if (buf != NULL)
			curwin->w_alt_fnum = buf->b_fnum;
		vim_free(fname);
		vim_free(sfname);
	}
	/* print full filename if :cd used */
	fileinfo(FALSE, FALSE, forceit);
}


/*
 * do the Ex mode :insert and :append commands
 */
	void
do_append(lnum, getline, cookie)
	linenr_t	lnum;
	char_u		*(*getline) __ARGS((int, void *, int));
	void		*cookie;				/* argument for getline() */
{
	char_u		*theline;
	int			did_undo = FALSE;

	State = INSERT;					/* behave like in Insert mode */
	while (1)
	{
		msg_scroll = TRUE;
		need_wait_return = FALSE;
		theline = getline(NUL, cookie, 0);
		lines_left = Rows - 1;
		if (theline == NULL || (theline[0] == '.' && theline[1] == NUL))
			break;

		if (!did_undo && u_save(lnum, lnum + 1) == FAIL)
			break;
		did_undo = TRUE;
		mark_adjust(lnum + 1, MAXLNUM, 1L, 0L);
		ml_append(lnum, theline, (colnr_t)0, FALSE);
		CHANGED;

		vim_free(theline);
		++lnum;
	}
	State = NORMAL;

	curwin->w_cursor.lnum = lnum;
	check_cursor_lnum();
	beginline(MAYBE);
	changed_line_abv_curs();
	invalidate_botline();

	dont_wait_return = TRUE;		/* don't use wait_return() now */
	need_wait_return = FALSE;
	update_screen(NOT_VALID);
}

/*
 * do the Ex mode :change command
 */
	void
do_change(start, end, getline, cookie)
	linenr_t	start;
	linenr_t	end;
	char_u		*(*getline) __ARGS((int, void *, int));
	void		*cookie;				/* argument for getline() */
{
	if (end >= start && u_save(start - 1, end + 1) == FAIL)
		return;

	while (end >= start)
	{
		if (curbuf->b_ml.ml_flags & ML_EMPTY) 		/* nothing to delete */
			break;
		ml_delete(start, FALSE);
		CHANGED;
		end--;
	}
	do_append(start - 1, getline, cookie);
}

	void
do_z(line, arg)
	linenr_t	line;
	char_u		*arg;
{
	char_u		*x;
	int			bigness = curwin->w_height - 3;
	char_u		kind;
	int 		minus = 0;
	linenr_t	start, end, curs, i;

	if (bigness < 1)
		bigness = 1;

	x = arg;
	if (*x == '-' || *x == '+' || *x == '=' || *x == '^' || *x == '.')
		x++;

	if (*x != 0)
	{
		if (!isdigit(*x))
		{
			EMSG("non-numeric argument to :z");
			return;
		}
		else
			bigness = atoi((char *)x);
	}

	kind = *arg;

	switch (kind)
	{
		case '-':
			start = line - bigness;
			end = line;
			curs = line;
			break;

		case '=':
			start = line - bigness / 2 + 1;
			end = line + bigness / 2 - 1;
			curs = line;
			minus = 1;
			break;

		case '^':
			start = line - bigness * 2;
			end = line - bigness;
			curs = line - bigness;
			break;

		case '.':
			start = line - bigness / 2;
			end = line + bigness / 2;
			curs = end;
			break;

		default:  /* '+' */
			start = line;
			end = line + bigness;
			curs = end;
			break;
	}

	if (start < 1)
		start = 1;

	if (end > curbuf->b_ml.ml_line_count)
		end = curbuf->b_ml.ml_line_count;

	if (curs > curbuf->b_ml.ml_line_count)
		curs = curbuf->b_ml.ml_line_count;

	for (i = start; i <= end; i++)
	{
		int j;

		if (minus && (i == line))
		{
			msg_putchar('\n');

			for (j = 1; j < Columns; j++)
				msg_putchar('-');
		}

		print_line(i, FALSE);

		if (minus && (i == line))
		{
			msg_putchar('\n');

			for (j = 1; j < Columns; j++)
				msg_putchar('-');
		}
	}

	curwin->w_cursor.lnum = curs;
}

/*
 * Check if the restricted flag is set.
 * If so, give an error message and return TRUE.
 * Otherwise, return FALSE.
 */
	int
check_restricted()
{
	if (restricted)
	{
		EMSG("Shell commands not allowed in rvim");
		return TRUE;
	}
	return FALSE;
}

/*
 * Check if the secure flag is set (.exrc or .vimrc in current directory).
 * If so, give an error message and return TRUE.
 * Otherwise, return FALSE.
 */
	int
check_secure()
{
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return TRUE;
	}
	return FALSE;
}

static char_u   	*old_sub = NULL;	/* previous substitute pattern */

/*
 * When ":global" is used to number of substitutions and changed lines is
 * accumulated until it's finished.
 */
static long			sub_nsubs;		/* total number of substitutions */
static linenr_t		sub_nlines;		/* total number of lines changed */

/* do_sub()
 *
 * Perform a substitution from line eap->line1 to line eap->line2 using the
 * command pointed to by eap->arg which should be of the form:
 *
 * /pattern/substitution/gc
 *
 * The trailing 'g' is optional and, if present, indicates that multiple
 * substitutions should be performed on each line, if applicable.
 * The trailing 'c' is optional and, if present, indicates that a confirmation
 * will be asked for each replacement.
 * The usual escapes are supported as described in the regexp docs.
 */
	void
do_sub(eap)
	EXARG		*eap;
{
	linenr_t		lnum;
	long			i;
	char_u		   *ptr;
	char_u		   *old_line;
	vim_regexp	   *prog;
	static int		do_all = FALSE; 	/* do multiple substitutions per line */
	static int		do_ask = FALSE; 	/* ask for confirmation */
	int				do_print = FALSE;	/* print last line with subst. */
	char_u		   *pat, *sub;
	int 			delimiter;
	int 			sublen;
	int				got_quit = FALSE;
	int				got_match = FALSE;
	int				temp;
	int				which_pat;
	char_u			*cmd;

	cmd = eap->arg;
	if (!global_busy)
	{
		sub_nsubs = 0;
		sub_nlines = 0;
	}

	if (eap->cmdidx == CMD_tilde)
		which_pat = RE_LAST;	/* use last used regexp */
	else
		which_pat = RE_SUBST;	/* use last substitute regexp */

								/* new pattern and substitution */
	if (eap->cmdidx == CMD_substitute && *cmd != NUL && !vim_iswhite(*cmd) &&
					   vim_strchr((char_u *)"0123456789gcr|\"", *cmd) == NULL)
	{
								/* don't accept alphanumeric for separator */
		if (isalpha(*cmd))
		{
			EMSG("Regular expressions can't be delimited by letters");
			return;
		}
		/*
		 * undocumented vi feature:
		 *	"\/sub/" and "\?sub?" use last used search pattern (almost like
		 *	//sub/r).  "\&sub&" use last substitute pattern (like //sub/).
		 */
		if (*cmd == '\\')
		{
			++cmd;
			if (vim_strchr((char_u *)"/?&", *cmd) == NULL)
			{
				emsg(e_backslash);
				return;
			}
			if (*cmd != '&')
				which_pat = RE_SEARCH;		/* use last '/' pattern */
			pat = (char_u *)"";				/* empty search pattern */
			delimiter = *cmd++;				/* remember delimiter character */
		}
		else			/* find the end of the regexp */
		{
			which_pat = RE_LAST;			/* use last used regexp */
			delimiter = *cmd++;				/* remember delimiter character */
			pat = cmd;						/* remember start of search pat */
			cmd = skip_regexp(cmd, delimiter, (int)p_magic);
			if (cmd[0] == delimiter)		/* end delimiter found */
				*cmd++ = NUL;				/* replace it with a NUL */
		}

		/*
		 * Small incompatibility: vi sees '\n' as end of the command, but in
		 * Vim we want to use '\n' to find/substitute a NUL.
		 */
		sub = cmd;			/* remember the start of the substitution */

		while (cmd[0])
		{
			if (cmd[0] == delimiter)			/* end delimiter found */
			{
				*cmd++ = NUL;					/* replace it with a NUL */
				break;
			}
			if (cmd[0] == '\\' && cmd[1] != 0)	/* skip escaped characters */
				++cmd;
			++cmd;
		}

		vim_free(old_sub);
		old_sub = vim_strsave(sub);
	}
	else						/* use previous pattern and substitution */
	{
		if (old_sub == NULL)    /* there is no previous command */
		{
			emsg(e_nopresub);
			return;
		}
		pat = NULL; 			/* search_regcomp() will use previous pattern */
		sub = old_sub;
	}

	/*
	 * find trailing options
	 */
	if (!p_ed)
	{
		if (p_gd)				/* default is global on */
			do_all = TRUE;
		else
			do_all = FALSE;
		do_ask = FALSE;
	}
	while (*cmd)
	{
		/*
		 * Note that 'g' and 'c' are always inverted, also when p_ed is off
		 * 'r' is never inverted.
		 */
		if (*cmd == 'g')
			do_all = !do_all;
		else if (*cmd == 'c')
			do_ask = !do_ask;
		else if (*cmd == 'r')		/* use last used regexp */
			which_pat = RE_LAST;
		else if (*cmd == 'p')
			do_print = TRUE;
		else
			break;
		++cmd;
	}

	/*
	 * check for a trailing count
	 */
	cmd = skipwhite(cmd);
	if (isdigit(*cmd))
	{
		i = getdigits(&cmd);
		if (i <= 0)
		{
			emsg(e_zerocount);
			return;
		}
		eap->line1 = eap->line2;
		eap->line2 += i - 1;
	}

	/*
	 * check for trailing command or garbage
	 */
	cmd = skipwhite(cmd);
	if (*cmd && *cmd != '\"')		/* if not end-of-line or comment */
	{
		eap->nextcomm = check_nextcomm(cmd);
		if (eap->nextcomm == NULL)
		{
			emsg(e_trailing);
			return;
		}
	}

	if ((prog = search_regcomp(pat, RE_SUBST, which_pat, SEARCH_HIS)) == NULL)
	{
		emsg(e_invcmd);
		return;
	}

	/*
	 * ~ in the substitute pattern is replaced with the old pattern.
	 * We do it here once to avoid it to be replaced over and over again.
	 */
	sub = regtilde(sub, (int)p_magic);

	old_line = NULL;
	for (lnum = eap->line1; lnum <= eap->line2 && !(got_int || got_quit);
																	   ++lnum)
	{
		ptr = ml_get(lnum);
		if (vim_regexec(prog, ptr, TRUE))  /* a match on this line */
		{
			char_u		*new_end, *new_start = NULL;
			char_u		*old_match, *old_copy;
			char_u		*prev_old_match = NULL;
			char_u		*p1;
			int			did_sub = FALSE;
			int			match, lastone;
			unsigned	len, needed_len;
			unsigned	new_start_len = 0;

			/* make a copy of the line, so it won't be taken away when updating
				the screen */
			if ((old_line = vim_strsave(ptr)) == NULL)
				continue;
			vim_regexec(prog, old_line, TRUE);  /* match again on this line to
											 	 * update the pointers. TODO:
												 * remove extra vim_regexec() */
			if (!got_match)
			{
				setpcmark();
				got_match = TRUE;
			}

			old_copy = old_match = old_line;
			for (;;)			/* loop until nothing more to replace */
			{
				/*
				 * Save the position of the last change for the final cursor
				 * position (just like the real vi).
				 */
				curwin->w_cursor.lnum = lnum;
				curwin->w_cursor.col = (int)(prog->startp[0] - old_line);
				changed_cline_bef_curs();

				/*
				 * Match empty string does not count, except for first match.
				 * This reproduces the strange vi behaviour.
				 * This also catches endless loops.
				 */
				if (old_match == prev_old_match && old_match == prog->endp[0])
				{
					++old_match;
					goto skip;
				}
				old_match = prog->endp[0];
				prev_old_match = old_match;

				while (do_ask)		/* loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed */
				{
					temp = RedrawingDisabled;
					RedrawingDisabled = FALSE;
					search_match_len = prog->endp[0] - prog->startp[0];
									/* invert the matched string
									 * remove the inversion afterwards */
					if (search_match_len == 0)
						search_match_len = 1;		/* show something! */
					highlight_match = TRUE;
					update_topline();
					update_screen(NOT_VALID);
					highlight_match = FALSE;
					redraw_later(NOT_VALID);
									/* same highlighting as for wait_return */
					smsg_attr(highlight_attr[HLF_R],
							(char_u *)"replace with %s (y/n/a/q/^E/^Y)?",
							sub);
					showruler(TRUE);
					RedrawingDisabled = temp;

					++no_mapping;				/* don't map this key */
					i = vgetc();
					--no_mapping;

						/* clear the question */
					msg_didout = FALSE;			/* don't scroll up */
					msg_col = 0;
					gotocmdline(TRUE);
					if (i == 'q' || i == ESC || i == Ctrl('C'))
					{
						got_quit = TRUE;
						break;
					}
					else if (i == 'n')
						goto skip;
					else if (i == 'y')
						break;
					else if (i == 'a')
					{
						do_ask = FALSE;
						break;
					}
					else if (i == Ctrl('E'))
						scrollup_clamp();
					else if (i == Ctrl('Y'))
						scrolldown_clamp();
				}
				if (got_quit)
					break;

						/* get length of substitution part */
				sublen = vim_regsub(prog, sub, old_line, FALSE, (int)p_magic);
				if (new_start == NULL)
				{
					/*
					 * Get some space for a temporary buffer to do the
					 * substitution into (and some extra space to avoid
					 * too many calls to alloc()/free()).
					 */
					new_start_len = STRLEN(old_copy) + sublen + 25;
					if ((new_start = alloc_check(new_start_len)) == NULL)
						goto outofmem;
					*new_start = NUL;
					new_end = new_start;
				}
				else
				{
					/*
					 * Extend the temporary buffer to do the substitution into.
					 * Avoid an alloc()/free(), it takes a lot of time.
					 */
					len = STRLEN(new_start);
					needed_len = len + STRLEN(old_copy) + sublen + 1;
					if (needed_len > new_start_len)
					{
						needed_len += 20;		/* get some extra */
						if ((p1 = alloc_check(needed_len)) == NULL)
							goto outofmem;
						STRCPY(p1, new_start);
						vim_free(new_start);
						new_start = p1;
						new_start_len = needed_len;
					}
					new_end = new_start + len;
				}

				/*
				 * copy the text up to the part that matched
				 */
				i = prog->startp[0] - old_copy;
				vim_memmove(new_end, old_copy, (size_t)i);
				new_end += i;

				vim_regsub(prog, sub, new_end, TRUE, (int)p_magic);
				sub_nsubs++;
				did_sub = TRUE;

				/*
				 * Now the trick is to replace CTRL-Ms with a real line break.
				 * This would make it impossible to insert CTRL-Ms in the text.
				 * That is the way vi works. In Vim the line break can be
				 * avoided by preceding the CTRL-M with a CTRL-V. Now you can't
				 * precede a line break with a CTRL-V, big deal.
				 */
				while ((p1 = vim_strchr(new_end, CR)) != NULL)
				{
					if (p1 == new_end || p1[-1] != Ctrl('V'))
					{
						if (u_inssub(lnum) == OK)	/* prepare for undo */
						{
							*p1 = NUL;				/* truncate up to the CR */
							mark_adjust(lnum, MAXLNUM, 1L, 0L);
							ml_append(lnum - 1, new_start,
										(colnr_t)(p1 - new_start + 1), FALSE);
							++lnum;
							++eap->line2;		/* number of lines increases */
							STRCPY(new_start, p1 + 1);	/* copy the rest */
							new_end = new_start;
						}
					}
					else							/* remove CTRL-V */
					{
						STRCPY(p1 - 1, p1);
						new_end = p1;
					}
				}

				old_copy = prog->endp[0];	/* remember next character to be copied */
				/*
				 * continue searching after the match
				 * prevent endless loop with patterns that match empty strings,
				 * e.g. :s/$/pat/g or :s/[a-z]* /(&)/g
				 */
skip:
				match = -1;
				lastone = (*old_match == NUL || got_int || got_quit || !do_all);
				if (lastone || do_ask ||
					  (match = vim_regexec(prog, old_match, (int)FALSE)) == 0)
				{
					if (new_start)
					{
						/*
						 * Copy the rest of the line, that didn't match.
						 * Old_match has to be adjusted, we use the end of the
						 * line as reference, because the substitute may have
						 * changed the number of characters.
						 */
						STRCAT(new_start, old_copy);
						i = old_line + STRLEN(old_line) - old_match;
						if (u_savesub(lnum) == OK)
							ml_replace(lnum, new_start, TRUE);

						vim_free(old_line);			/* free the temp buffer */
						old_line = new_start;
						new_start = NULL;
						old_match = old_line + STRLEN(old_line) - i;
						if (old_match < old_line)		/* safety check */
						{
							EMSG("do_sub internal error: old_match < old_line");
							old_match = old_line;
						}
						old_copy = old_line;
					}
					if (match == -1 && !lastone)
						match = vim_regexec(prog, old_match, (int)FALSE);
					if (match <= 0)	  /* quit loop if there is no more match */
						break;
				}
				line_breakcheck();

			}
			if (did_sub)
				++sub_nlines;
			vim_free(old_line);		/* free the copy of the original line */
			old_line = NULL;
		}
		line_breakcheck();
	}

outofmem:
	vim_free(old_line);		/* may have to free an allocated copy of the line */
	if (sub_nsubs)
	{
		CHANGED;
		approximate_botline();
		if (!global_busy)
		{
			update_topline();
			beginline(TRUE);
			update_screen(NOT_VALID); /* need this to update LineSizes */
			if (!do_sub_msg() && do_ask)
				MSG("");
		}
		if (do_print)
			print_line(curwin->w_cursor.lnum, FALSE);
	}
	else if (!global_busy)
	{
		if (got_int)			/* interrupted */
			emsg(e_interr);
		else if (got_match)		/* did find something but nothing substituted */
			MSG("");
		else					/* nothing found */
			emsg(e_nomatch);
	}

	vim_free(prog);
}

/*
 * Give message for number of substitutions.
 * Can also be used after a ":global" command.
 * Return TRUE if a message was given.
 */
	static int
do_sub_msg()
{
	/*
	 * Only report substitutions when:
	 * - more than 'report' substitutions
	 * - command was typed by user, or number of changed lines > 'report'
	 * - giving messages is not disabled by 'lazyredraw'
	 */
	if (sub_nsubs > p_report &&
			(KeyTyped || sub_nlines > 1 || p_report < 1) &&
			messaging())
	{
		sprintf((char *)msg_buf, "%s%ld substitution%s on %ld line%s",
				got_int ? "(Interrupted) " : "",
				sub_nsubs, plural(sub_nsubs),
				(long)sub_nlines, plural((long)sub_nlines));
		if (msg(msg_buf))
		{
			keep_msg = msg_buf;
			keep_msg_attr = 0;
		}
		return TRUE;
	}
	if (got_int)
	{
		emsg(e_interr);
		return TRUE;
	}
	return FALSE;
}

/*
 * do_glob(cmd)
 *
 * Execute a global command of the form:
 *
 * g/pattern/X : execute X on all lines where pattern matches
 * v/pattern/X : execute X on all lines where pattern does not match
 *
 * where 'X' is an EX command
 *
 * The command character (as well as the trailing slash) is optional, and
 * is assumed to be 'p' if missing.
 *
 * This is implemented in two passes: first we scan the file for the pattern and
 * set a mark for each line that (not) matches. secondly we execute the command
 * for each line that has a mark. This is required because after deleting
 * lines we do not know where to search for the next match.
 */
	void
do_glob(eap)
	EXARG		*eap;
{
	linenr_t		lnum;		/* line number according to old situation */
	linenr_t		old_lcount; /* b_ml.ml_line_count before the command */
	int 			ndone;
	int				type;		/* first char of cmd: 'v' or 'g' */
	char_u			*cmd;		/* command argument */

	char_u			delim;		/* delimiter, normally '/' */
	char_u		   *pat;
	vim_regexp	   *prog;
	int				match;
	int				which_pat;

	if (global_busy)
	{
		EMSG("Cannot do :global recursive");	/* will increment global_busy */
		return;
	}

	type = *eap->cmd;
	cmd = eap->arg;
	which_pat = RE_LAST;			/* default: use last used regexp */
	sub_nsubs = 0;
	sub_nlines = 0;

	/*
	 * undocumented vi feature:
	 *	"\/" and "\?": use previous search pattern.
	 *  	     "\&": use previous substitute pattern.
	 */
	if (*cmd == '\\')
	{
		++cmd;
		if (vim_strchr((char_u *)"/?&", *cmd) == NULL)
		{
			emsg(e_backslash);
			return;
		}
		if (*cmd == '&')
			which_pat = RE_SUBST;		/* use previous substitute pattern */
		else
			which_pat = RE_SEARCH;		/* use previous search pattern */
		++cmd;
		pat = (char_u *)"";
	}
	else if (*cmd == NUL)
	{
		EMSG("Regular expression missing from global");
		return;
	}
	else
	{
		delim = *cmd; 			/* get the delimiter */
		if (delim)
			++cmd;				/* skip delimiter if there is one */
		pat = cmd;				/* remember start of pattern */
		cmd = skip_regexp(cmd, delim, (int)p_magic);
		if (cmd[0] == delim)				/* end delimiter found */
			*cmd++ = NUL;					/* replace it with a NUL */
	}

	if ((prog = search_regcomp(pat, RE_BOTH, which_pat, SEARCH_HIS)) == NULL)
	{
		emsg(e_invcmd);
		return;
	}

/*
 * pass 1: set marks for each (not) matching line
 */
	ndone = 0;
	for (lnum = eap->line1; lnum <= eap->line2 && !got_int; ++lnum)
	{
		/* a match on this line? */
		match = vim_regexec(prog, ml_get(lnum), (int)TRUE);
		if ((type == 'g' && match) || (type == 'v' && !match))
		{
			ml_setmarked(lnum);
			ndone++;
		}
		line_breakcheck();
	}

/*
 * pass 2: execute the command for each line that has been marked
 */
	if (got_int)
		MSG("Interrupted");
	else if (ndone == 0)
		msg(e_nomatch);
	else
	{
		/*
		 * Set current position only once for a global command.
		 * If global_busy is set, setpcmark() will not do anything.
		 * If there is an error, global_busy will be incremented.
		 */
		setpcmark();

		global_busy = 1;
		old_lcount = curbuf->b_ml.ml_line_count;
		while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1)
		{
			curwin->w_cursor.lnum = lnum;
			curwin->w_cursor.col = 0;
			if (*cmd == NUL || *cmd == '\n')
				do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT);
			else
				do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT);
			ui_breakcheck();
		}

		global_busy = 0;
		adjust_cursor();		/* cursor may be beyond the end of the line */

		/*
		 * Redraw everything.  Could use CLEAR, which is faster in some
		 * situations, but when there are few changes this makes the display
		 * flicker.
		 */
		redraw_later(NOT_VALID);

		/* If subsitutes done, report number of substitues, otherwise report
		 * number of extra or deleted lines. */
		if (!do_sub_msg())
			msgmore(curbuf->b_ml.ml_line_count - old_lcount);
	}

	ml_clearmarked();      /* clear rest of the marks */
	vim_free(prog);
}

#ifdef VIMINFO
	int
read_viminfo_sub_string(line, fp, force)
	char_u	*line;
	FILE	*fp;
	int		force;
{
	if (old_sub != NULL && force)
		vim_free(old_sub);
	if (force || old_sub == NULL)
	{
		viminfo_readstring(line);
		old_sub = vim_strsave(line + 1);
	}
	return vim_fgets(line, LSIZE, fp);
}

	void
write_viminfo_sub_string(fp)
	FILE	*fp;
{
	if (get_viminfo_parameter('/') != 0 && old_sub != NULL)
	{
		fprintf(fp, "\n# Last Substitute String:\n$");
		viminfo_writestring(fp, old_sub);
	}
}
#endif /* VIMINFO */

/*************************************************************************
 * functions for ":help": open a read-only window on the help.txt file
 */

	void
do_help(arg)
	char_u		*arg;
{
	FILE	*helpfd;			/* file descriptor of help file */
	int		n;
	WIN		*wp;
	int		num_matches;
	char_u	**matches;
	int		need_free = FALSE;

	/*
	 * If an argument is given, check if there is a match for it.
	 */
	if (*arg != NUL)
	{
		n = find_help_tags(arg, &num_matches, &matches);
		if (num_matches == 0 || n == FAIL)
		{
			EMSG2("Sorry, no help for %s", arg);
			return;
		}

		/* The first match is the best match */
		arg = vim_strsave(matches[0]);
		need_free = TRUE;
		FreeWild(num_matches, matches);
	}

	/*
	 * If there is already a help window open, use that one.
	 */
	if (!curwin->w_buffer->b_help)
	{
		for (wp = firstwin; wp != NULL; wp = wp->w_next)
			if (wp->w_buffer != NULL && wp->w_buffer->b_help)
				break;
		if (wp != NULL && wp->w_buffer->b_nwindows > 0)
			win_enter(wp, TRUE);
		else
		{
			/*
			 * There is no help buffer yet.
			 * Try to open the file specified by the "helpfile" option.
			 */
			if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL)
			{
				smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf);
				goto erret;
			}
			fclose(helpfd);

			if (win_split(0, FALSE) == FAIL)
				goto erret;

			if (curwin->w_height < p_hh)
				win_setheight((int)p_hh);

#ifdef RIGHTLEFT
			curwin->w_p_rl = 0;				/* help window is left-to-right */
#endif
			curwin->w_p_nu = 0;				/* no line numbers */

			/*
			 * open help file (do_ecmd() will set b_help flag, readfile() will
			 * set b_p_ro flag)
			 */
			(void)do_ecmd(0, p_hf, NULL, NULL, (linenr_t)0,
												   ECMD_HIDE + ECMD_SET_HELP);

			/* save the values of the options we change */
			vim_free(help_save_isk);
			help_save_isk = vim_strsave(curbuf->b_p_isk);
			help_save_ts = curbuf->b_p_ts;

			/* accept all chars for keywords, except ' ', '*', '"', '|' */
			set_string_option((char_u *)"isk", -1,
											 (char_u *)"!-~,^*,^|,^\"", TRUE);
			curbuf->b_p_ts = 8;
			check_buf_options(curbuf);
			(void)init_chartab();		/* needed because 'isk' changed */
		}
	}

	restart_edit = 0;		/* don't want insert mode in help file */

	stuffReadbuff((char_u *)":ta ");
	if (arg != NULL && *arg != NUL)
		stuffReadbuff(arg);
	else
		stuffReadbuff((char_u *)"help.txt");		/* go to the index */
	stuffcharReadbuff('\n');

erret:
	if (need_free)
		vim_free(arg);
}

/*
 * Return a heuristic indicating how well the given string matches.  The
 * smaller the number, the better the match.  This is the order of priorities,
 * from best match to worst match:
 *		- Match with least alpha-numeric characters is better.
 *		- Match with least total characters is better.
 *		- Match towards the start is better.
 * Assumption is made that the matched_string passed has already been found to
 * match some string for which help is requested.  webb.
 */
	int
help_heuristic(matched_string, offset)
	char_u	*matched_string;
	int		offset;				/* offset for match */
{
	int		num_letters;
	char_u	*p;

	num_letters = 0;
	for (p = matched_string; *p; p++)
		if (isalnum(*p))
			num_letters++;

	/*
	 * Multiply the number of letters by 100 to give it a much bigger
	 * weighting than the number of characters.
	 * If the match starts in the middle of a word, add 10000 to put it
	 * somewhere in the last half.
	 * If the match is more than 2 chars from the start, multiply by 200 to
	 * put it after matches at the start.
	 */
	if (isalnum(matched_string[offset]) && offset > 0 &&
										  isalnum(matched_string[offset - 1]))
		offset += 10000;
	else if (offset > 2)
		offset *= 200;
	return (int)(100 * num_letters + STRLEN(matched_string) + offset);
}

/*
 * Compare functions for qsort() below, that checks the help heuristics number
 * that has been put after the tagname by find_tags().
 */
	static int
help_compare(s1, s2)
	const void	*s1;
	const void	*s2;
{
	char	*p1;
	char	*p2;

	p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
	p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
	return strcmp(p1, p2);
}

/*
 * Find all help tags matching "arg", sort them and return in matches[], with
 * the number of matches in num_matches.
 * We try first with case, and then ignoring case.  Then we try to choose the
 * "best" match from the ones found.
 */
	int
find_help_tags(arg, num_matches, matches)
	char_u		*arg;
	int			*num_matches;
	char_u		***matches;
{
	char_u		*s, *d;
	vim_regexp	*prog;
	int			attempt;
	int			retval = FAIL;
	int			i;
	static char *(mtable[]) = {"*", "g*", "[*", "]*",
							   "/*", "/\\*", "/\\(\\)",
							   "?", ":?", "?<CR>"};
	static char *(rtable[]) = {"star", "gstar", "[star", "]star",
							   "/star", "/\\\\star", "/\\\\(\\\\)",
							   "?", ":?", "?<CR>"};

	d = IObuff;				/* assume IObuff is long enough! */

	/*
	 * Recognize a few exceptions to the rule.  Some strings that contain '*'
	 * with "star".  Otherwise '*' is recognized as a wildcard.
	 */
	for (i = sizeof(mtable) / sizeof(char *); --i >= 0; )
	{
		if (STRCMP(arg, mtable[i]) == 0)
		{
			STRCPY(d, rtable[i]);
			break;
		}
	}

	if (i < 0)		/* no match in table, replace single characters */
	{
		for (s = arg; *s; ++s)
		{
			/*
			 * Replace "|" with "bar" and '"' with "quote" to match the name of
			 * the tags for these commands.
			 * Replace "*" with ".*" and "?" with "." to match command line
			 * completion.
			 * Insert a backslash before '~', '$' and '.' to avoid their
			 * special meaning.
			 */
			if (d - IObuff > IOSIZE - 10)		/* getting too long!? */
				break;
			switch (*s)
			{
				case '|':	STRCPY(d, "bar");
							d += 3;
							continue;
				case '\"':	STRCPY(d, "quote");
							d += 5;
							continue;
				case '*':	*d++ = '.';
							break;
				case '?':	*d++ = '.';
							continue;
				case '$':
				case '.':
				case '~':	*d++ = '\\';
							break;
			}

			/*
			 * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
			 * ":help i_^_CTRL-D" work.
			 */
			if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_'))	/* ^x */
			{
				STRCPY(d, "CTRL-");
				d += 5;
				if (*s < ' ')
				{
					*d++ = *s + '@';
					continue;
				}
				++s;
			}
			else if (*s == '^')			/* "^" or "CTRL-^" or "^_" */
				*d++ = '\\';

			/*
			 * Insert a backslash before a backslash after a slash, for search
			 * pattern tags: "/\|" --> "/\\|".
			 */
			else if (s[0] == '\\' && s[1] != '\\' &&
												  *arg == '/' && s == arg + 1)
				*d++ = '\\';

			*d++ = *s;

			/*
			 * If tag starts with ', toss everything after a second '. Fixes
			 * CTRL-] on 'option'. (would include the trailing '.').
			 */
			if (*s == '\'' && s > arg && *arg == '\'')
				break;
		}
		*d = NUL;
	}

	reg_ic = FALSE;
	prog = vim_regcomp(IObuff, (int)p_magic);
	if (prog == NULL)
		return FAIL;

	/* First try to match with case, then without */
	for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE)
	{
		*matches = (char_u **)"";
		*num_matches = 0;
		retval = find_tags(NULL, prog, num_matches, matches, TRUE, FALSE);
		if (retval == FAIL || *num_matches)
			break;
	}
	vim_free(prog);

#ifdef HAVE_QSORT
	/*
	 * Sort the matches found on the heuristic number that is after the
	 * tag name.  If there is no qsort, the output will be messy!
	 */
	qsort((void *)*matches, (size_t)*num_matches,
											  sizeof(char_u *), help_compare);
#endif
	return OK;
}

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