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

/*
 *
 * csearch.c: command line searching commands
 */

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

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

int global_busy = 0;			/* set to 1 if global busy, 2 if global has
									been called during a global command */
int global_wait;				/* set to 1 if wait_return has to be called
									after global command */
extern regexp *myregcomp __ARGS((char *));

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

	void
dosub(lp, up, cmd, nextcommand)
	linenr_t	lp;
	linenr_t	up;
	char		*cmd;
	u_char		**nextcommand;
{
	linenr_t		lnum;
	long			i;
	char		   *ptr;
	regexp		   *prog;
	long			nsubs = 0;
	linenr_t		nlines = 0;
	static int		do_all = FALSE; 	/* do multiple substitutions per line */
	static int		do_ask = FALSE; 	/* ask for confirmation */
	char		   *pat, *sub = NULL;
	static char    *old_sub = NULL;
	int 			delimiter;
	int 			sublen;
	int				got_quit = FALSE;
	int				got_match = FALSE;
	int				temp;

	if (strchr("0123456789gc|\"#", *cmd) == NULL)       /* new pattern and substitution */
	{
		delimiter = *cmd++;			/* remember delimiter character */
		pat = cmd;					/* remember the start of the regexp */

		/*
		 * do the next loop twice:
		 *  i == 0: find the end of the regexp
		 *  i == 1: find the end of the substitution
		 */
		for (i = 0; ; ++i)
		{
			while (cmd[0])
			{
				if (cmd[0] == delimiter)			/* end delimiter found */
				{
					*cmd++ = NUL;					/* replace it by a NUL */
					break;
				}
				if (cmd[0] == '\\' && cmd[1] != 0)	/* skip escaped characters */
					++cmd;
				++cmd;
			}
			if (i == 1)
				break;
			sub = cmd;				/* remember the start of the substitution */
		}
		free(old_sub);
		old_sub = strsave(sub);
	}
	else								/* use previous pattern and substitution */
	{
		if (old_sub == NULL)    /* there is no previous command */
		{
			beep();
			return;
		}
		pat = NULL; 			/* myregcomp() will use previous pattern */
		sub = old_sub;
	}

	/*
	 * find trailing options
	 */
	if (!p_ed)
	{
		do_all = FALSE;
		do_ask = FALSE;
	}
	while (*cmd)
	{
		if (*cmd == 'g')
			do_all = !do_all;
		else if (*cmd == 'c')
			do_ask = !do_ask;
		else
			break;
		++cmd;
	}

	/*
	 * check for a trailing count
	 */
	skipspace(&cmd);
	if (isdigit(*cmd))
	{
		i = getdigits(&cmd);
		if (i <= 0)
		{
			emsg(e_zerocount);
			return;
		}
		lp = up;
		up += i - 1;
	}

	/*
	 * check for trailing '|', '"' or '#'
	 */
	skipspace(&cmd);
	if (*cmd)
	{
		if (strchr("|\"#", *cmd) != NULL)
		{
			*nextcommand = (u_char *)cmd;
		}
		else
		{
			emsg(e_trailing);
			return;
		}
	}

	if ((prog = myregcomp(pat)) == NULL)
	{
		emsg(e_invcmd);
		return;
	}

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

	for (lnum = lp; lnum <= up && !(got_int || got_quit); ++lnum)
	{
		ptr = nr2ptr(lnum);
		if (regexec(prog, ptr, TRUE))  /* a match on this line */
		{
			char		*new_end, *new_start = NULL;
			char		*old_match, *old_copy;
			char		*prev_old_match = NULL;
			char		*p1, *p2;
			int			did_sub = FALSE;
			int			match, lastone;

			if (!got_match)
			{
				setpcmark();
				got_match = TRUE;
			}

			/*
			 * Save the line that was last changed for the final cursor
			 * position (just like the real vi).
			 */
			Curpos.lnum = lnum;

			old_copy = old_match = ptr;
			for (;;)			/* loop until nothing more to replace */
			{
				Curpos.col = (int)(prog->startp[0] - ptr);
				/*
				 * 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 skip2;
				}
				while (do_ask)		/* loop until 'y', 'n' or 'q' typed */
				{
					temp = RedrawingDisabled;
					RedrawingDisabled = FALSE;
					updateScreen(CURSUPD);
					if (p_fm)
					{
						sprintf(IObuff, "replace by %s (y/n/q)? ", sub);
						screen_msg(TRUE, IObuff);
					}
					else
						msg("replace by %s (y/n/q)? ", sub);
					setcursor();
					RedrawingDisabled = temp;
					if ((i = vgetc()) == 'q' || i == ESC || i == Ctrl('C'))
					{
						got_quit = TRUE;
						if (p_fm)
							screen_msg(FALSE, NULL);
						break;
					}
					else if (i == 'n')
						goto skip;
					else if (i == 'y')
						break;
				}
				if (got_quit)
					break;

						/* get length of substitution part */
				sublen = regsub(prog, sub, ptr, 0, (int)p_magic);
				if (new_start == NULL)
				{
					/*
					 * Get some space for a temporary buffer to do the substitution
					 * into.
					 */
					if ((new_start = alloc((unsigned)(strlen(ptr) + sublen + 5))) == NULL)
						goto outofmem;
					*new_start = NUL;
				}
				else
				{
					/*
					 * extend the temporary buffer to do the substitution into.
					 */
					if ((p1 = alloc((unsigned)(strlen(new_start) + strlen(old_copy) + sublen + 1))) == NULL)
						goto outofmem;
					strcpy(p1, new_start);
					free(new_start);
					new_start = p1;
				}

				for (new_end = new_start; *new_end; new_end++)
					;
				/*
				 * copy up to the part that matched
				 */
				while (old_copy < prog->startp[0])
					*new_end++ = *old_copy++;

				regsub(prog, sub, new_end, 1, (int)p_magic);
				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 = strchr(new_end, CR)) != NULL)
				{
					if (p1 == new_end || p1[-1] != Ctrl('V'))
					{
						if (u_inssub(lnum))				/* prepare for undo */
						{
							*p1 = NUL;					/* truncate up to the CR */
							if ((p2 = save_line(new_start)) != NULL)
							{
								appendline(lnum - 1, p2);
								++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:
				old_match = prog->endp[0];
				prev_old_match = old_match;
skip2:
				match = -1;
				lastone = (*old_match == NUL || got_int || got_quit || !do_all);
				if (lastone || do_ask || (match = regexec(prog, old_match, (int)FALSE)) == 0)
				{
					if (new_start)
					{
						/*
						 * copy the rest of the line, that didn't match
						 */
						strcat(new_start, old_copy);
						i = old_match - ptr;

						if ((ptr = save_line(new_start)) != NULL && u_savesub(lnum))
							replaceline(lnum, ptr);

						free(new_start);          /* free the temp buffer */
						new_start = NULL;
						old_match = ptr + i;
						old_copy = ptr;
					}
					if (match == -1 && !lastone)
						match = regexec(prog, old_match, (int)FALSE);
					if (match <= 0)		/* quit loop if there is no more match */
						break;
				}
					/* breakcheck is slow, don't call it too often */
				if ((nsubs & 15) == 0)
					breakcheck();

			}
			if (did_sub)
				++nlines;
		}
			/* breakcheck is slow, don't call it too often */
		if ((lnum & 15) == 0)
			breakcheck();
	}

outofmem:
	if (nsubs)
	{
		CHANGED;
		updateScreen(CURSUPD); /* need this to update LineSizes */
		beginline(TRUE);
		if (nsubs > p_report)
			smsg("%s%ld substitution%s on %ld line%s",
								got_int ? "(Interrupted) " : "",
								nsubs, plural(nsubs),
								(long)nlines, plural((long)nlines));
		else if (got_int)
				msg(e_interr);
		else if (do_ask)
			 {
				if (p_fm)
					screen_msg(FALSE, NULL);
				else
					msg("");
			 }
	}
	else if (got_int)		/* interrupted */
		msg(e_interr);
	else if (got_match)		/* did find something but nothing substituted */
		msg("");
	else					/* nothing found */
		msg(e_nomatch);

	free((char *) prog);
}

/*
 * doglob(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
doglob(type, lp, up, cmd)
	int 		type;
	linenr_t	lp, up;
	char		*cmd;
{
	linenr_t		lnum;		/* line number according to old situation */
	linenr_t		old_lcount; /* line_count before the command */
	int 			ndone;

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

	if (global_busy)
	{
		emsg("Cannot do :global recursive");
		++global_busy;
		return;
	}

	delim = *cmd; 			/* get the delimiter */
	if (delim)
		++cmd;				/* skip delimiter if there is one */
	pat = cmd;

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

	reg_ic = p_ic;           /* set "ignore case" flag appropriately */

	if ((prog = myregcomp(pat)) == NULL)
	{
		emsg(e_invcmd);
		return;
	}

	if (!*cmd || *cmd == 'p')
	{						/* redraw cmdbuf as a separator between the text and the result */
		*(cmd - 1) = delim;
		outstr(T_TI);
		redrawcmd();
		outstr(T_TP);
		outchar('\n');
	}
	else
		msg("");


/*
 * pass 1: set marks for each (not) matching line
 */
	ndone = 0;
	for (lnum = lp; lnum <= up && !got_int; ++lnum)
	{
		match = regexec(prog, nr2ptr(lnum), (int)TRUE);     /* a match on this line? */
		if ((type == 'g' && match) || (type == 'v' && !match))
		{
			setmarked(lnum);
			ndone++;
		}
			/* breakcheck is slow, don't call it too often */
		if ((lnum & 15) == 0)
			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
	{
		global_busy = 1;
		global_wait = 0;
		RedrawingDisabled = TRUE;
		old_lcount = line_count;
		while (!got_int && (lnum = firstmarked()) != 0 && global_busy == 1)
		{
			Curpos.lnum = lnum;
			Curpos.col = 0;
			if (*cmd == NUL)
				docmdline((u_char *)"p");
			else
				docmdline((u_char *)cmd);
			breakcheck();
		}

		RedrawingDisabled = FALSE;
		global_busy = 0;
		if (global_wait)                /* wait for return */
			wait_return(FALSE);
		screenclear();
		updateScreen(CURSUPD);
		msgmore(line_count - old_lcount);
	}

	clearmarked();      /* clear rest of the marks */
	free((char *) prog);
}


syntax highlighted by Code2HTML, v. 0.9.1