/* 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.
 */

/*
 *
 * csearch.c: do_sub() and do_glob() for :s, :g and :v
 */

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

/* we use modified Henry Spencer's regular expression routines */
#include "regexp.h"

static int do_sub_msg __ARGS((void));

#ifdef VIMINFO
	static char_u   *old_sub = NULL;
#endif /* VIMINFO */

/* do_sub(lp, up, cmd)
 *
 * Perform a substitution from line 'lp' to line 'up' using the
 * command pointed to by 'cmd' 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.
 *
 * use_old == 0 for :substitute
 * use_old == 1 for :&
 * use_old == 2 for :~
 */

static long			sub_nsubs;		/* total number of substitutions */
static linenr_t		sub_nlines;		/* total number of lines changed */

	void
do_sub(lp, up, cmd, nextcommand, use_old)
	linenr_t	lp;
	linenr_t	up;
	char_u		*cmd;
	char_u		**nextcommand;
	int			use_old;
{
	linenr_t		lnum;
	long			i;
	char_u		   *ptr;
	char_u		   *old_line;
	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;
#ifndef VIMINFO						/* otherwise it is global */
	static char_u   *old_sub = NULL;
#endif
	int 			delimiter;
	int 			sublen;
	int				got_quit = FALSE;
	int				got_match = FALSE;
	int				temp;
	int				which_pat;
	
	if (!global_busy)
	{
		sub_nsubs = 0;
		sub_nlines = 0;
	}

	if (use_old == 2)
		which_pat = RE_LAST;	/* use last used regexp */
	else
		which_pat = RE_SUBST;	/* use last substitute regexp */

								/* new pattern and substitution */
	if (use_old == 0 && *cmd != NUL &&
					   vim_strchr((char_u *)"0123456789gcr|\"", *cmd) == NULL)
	{
								/* don't accept alphanumeric for separator */
		if (isalpha(*cmd) || isdigit(*cmd))
		{
			EMSG("Regular expressions can't be delimited by letters or digits");
			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);
			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 = strsave(sub);
	}
	else								/* use previous pattern and substitution */
	{
		if (old_sub == NULL)    /* there is no previous command */
		{
			emsg(e_nopresub);
			return;
		}
		pat = NULL; 			/* myregcomp() 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;
		}
		lp = up;
		up += i - 1;
	}

	/*
	 * check for trailing '|', '"' or '\n'
	 */
	cmd = skipwhite(cmd);
	if (*cmd && *cmd != '\"')
	{
		if (vim_strchr((char_u *)"|\n", *cmd) == NULL)
		{
			emsg(e_trailing);
			return;
		}
		else
			*nextcommand = cmd + 1;
	}

	if ((prog = myregcomp(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 = lp; lnum <= up && !(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 = 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);

				/*
				 * 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;
					comp_Botline(curwin);
					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;
					updateScreen(CURSUPD);
					highlight_match = FALSE;
					redraw_later(NOT_VALID);
									/* same highlighting as for wait_return */
					(void)set_highlight('r');
					msg_highlight = TRUE;
					smsg((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;
							++up;				/* 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;
		if (!global_busy)
		{
			updateScreen(CURSUPD); /* need this to update LineSizes */
			beginline(TRUE);
			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()
{
	if (sub_nsubs > p_report)
	{
		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;
		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(type, lp, up, cmd)
	int 		type;
	linenr_t	lp, up;
	char_u		*cmd;
{
	linenr_t		lnum;		/* line number according to old situation */
	linenr_t		old_lcount; /* curbuf->b_ml.ml_line_count before the command */
	int 			ndone;

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

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

	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);
		if (cmd[0] == delim)				/* end delimiter found */
			*cmd++ = NUL;					/* replace it with a NUL */
	}

	if ((prog = myregcomp(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 = lp; lnum <= up && !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", FALSE, TRUE);
			else
				do_cmdline(cmd, FALSE, TRUE);
			mch_breakcheck();
		}

		global_busy = 0;

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

		/* 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 = 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 */
