/*
 *	Search & Replace system
 *	Copyright
 *		(C) 1992 Joseph H. Allen
 *
 *	This file is part of JOE (Joe's Own Editor)
 */
#include "types.h"

int wrap = 0;			/* Allow wrap */
int smode = 0;			/* Decremented to zero by execmd */
int csmode = 0;			/* Set for continued search mode */
int opt_icase = 0;			/* Set to force case insensitive search */
int pico = 0;			/* Pico search prompting */

B *findhist = NULL;		/* Search string history */
B *replhist = NULL;		/* Replacement string history */

SRCH *globalsrch = NULL;	/* Most recent completed search data */

static SRCHREC fsr = { {&fsr, &fsr} };

/* Clear compiled version of pattern */

static void clrcomp(SRCH *srch)
{
	if (srch->comp) {
		joe_regfree(srch->comp);
		srch->comp = 0;
	}
}

/* Set pattern, clear compiled version of it */

void setpat(SRCH *srch, char *s)
{
	vsrm(srch->pattern);
	srch->pattern = s;
	clrcomp(srch);
}

/* Completion stuff: should go somewhere else */

static char **word_list;

#define MAX_WORD_SIZE 64
static char **get_word_list(B *b,off_t ignore)
{
	char buf[MAX_WORD_SIZE];
	char *s;
	char **list = 0;
	HASH *h;
	HENTRY *t;
	P *p;
	int c;
	ptrdiff_t idx;
	off_t start = 0;

	h = htmk(1024);

	p = pdup(b->bof, "get_word_list");
	idx = 0;
	do {
		c = pgetc(p);
		if (idx) {
			if (joe_isalnum_(b->o.charmap, c)) {
				if (b->o.charmap->type) {
					if (idx < MAX_WORD_SIZE - 8) {
						idx += utf8_encode(buf+idx, c);
					}
				} else {
					if (idx!=MAX_WORD_SIZE)
						buf[idx++] = TO_CHAR_OK(c);
				}
			} else {
				if (idx!=MAX_WORD_SIZE && start!=ignore) {
					buf[idx] = 0;
					if (!htfind(h,buf)) {
						s = vsncpy(NULL,0,buf,idx);
						htadd(h, s, s);
					}
				}
				idx = 0;
			}
		} else {
			start=p->byte-1;
			if (joe_isalpha_(b->o.charmap, c)) {
				if (b->o.charmap->type) {
					idx += utf8_encode(buf+idx, c);
				} else {
					buf[idx++] = TO_CHAR_OK(c);
				}
			}
		}
	} while (c != NO_MORE_DATA);
	prm(p);

	for (idx = 0;idx != h->len;++idx)
		for (t = h->tab[idx];t;t=t->next)
			list = vaadd(list, (char *)t->val);
	if (list)
		vasort(list,sLEN(list));

	htrm(h);

	return list;
}

static void fcmplt_ins(BW *bw, char *line)
{
	P *p;
	int c;

	if (!piseol(bw->cursor)) {
		c = brch(bw->cursor);
		if (joe_isalnum_(bw->b->o.charmap, c))
			return;
	}

	/* Move p to beginning of word */

	p = pdup(bw->cursor, "fcmplt_ins");
	do
		c = prgetc(p);
		while (joe_isalnum_(bw->b->o.charmap,c));
	if (c!=NO_MORE_DATA)
		pgetc(p);

	if (bw->cursor->byte!=p->byte && bw->cursor->byte-p->byte<64) {
		/* Insert single match */
		bdel(p,bw->cursor);
		binsm(bw->cursor,sv(line));
		pfwrd(bw->cursor,sLEN(line));
		bw->cursor->xcol = piscol(bw->cursor);
		prm(p);
	} else {
		prm(p);
	}
}

static int fcmplt_abrt(W *w, ptrdiff_t x, void *obj)
{
	BW *bw;
	char *line = (char *)obj;
	WIND_BW(bw, w);
	if (line) {
		fcmplt_ins(bw, line);
		vsrm(line);
	}
	return -1;
}

static int fcmplt_rtn(MENU *m, ptrdiff_t x, void *obj, int k)
{
	char *line = (char *)obj;
	fcmplt_ins((BW *)m->parent->win->object, m->list[x]);
	vsrm(line);
	m->object = NULL;
	wabort(m->parent);
	return 0;
}

int ufinish(W *w, int k)
{
	char *line;
	char *line1;
	char **lst;
	P *p;
	int c;
	MENU *m;
	BW *bw;
	WIND_BW(bw, w);

	/* Make sure we're not in a word */

	if (!piseol(bw->cursor)) {
		c = brch(bw->cursor);
		if (joe_isalnum_(bw->b->o.charmap, c))
			return -1;
	}

	/* Move p to beginning of word */

	p = pdup(bw->cursor, "ufinish");
	do
		c = prgetc(p);
		while (joe_isalnum_(bw->b->o.charmap,c));
	if (c!=NO_MORE_DATA)
		pgetc(p);

	if (bw->cursor->byte!=p->byte && bw->cursor->byte-p->byte<64) {
		line = brvs(p, bw->cursor->byte - p->byte); /* CVT: Risky- we assume the match fits in memory */

		/* We have a word */

		/* Get word list */
		if (word_list)
			varm(word_list);

		word_list = get_word_list(bw->b, p->byte);

		if (!word_list) {
			vsrm(line);
			prm(p);
			return -1;
		}

		line1 = vsncpy(NULL,0,sv(line));
		line1 = vsadd(line1,'*');
		lst = regsub(word_list, aLEN(word_list), line1);
		vsrm(line1);

		if (!lst) {
			ttputc(7);
			vsrm(line);
			return -1;
		}

		m = mkmenu(bw->parent, bw->parent, lst, fcmplt_rtn, fcmplt_abrt, NULL, 0, line, NULL);
		if (!m) {
			varm(lst);
			vsrm(line);
			return -1;
		}

		/* Possible match list is now in lst */

		if (aLEN(lst) == 1)
			return fcmplt_rtn(m, 0, line, 0);
		else if (smode)
			return 0;
		else {
			char *com = mcomplete(m);
			vsrm((char *)m->object);
			m->object = com;
			wabort(m->parent);
			smode = 2;
			ttputc(7);
			return 0;
		}
	} else {
		prm(p);
		return -1;
	}
}

static int srch_cmplt(BW *bw, int k)
{
	if (word_list)
		varm(word_list);

	word_list = get_word_list(((BW *)bw->parent->win->object)->b, -1);

	if (!word_list) {
		ttputc(7);
		return 0;
	}

	return simple_cmplt(bw,word_list);
}

/* Search forward.
   bw, pattern and ignore must be set

   The first possible string we can find is the one beginning under p

   Returns p if we found a string:
     The found string is placed in entire/pieces
     p is placed right after the found string

   Return 0 if we did not find the string:
     p is left in its original spot
*/

static P *searchf(BW *bw,SRCH *srch, P *p)
{
	P *start;
	P *end;
	int flag = 0;

	start = pdup(p, "searchf");
	end = pdup(p, "searchf");

	try_again:

	wrapped:
	while (srch->ignore ? pifind(start, srch->comp->prefix, srch->comp->prefix_len) : pfind(start, srch->comp->prefix, srch->comp->prefix_len)) {
		pset(end, start);
		/* pfwrd(end, x); */ /* Comment out for regexec */
		if (srch->wrap_flag && start->byte>=srch->wrap_p->byte)
			break;
		if (!joe_regexec(srch->comp, end, NMATCHES, srch->pieces, srch->ignore)) {
			if (end->byte == srch->last_repl && !flag) {
				/* Stuck on zero-width regex? */
				pset(start, p);
				if (pgetc(start) == NO_MORE_DATA)
					break;
				pset(end, start);
				++flag; /* Try repeating, but only one time */
				goto try_again;
			} else {
				srch->entire.rm_so = start->byte;
				srch->entire.rm_eo = end->byte;
				pset(p, end);
				prm(start);
				prm(end);
				srch->last_repl = p->byte; /* Prevent getting stuck with zero-length regex */
				return p;
			}
		}
		if (pgetc(start) == NO_MORE_DATA)
			break;
	}
	if (srch->allow_wrap && !srch->wrap_flag && srch->wrap_p) {
		msgnw(bw->parent, joe_gettext(_("Wrapped")));
		srch->wrap_flag = 1;
		p_goto_bof(start);
		goto wrapped;
	}
	srch->last_repl = -1;
	prm(start);
	prm(end);
	return NULL;
}

/* Search backwards.
   bw, pattern and ignore must be set

   The first possible string we can find is the one beginning one position
   to the left of p.

   Returns 1 if we found a string:
     The found string is placed in entire
     p is placed at the beginning of the string

   Return 0 if we did not find the string:
     p is left in its original spot
*/

static P *searchb(BW *bw,SRCH *srch, P *p)
{
	P *start;
	P *end;
	int flag = 0;

	start = pdup(p, "searchb");
	end = pdup(p, "searchb");

	try_again:

	wrapped:
	while (pbkwd(start, 1L)
	       && (srch->ignore ? prifind(start, srch->comp->prefix, srch->comp->prefix_len) : prfind(start, srch->comp->prefix, srch->comp->prefix_len))) {
		pset(end, start);
		/* pfwrd(end, x); */ /* Comment out for joe_regexec */
		if (srch->wrap_flag && start->byte<srch->wrap_p->byte)
			break;
		if (!joe_regexec(srch->comp, end, NMATCHES, srch->pieces, srch->ignore)) {
			if (start->byte == srch->last_repl && !flag) {
				/* Stuck? */
				pset(start, p);
				if (prgetc(start) == NO_MORE_DATA)
					break;
				pset(end, start);
				++flag;
				goto try_again;
			} else {
				srch->entire.rm_so = start->byte;
				srch->entire.rm_eo = end->byte;
				pset(p, start);
				prm(start);
				prm(end);
				return p;
			}
		}
	}

	if (srch->allow_wrap && !srch->wrap_flag && srch->wrap_p) {
		msgnw(bw->parent, joe_gettext(_("Wrapped")));
		srch->wrap_flag = 1;
		p_goto_eof(start);
		goto wrapped;
	}
	srch->last_repl = -1;
	prm(start);
	prm(end);
	return NULL;
}

/* Make a search stucture */

static SRCH *setmark(SRCH *srch)
{
	if (markv(0))
		srch->valid = 1;

	srch->markb = markb;
	if (srch->markb)
		srch->markb->owner = &srch->markb;
	markb = NULL;

	srch->markk = markk;
	if (srch->markk)
		srch->markk->owner = &srch->markk;
	markk = NULL;

	return srch;
}

SRCH *mksrch(char *pattern, char *replacement, int ignore, int backwards, int repeat, int replace, int rest, int all, int regex)
{
	SRCH *srch = (SRCH *) joe_malloc(SIZEOF(SRCH));
	int x;

	srch->first = NULL;
	srch->current = NULL;
	srch->all = all;
	srch->pattern = pattern;
	srch->comp = 0;
	srch->replacement = replacement;
	srch->ignore = ignore;
	srch->regex = regex;
	srch->debug = 0;
	srch->backwards = backwards;
	srch->repeat = repeat;
	srch->replace = replace;
	srch->rest = rest;
	srch->flg = 0;
	srch->addr = -1;
	srch->last_repl = -1;
	srch->markb = NULL;
	srch->markk = NULL;
	srch->wrap_p = NULL;
	srch->allow_wrap = wrap;
	srch->wrap_flag = 0;
	srch->valid = 0;
	srch->block_restrict = 0;
	izque(SRCHREC, link, &srch->recs);
	for (x = 0; x != NMATCHES; ++x) {
		srch->pieces[x].rm_so = -1;
		srch->pieces[x].rm_eo = -1;
	}
	srch->entire.rm_so = -1;
	srch->entire.rm_eo = -1;
	return srch;
}

/* Eliminate a search structure */

void rmsrch(SRCH *srch)
{
	if (srch->comp)
		joe_regfree(srch->comp);
	prm(srch->wrap_p);
	if (srch->markb || srch->markk) {
		/* We don't want isearch to clear an existing block, which it will do if
		   both srch->markb and srch->markk are null.  On the other hand, finishing
		   a find/replace should absolutely restore the old state, even if only one
		   of the two was set.  If we prm inside the if's below, then we get a half-
		   way state with a weird, unexpected block. */
		prm(markb);
		prm(markk);
	}
	if (srch->markb) {
		markb = srch->markb;
		markb->owner = &markb;
		markb->xcol = piscol(markb);
	}
	if (srch->markk) {
		markk = srch->markk;
		markk->owner = &markk;
		markk->xcol = piscol(markk);
	}
	frchn(&fsr, &srch->recs);
	vsrm(srch->pattern);
	vsrm(srch->replacement);
	joe_free(srch);
	updall();
}

/* Insert a replacement string
 * p is advanced past the inserted text
 */

static P *insert(SRCH *srch, P *p, const char *s, ptrdiff_t len, B **entire, B **pieces)
{
	ptrdiff_t x;
	off_t starting = p->byte;
	int nth;
	int case_flag = 0;
	B *b;

	while (len) {
		for (x = 0; x != len && s[x] != '\\'; ++x) ;
		if (x) {
			const char *t = s;
			ptrdiff_t y = x;
			switch (case_flag) {
				case 1: {
					case_flag = 0;
					FALLTHROUGH
				} case 2: {
					while (y) {
						int ch = fwrd_c(p->b->o.charmap, &t, &y);
						ch = joe_tolower(p->b->o.charmap, ch);
						binsc(p, ch);
						pgetc(p);
					}
					break;
				} case -1: {
					case_flag = 0;
					FALLTHROUGH
				} case -2: {
					while (y) {
						int ch = fwrd_c(p->b->o.charmap, &t, &y);
						ch = joe_toupper(p->b->o.charmap, ch);
						binsc(p, ch);
						pgetc(p);
					}
					break;
				}
			}
			if (y) {
				binsm(p, t, y);
				pfwrd(p, y);
			}
			len -= x;
			s += x;
		} else if (len >= 2) {
			if (s[1] == 'l') {
				case_flag = 1;
				s += 2;
				len -= 2;
			} else if (s[1] == 'L') {
				case_flag = 2;
				s += 2;
				len -= 2;
			} else if (s[1] == 'u') {
				case_flag = -1;
				s += 2;
				len -= 2;
			} else if (s[1] == 'U') {
				case_flag = -2;
				s += 2;
				len -= 2;
			} else if (s[1] == 'E') {
				case_flag = 0;
				s += 2;
				len -= 2;
			} else if (s[1] >= '1' && s[1] <= '9') {
				nth = s[1] - '1';
				s += 2;
				len -= 2;
				if (pieces[nth]) {
					b = bcpy(pieces[nth]->bof, pieces[nth]->eof);
					goto insertit;
				}
			} else if (s[1] == '&') {
				s += 2;
				len -= 2;
				if (*entire) {
					b = bcpy(entire[0]->bof, entire[0]->eof);
					insertit:
					if (case_flag) {
						P *q = pdup(b->bof, "insert");
						while (!piseof(q)) {
							int ch = pgetc(q);
							switch (case_flag) {
								case 1: {
									case_flag = 0;
									FALLTHROUGH
								} case 2: {
									ch = joe_tolower(p->b->o.charmap, ch);
									break;
								} case -1: {
									case_flag = 0;
									FALLTHROUGH
								} case -2: {
									ch = joe_toupper(p->b->o.charmap, ch);
									break;
								}
							}
							binsc(p, ch);
							pgetc(p);
						}
						prm(q);
						brm(b);
					} else {
						off_t l = b->eof->byte;
						binsb(p, b);
						pfwrd(p, l);
					}
				}
			} else {
				const char *a = s + x;
				ptrdiff_t l = len - x;
				int ch = escape(p->b->o.charmap->type, &a, &l, NULL);
				if (ch != -256) {
					switch (case_flag) {
						case 1: {
							case_flag = 0;
							FALLTHROUGH
						} case 2: {
							ch = joe_tolower(p->b->o.charmap, ch);
							break;
						} case -1: {
							case_flag = 0;
							FALLTHROUGH
						} case -2: {
							ch = joe_toupper(p->b->o.charmap, ch);
							break;
						}
					}
					binsc(p, ch);
					pgetc(p);
				}
				len -= a - s;
				s = a;
			}
		} else
			len = 0;
	}

	if (srch->backwards)
		pbkwd (p, p->byte - starting);

	return p;
}

/* Search system user interface */

/* Query for search string, search options, possible replacement string,
 * and execute first search */

char srchstr[] = "Search";	/* Context sensitive help identifier */
char replstr[] = "Replace";	/* Context sensitive help identifier */
char srchopt[] = "SearchOptions";

static int pfabort(W *w, void *obj)
{
	SRCH *srch = (SRCH *)obj;
	if (srch)
		rmsrch(srch);
	return -1;
}

static int pfsave(W *w, void *obj)
{
	SRCH *srch = (SRCH *)obj;
	if (srch) {
		if (globalsrch)
			rmsrch(globalsrch);
		globalsrch = srch;
		srch->rest = 0;
		srch->repeat = -1;
		srch->flg = 0;

		if (srch->markb || srch->markk) {
			/* We don't want isearch to clear an existing block, which it will do if
			   both srch->markb and srch->markk are null.  On the other hand, finishing
			   a find/replace should absolutely restore the old state, even if only one
			   of the two was set.  If we prm inside the if's below, then we get a half-
			   way state with a weird, unexpected block. */
			prm(markb);
			prm(markk);
		}
		if (srch->markb) {
			markb = srch->markb;
			markb->owner = &markb;
			markb->xcol = piscol(markb);
		}
		if (srch->markk) {
			markk = srch->markk;
			markk->owner = &markk;
			markk->xcol = piscol(markk);
		}
		srch->markb = NULL;
		srch->markk = NULL;

		updall();
	}
	return -1;
}

static int set_replace(W *w, char *s, void *obj, int *notify)
{
	SRCH *srch = (SRCH *)obj;
	BW *bw;
	WIND_BW(bw, w);

	if (sLEN(s) || !globalsrch || !pico)
		srch->replacement = s;
	else {
		/* Use previous string: this prevents replace with empty string */
		/* vsrm(s);
		srch->replacement = vsdup(globalsrch->replacement); */
		srch->replacement = s;
	}
	return dopfnext(bw, setmark(srch), notify);
}

/* Option characters */

const char *all_key = _("|all files|aA");
const char *list_key = _("|error list files|eE");
const char *replace_key = _("|search and replace|rR");
const char *backwards_key = _("|backwards|bB");
const char *ignore_key = _("|ignore case|iI");
const char *block_key = _("|restrict to highlighted block|kK");
const char *noignore_key = _("|don't ignore case|sS");
const char *wrap_key = _("|wrap|wW");
const char *nowrap_key = _("|don't wrap|nN");
const char *regex_key = _("|regex|xX");
const char *noregex_key = _("|no regex|yY");
const char *regex_debug_key = _("|regex_debug|v");

static int set_options(W *w, char *s, void *obj, int *notify)
{
	SRCH *srch = (SRCH *)obj;
	BW *bw;
	char buf[80];
	const char *t;
	WIND_BW(bw, w);
	srch->ignore = opt_icase;

	t = s;
	while (*t) {
		int c = fwrd_c(locale_map, &t, NULL);
		if (yncheck(all_key, c))
			srch->all = 1;
		else if (yncheck(list_key, c))
			srch->all = 2;
		else if (yncheck(replace_key, c))
			srch->replace = 1;
		else if (yncheck(backwards_key, c))
			srch->backwards = 1;
		else if (yncheck(ignore_key, c))
			srch->ignore = 1;
		else if (yncheck(noignore_key, c))
			srch->ignore = 0;
		else if (yncheck(wrap_key, c))
			srch->allow_wrap = 1;
		else if (yncheck(nowrap_key, c))
			srch->allow_wrap = 0;
		else if (yncheck(block_key, c))
			srch->block_restrict = 1;
		else if (yncheck(regex_key, c))
			srch->regex = 1;
		else if (yncheck(regex_debug_key, c))
			srch->debug = 1;
		else if (yncheck(noregex_key, c))
			srch->regex = 0;
		else if (c >= '0' && c <= '9') {
			if (srch->repeat == -1)
				srch->repeat = 0;
			srch->repeat = srch->repeat * 10 + c - '0';
		}
	}
	vsrm(s);
	if (srch->replace) {
		/* if (pico && globalsrch && globalsrch->replacement) {
			joe_snprintf_1(bf1,30,"%s",globalsrch->replacement);
			if (zlen(globalsrch->replacement)>29)
				zlcat(bf1, SIZEOF(bf1), "$");
			joe_snprintf_1(buf,SIZEOF(buf),joe_gettext(_("Replace with (%{help} for help) [%s]: ")),bf1);
		} else */
			joe_snprintf_0(buf, SIZEOF(buf), joe_gettext(_("Replace with (%{help} for help): ")));
		if (wmkpw(bw->parent, buf, &replhist, set_replace, replstr, pfabort, srch_cmplt, srch, notify, bw->b->o.charmap, 0))
			return 0;
		else
			return -1;
	} else
		return dopfnext(bw, setmark(srch), notify);
}

static int set_pattern(W *w, char *s, void *obj, int *notify)
{
	SRCH *srch = (SRCH *)obj;
	BW *bw;
	BW *pbw;
	const char *p;
	WIND_BW(bw, w);
	if (opt_icase)
		p = joe_gettext(_("case (S)ensitive (R)eplace (B)ackwards Bloc(K) (%{help} for help): "));
	else
		p = joe_gettext(_("(I)gnore (R)eplace (B)ackwards Bloc(K) (%{help} for help): "));

	if (sLEN(s) || !globalsrch || !pico) {
		setpat(srch, s);
	} else {
		vsrm(s);
		setpat(srch, vsdup(globalsrch->pattern));
	}
	if ((pbw = wmkpw(bw->parent, p, NULL, set_options, srchopt, pfabort, utypebw, srch, notify, locale_map, 0)) != NULL) {
		char buf[16];

		if (srch->ignore) {
			const char *t = joe_gettext(ignore_key);
			binsc(pbw->cursor, fwrd_c(locale_map, &t, NULL));
		}
		if (srch->replace) {
			const char *t = joe_gettext(replace_key);
			binsc(pbw->cursor, fwrd_c(locale_map, &t, NULL));
		}
		if (srch->backwards) {
			const char *t = joe_gettext(backwards_key);
			binsc(pbw->cursor, fwrd_c(locale_map, &t, NULL));
		}
		if (srch->repeat >= 0)
			joe_snprintf_1(buf, SIZEOF(buf), "%d", srch->repeat), binss(pbw->cursor, buf);
		pset(pbw->cursor, pbw->b->eof);
		pbw->cursor->xcol = piscol(pbw->cursor);
		srch->ignore = 0;
		srch->replace = 0;
		srch->backwards = 0;
		srch->repeat = -1;
		return 0;
	} else {
		rmsrch(srch);
		return -1;
	}
}

/* Unescape for text going to genfmt */

static void unesc_genfmt(char *d, char *s, ptrdiff_t len,ptrdiff_t max)
{
	while (max > 0 && len) {
		if (!*s) {
			*d++ = '\\';
			*d++ = '@';
			++s;
		} else {
			if (*s == '\\') {
				*d++ = '\\';
				--max;
			}
			*d++ = *s++;
		}
		--len;
		--max;
	}
	if (len)
		*d++ = '$';
	*d = 0;
}

int std_regex = 0; /* Standard regex format by default */

int dofirst(BW *bw, int back, int repl, char *hint)
{
	SRCH *srch;
	BW *pbw;
	char bf1[80];
	char buf[80];

	if (smode && globalsrch) {
		globalsrch->backwards = back;
		globalsrch->replace = repl;
		return pfnext(bw->parent, 0);
	}
	if (bw->parent->huh == srchstr) {
		off_t byte;

		p_goto_eol(bw->cursor);
		byte = bw->cursor->byte;
		p_goto_bol(bw->cursor);
		if (byte == bw->cursor->byte)
			prgetc(bw->cursor);
		return urtn(bw->parent, -1);
	}
	srch = mksrch(NULL, NULL, 0, back, -1, repl, 0, 0, std_regex);
	srch->addr = bw->cursor->byte;
	srch->wrap_p = pdup(bw->cursor, "dofirst");
	srch->wrap_p->owner = &srch->wrap_p;
	if (pico && globalsrch && globalsrch->pattern) {
		unesc_genfmt(bf1, sv(globalsrch->pattern), 30);
		joe_snprintf_1(buf,SIZEOF(buf),joe_gettext(_("Find (%{help} for help) [%s]: ")),bf1);
	} else
		joe_snprintf_0(buf, SIZEOF(buf), joe_gettext(_("Find (%{help} for help): ")));
	if ((pbw=wmkpw(bw->parent, buf, &findhist, set_pattern, srchstr, pfabort, srch_cmplt, srch, NULL, bw->b->o.charmap, 0))) {
		if (hint) {
			binss(pbw->cursor, hint);
			pset(pbw->cursor, pbw->b->eof);
			pbw->cursor->xcol = piscol(pbw->cursor);
		}
		return 0;
	} else {
		rmsrch(srch);
		return -1;
	}
}

int pffirst(W *w, int k)
{
	BW *bw;
	WIND_BW(bw, w);
	return dofirst(bw, 0, 0, NULL);
}

int prfirst(W *w, int k)
{
	BW *bw;
	WIND_BW(bw, w);
	return dofirst(bw, 1, 0, NULL);
}

int pqrepl(W *w, int k)
{
	BW *bw;
	WIND_BW(bw, w);
	return dofirst(bw, 0, 1, NULL);
}

/* Execute next search */

static int doreplace(BW *bw, SRCH *srch)
{
	P *from, *to;
	P *q;

	int x;
	B *pieces[NMATCHES];
	B *entire;

	if (!modify_logic(bw,bw->b))
		return -1;
	if (markk)
		markk->end = 1;
	if (srch->markk)
		srch->markk->end = 1;

	/* Copy matched strings before we delete */
	from = pdup(bw->cursor, "doreplace:from");
	to = pdup(bw->cursor, "doreplace:to");
	for (x = 0; x != NMATCHES; ++x) {
		Regmatch_t *m = &srch->pieces[x];
		if (m->rm_eo > m->rm_so) {
			pgoto(from, m->rm_so);
			pgoto(to, m->rm_eo);
			pieces[x] = bcpy(from, to);
		} else {
			pieces[x] = 0;
		}
	}
	if (srch->entire.rm_eo > srch->entire.rm_so) {
		pgoto(from, srch->entire.rm_so);
		pgoto(to, srch->entire.rm_eo);
		entire = bcpy(from, to);
	} else {
		entire = 0;
	}
	prm(from);
	prm(to);

	q = pdup(bw->cursor, "doreplace");
	if (srch->backwards) {
		q = pfwrd(q, srch->entire.rm_eo - srch->entire.rm_so);
		bdel(bw->cursor, q);
		prm(q);
	} else {
		q = pbkwd(q, (srch->entire.rm_eo - srch->entire.rm_so));
		bdel(q, bw->cursor);
		prm(q);
	}
	insert(srch, bw->cursor, sv(srch->replacement), &entire, pieces);

	/* Delete copies */
	for (x = 0; x != NMATCHES; ++x)
		if (pieces[x])
			brm(pieces[x]);
	if (entire)
		brm(entire);

	srch->addr = bw->cursor->byte;
	srch->last_repl = bw->cursor->byte;
	if (markk)
		markk->end = 0;
	if (srch->markk)
		srch->markk->end = 0;
	return 0;
}

static void visit(SRCH *srch, BW *bw, int myyn)
{
	SRCHREC *r = (SRCHREC *) alitem(&fsr, SIZEOF(SRCHREC));
	r->addr = bw->cursor->byte;
	r->yn = myyn;
	r->wrap_flag = srch->wrap_flag;
	r->last_repl = srch->last_repl;
	r->b = bw->b;
	enqueb(SRCHREC, link, &srch->recs, r);
}

static void goback(SRCH *srch, BW *bw)
{
	SRCHREC *r = srch->recs.link.prev;

	if (r != &srch->recs) {
		srch->current = r->b;
		if (r->yn) {
			uundo(bw->parent, 0);
		}
		if (r->b != bw->b) {
			W *w = bw->parent;
			get_buffer_in_window(bw, r->b);
			bw = (BW *)w->object;
		}
		if (bw->cursor->byte != r->addr)
			pgoto(bw->cursor, r->addr);
		srch->wrap_flag = r->wrap_flag;
		srch->last_repl = r->last_repl;
		demote(SRCHREC, link, &fsr, r);
	}
}

const char *rest_key = _("|rest of file|rR");
const char *backup_key = _("|backup|bB");

static int dopfrepl(W *w, int c, void *obj, int *notify)
{
	BW *bw;
	SRCH *srch = (SRCH *)obj;
	WIND_BW(bw, w);
	srch->addr = bw->cursor->byte;
	/* Backspace means no for jmacs */
	if (c == NO_CODE || c == 8 || c == 127 || yncheck(no_key, c))
		return dopfnext(bw, srch, notify);
	else if (c == YES_CODE || yncheck(yes_key, c) || c == ' ') {
		srch->recs.link.prev->yn = 1;
		if (doreplace(bw, srch)) {
			pfsave(bw->parent, srch);
			return -1;
		} else
			return dopfnext(bw, srch, notify);
	} else if (yncheck(rest_key, c) || c == '!') {
		if (doreplace(bw, srch))
			return -1;
		srch->rest = 1;
		return dopfnext(bw, srch, notify);
	} else if (/* c == 8 || c == 127 || */ yncheck(backup_key, c)) {
 		W *tw = bw->parent;
		goback(srch, bw);
		goback(srch, (BW *)tw->object);
		return dopfnext((BW *)tw->object, srch, notify);
	} else if (c != -1) {
		if (notify)
			*notify = 1;
		pfsave(bw->parent, srch);
		nungetc(c);
		return 0;
	}
	if (mkqwnsr(bw->parent, sz(joe_gettext(_("Replace (Y)es (N)o (R)est (B)ackup (%{abort} to abort)?"))), dopfrepl, pfsave, srch, notify))
		return 0;
	else
		return pfsave(bw->parent, srch);
}

/* Test if found text is within region
 * return 0 if it is,
 * -1 if we should keep searching
 * 1 if we're done
 */

static int restrict_to_block(BW *bw, SRCH *srch)
{
	if (!srch->valid || !srch->block_restrict)
		return 0;
	bw->cursor->xcol = piscol(bw->cursor);
	if (srch->backwards)
		if (!square) {
			if (bw->cursor->byte < srch->markb->byte)
				return 1;
			else if (bw->cursor->byte + (srch->entire.rm_eo - srch->entire.rm_so) > srch->markk->byte)
				return -1;
		} else {
			if (bw->cursor->line < srch->markb->line)
				return 1;
			else if (bw->cursor->line > srch->markk->line)
				return -1;
			else if (piscol(bw->cursor) + (srch->entire.rm_eo - srch->entire.rm_so) > srch->markk->xcol || piscol(bw->cursor) < srch->markb->xcol)
				return -1;
	} else if (!square) {
		if (bw->cursor->byte > srch->markk->byte)
			return 1;
		else if (bw->cursor->byte - (srch->entire.rm_eo - srch->entire.rm_so) < srch->markb->byte)
			return -1;
	} else {
		if (bw->cursor->line > srch->markk->line)
			return 1;
		if (bw->cursor->line < srch->markb->line)
			return -1;
		if (piscol(bw->cursor) > srch->markk->xcol || piscol(bw->cursor) - (srch->entire.rm_eo - srch->entire.rm_so) < srch->markb->xcol)
			return -1;
	}
	return 0;
}

/* Possible results:
 *   0) Search or search & replace is finished.
 *   1) Search string was not found.
 *   2) Search string was found.
 *   3) Abort due to infinite loop
 *   4) Abort due to compile error
 */

static int fnext(BW *bw, SRCH *srch)
{
	P *sta;

	if (!srch->first) {
		srch->first = bw->b;
		srch->current = bw->b;
	}

	next:
	if (srch->repeat != -1) {
		if (!srch->repeat)
			return 0;
		else
			--srch->repeat;
	}
	again:
	/* Clear compiled version of pattern if character map changed (perhaps because we switched buffer) */
	if (srch->comp && srch->comp->cmap != bw->b->o.charmap) {
		clrcomp(srch);
		/* Fail if character map of search prompt doesn't match map of buffer */
		msgnw(bw->parent, joe_gettext(_("Character set of buffer does not match character set of search string")));
		return 4;
	}
	/* Compile pattern if we don't already have it */
	if (!srch->comp) {
		srch->comp = joe_regcomp(bw->b->o.charmap, srch->pattern, sLEN(srch->pattern), srch->ignore, srch->regex, srch->debug);
		if (srch->comp->err) {
			msgnw(bw->parent, joe_gettext(srch->comp->err));
			return 4;
		}
	}
	if (srch->backwards)
		sta = searchb(bw, srch, bw->cursor);
	else
		sta = searchf(bw, srch, bw->cursor);
	if (!sta && srch->all) {
		B *b;
		if (srch->all == 2)
			b = beafter(srch->current);
		else {
			berror = 0;
			b = bafter(srch->current);
		}
		if (b && b != srch->first && !berror) {
			W *w = bw->parent;
			srch->current = b;
			/* this bumps reference count of b */
			get_buffer_in_window(bw, b);
			bw = (BW *)w->object;
			p_goto_bof(bw->cursor);
			goto again;
		} else if (berror) {
			msgnw(bw->parent, joe_gettext(msgs[-berror]));
		}
	}
	if (!sta) {
		srch->repeat = -1;
		return 1;
	}
	if (srch->rest || (srch->repeat != -1 && srch->replace)) {
		if (srch->valid)
			switch (restrict_to_block(bw, srch)) {
			case -1:
				goto again;
			case 1:
				if (srch->addr >= 0)
					pgoto(bw->cursor, srch->addr);
				return !srch->rest;
			}
		if (doreplace(bw, srch))
			return 0;
		goto next;
	} else if (srch->repeat != -1) {
		if (srch->valid)
			switch (restrict_to_block(bw, srch)) {
			case -1:
				goto again;
			case 1:
				if (srch->addr >= 0)
					pgoto(bw->cursor, srch->addr);
				return 1;
			}
		srch->addr = bw->cursor->byte;
		goto next;
	} else
		return 2;
}

int dopfnext(BW *bw, SRCH *srch, int *notify)
{
	W *w;
	int fnr;
	int orgmid = opt_mid;	/* Original mid status */
	int ret = 0;

	opt_mid = 1;		/* Screen recenters mode during search */
	if (csmode)
		smode = 2;	/* We have started a search mode */
	if (srch->replace)
		visit(srch, bw, 0);
again:	w = bw->parent;
	fnr = fnext(bw, srch);
	bw  = (BW *)w->object;
	switch (fnr) {
	case 0:
		break;
	case 1:
bye:		if (!srch->flg && !srch->rest) {
			if (srch->valid && srch->block_restrict)
				msgnw(bw->parent, joe_gettext(_("Not found (search restricted to marked block)")));
			else
				msgnw(bw->parent, joe_gettext(_("Not found")));
			ret = -1;
		}
		break;
	case 3:
		msgnw(bw->parent, joe_gettext(_("Infinite loop aborted: your search repeatedly matched same place")));
		ret = -1;
		break;
	case 4:
		ret = -1;
		break;
	case 2:
		if (srch->valid)
			switch (restrict_to_block(bw, srch)) {
			case -1:
				goto again;
			case 1:
				if (srch->addr >= 0)
					pgoto(bw->cursor, srch->addr);
				goto bye;
			}
		srch->addr = bw->cursor->byte;

		/* Make sure found text is fully on screen */
		if(srch->backwards) {
			bw->offset=0;
			pfwrd(bw->cursor,(srch->entire.rm_eo - srch->entire.rm_so));
			bw->cursor->xcol = piscol(bw->cursor);
			dofollows();
			pbkwd(bw->cursor,(srch->entire.rm_eo - srch->entire.rm_so));
		} else {
			bw->offset=0;
			pbkwd(bw->cursor,(srch->entire.rm_eo - srch->entire.rm_so));
			bw->cursor->xcol = piscol(bw->cursor);
			dofollows();
			pfwrd(bw->cursor,(srch->entire.rm_eo - srch->entire.rm_so));
		}

		if (srch->replace) {
			if (square)
				bw->cursor->xcol = piscol(bw->cursor);
			if (srch->backwards) {
				pdupown(bw->cursor, &markb, "dopfnext");
				markb->xcol = piscol(markb);
				pdupown(markb, &markk, "dopfnext");
				pfwrd(markk, (srch->entire.rm_eo - srch->entire.rm_so));
				markk->xcol = piscol(markk);
			} else {
				pdupown(bw->cursor, &markk, "dopfnext");
				markk->xcol = piscol(markk);
				pdupown(bw->cursor, &markb, "dopfnext");
				pbkwd(markb, (srch->entire.rm_eo - srch->entire.rm_so));
				markb->xcol = piscol(markb);
			}
			srch->flg = 1;
			if (dopfrepl(bw->parent, -1, srch, notify))
				ret = -1;
			notify = 0;
			srch = 0;
		}
		break;
	}
	bw->cursor->xcol = piscol(bw->cursor);
	dofollows();
	opt_mid = orgmid;
	if (notify)
		*notify = 1;
	if (srch)
		pfsave(bw->parent, srch);
	else
		updall();
	return ret;
}

int pfnext(W *w, int k)
{
	BW *bw;
	WIND_BW(bw, w);
	if (!globalsrch) {	/* Query for search string if there isn't any */
		return pffirst(bw->parent, 0);
	} else {
		SRCH *srch = globalsrch;

		globalsrch = 0;
		srch->addr = bw->cursor->byte;
		if (!srch->wrap_p || srch->wrap_p->b!=bw->b) {
			prm(srch->wrap_p);
			srch->wrap_p = pdup(bw->cursor, "pfnext");
			srch->wrap_p->owner = &srch->wrap_p;
			srch->wrap_flag = 0;
		}
		return dopfnext(bw, setmark(srch), NULL);
	}
}

void save_srch(FILE *f)
{
	if(globalsrch) {
		if(globalsrch->pattern) {
			fprintf(f,"	pattern ");
			emit_string(f,globalsrch->pattern,sLEN(globalsrch->pattern));
			fprintf(f,"\n");
		}
		if(globalsrch->replacement) {
			fprintf(f,"	replacement ");
			emit_string(f,globalsrch->replacement,sLEN(globalsrch->replacement));
			fprintf(f,"\n");
		}
		fprintf(f,"	backwards %d\n",globalsrch->backwards);
		fprintf(f,"	ignore %d\n",globalsrch->ignore);
		fprintf(f,"	replace %d\n",globalsrch->replace);
		fprintf(f,"	block_restrict %d\n",globalsrch->block_restrict);
		fprintf(f,"	regex %d\n",globalsrch->regex);
	}
	fprintf(f,"done\n");
}

void load_srch(FILE *f)
{
	char buf[1024];
	char bf[1024];
	char *pattern = 0;
	char *replacement = 0;
	int backwards = 0;
	int ignore = 0;
	int regex = 0;
	int replace = 0;
	int block_restrict = 0;
	while(fgets(buf,sizeof(buf),f) && zcmp(buf,"done\n")) {
		const char *p=buf;
		parse_ws(&p,'#');
		if(!parse_kw(&p,"pattern")) {
			ptrdiff_t len;
			parse_ws(&p,'#');
			bf[0] = 0;
			len = parse_string(&p,bf,SIZEOF(bf));
			if (len>0)
				pattern = vsncpy(NULL,0,bf,len);
		} else if(!parse_kw(&p,"replacement")) {
			ptrdiff_t len;
			parse_ws(&p,'#');
			bf[0] = 0;
			len = parse_string(&p,bf,SIZEOF(bf));
			if (len>0)
				replacement = vsncpy(NULL,0,bf,len);
		} else if(!parse_kw(&p,"backwards")) {
			parse_ws(&p,'#');
			parse_int(&p,&backwards);
		} else if(!parse_kw(&p,"ignore")) {
			parse_ws(&p,'#');
			parse_int(&p,&ignore);
		} else if(!parse_kw(&p,"regex")) {
			parse_ws(&p,'#');
			parse_int(&p,&ignore);
		} else if(!parse_kw(&p,"replace")) {
			parse_ws(&p,'#');
			parse_int(&p,&replace);
		} else if(!parse_kw(&p,"block_restrict")) {
			parse_ws(&p,'#');
			parse_int(&p,&block_restrict);
		}
	}
	globalsrch = mksrch(pattern,replacement,ignore,backwards,-1,replace,0,0,regex);
	globalsrch->block_restrict = block_restrict;
}
