/* find.c -- Searching and replacing
   Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>

   This file is part of Jade.

   Jade is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   Jade is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Jade; see the file COPYING.	If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "jade.h"
#include "jade_protos.h"
#include "regexp/regexp.h"

#include <string.h>
#include <ctype.h>
#include <stdlib.h>

_PR bool findnext(TX *, u_char *, POS *, bool);
_PR bool findprev(TX *, u_char *, POS *, bool);
_PR bool findstrnext(TX *, u_char *, POS *);
_PR bool findstrprev(TX *, u_char *, POS *);
_PR bool findcharnext(TX *, u_char, POS *);
_PR bool findcharprev(TX *, u_char, POS *);
_PR bool replaceit(TX *, u_char *, u_char *, POS *, bool);
_PR bool replaceitstr(TX *, u_char *, u_char *, POS *);
_PR bool mystrcmp(u_char *, u_char *);
_PR u_char *mystrrstrn(u_char *, u_char *, int);
_PR u_char *mystrrchrn(u_char *, u_char, int);
_PR void find_init(void);

/* Storage for remembering where the last match was.
   LAST_STRING is the string that was matched against, only it's address is
   used.
   LAST_START and LAST_END is copied straight out of the regexp struct
   LAST_LINE is either -1 meaning the string was not in a buffer, or the
   line number it came from.  */
static u_char *last_string;
static u_char *last_start[NSUBEXP];
static u_char *last_end[NSUBEXP];
static long last_line;

static VALUE sym_regexp_error;

static void
update_last_match(u_char *str, regexp *prog, long line)
{
    last_string = str;
    memcpy(last_start, (u_char **)prog->startp, sizeof(last_start));
    memcpy(last_end, (u_char **)prog->endp, sizeof(last_end));
    last_line = line;
}

_PR VALUE cmd_find_next_regexp(VALUE re, VALUE pos, VALUE tx, VALUE nocase_p);
DEFUN("find-next-regexp", cmd_find_next_regexp, subr_find_next_regexp, (VALUE re, VALUE pos, VALUE tx, VALUE nocase_p), V_Subr4, DOC_find_next_regexp) /*
::doc:find_next_regexp::
find-next-regexp REGEXP [POS] [BUFFER] [IGNORE-CASE-P]

Scans forwards from POS (or the cursor), in BUFFER, looking for a match
with REGEXP. Returns the position of the next match or nil.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    POS res;
    DECLARE1(re, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findnext(VTX(tx), VSTR(re), &res, !NILP(nocase_p)))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_find_prev_regexp(VALUE re, VALUE pos, VALUE tx, VALUE nocase_p);
DEFUN("find-prev-regexp", cmd_find_prev_regexp, subr_find_prev_regexp, (VALUE re, VALUE pos, VALUE tx, VALUE nocase_p), V_Subr4, DOC_find_prev_regexp) /*
::doc:find_prev_regexp::
find-prev-regexp REGEXP [POS] [BUFFER] [IGNORE-CASE-P]

Scans backwards from POS (or the cursor), in BUFFER, looking for a match
with REGEXP. Returns the position of the next match or nil.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    POS res;
    DECLARE1(re, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findprev(VTX(tx), VSTR(re), &res, !NILP(nocase_p)))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_find_next_string(VALUE str, VALUE pos, VALUE tx);
DEFUN("find-next-string", cmd_find_next_string, subr_find_next_string, (VALUE str, VALUE pos, VALUE tx), V_Subr3, DOC_find_next_string) /*
::doc:find_next_string::
find-next-string STRING [POS] [BUFFER]

Scans forwards from POS (or the cursor), in BUFFER, looking for a match
with STRING. Returns the position of the next match or nil.
::end:: */
{
    POS res;
    DECLARE1(str, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findstrnext(VTX(tx), VSTR(str), &res))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_find_prev_string(VALUE str, VALUE pos, VALUE tx);
DEFUN("find-prev-string", cmd_find_prev_string, subr_find_prev_string, (VALUE str, VALUE pos, VALUE tx), V_Subr3, DOC_find_prev_string) /*
::doc:find_prev_string::
find-prev-string STRING [POS] [BUFFER]

Scans backwards from POS (or the cursor), in BUFFER, looking for a match
with STRING. Returns the position of the next match or nil.
::end:: */
{
    POS res;
    DECLARE1(str, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findstrprev(VTX(tx), VSTR(str), &res))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_find_next_char(VALUE ch, VALUE pos, VALUE tx);
DEFUN("find-next-char", cmd_find_next_char, subr_find_next_char, (VALUE ch, VALUE pos, VALUE tx), V_Subr3, DOC_find_next_char) /*
::doc:find_next_char::
find-next-char CHAR [POS] [BUFFER]

Scans forwards from POS (or the cursor), in BUFFER, looking for a match
with CHAR. Returns the position of the next match or nil.
::end:: */
{
    POS res;
    DECLARE1(ch, CHARP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findcharnext(VTX(tx), VCHAR(ch), &res))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_find_prev_char(VALUE ch, VALUE pos, VALUE tx);
DEFUN("find-prev-char", cmd_find_prev_char, subr_find_prev_char, (VALUE ch, VALUE pos, VALUE tx), V_Subr3, DOC_find_prev_char) /*
::doc:find_prev_char::
find-prev-char CHAR [POS] [BUFFER]

Scans backwards from POS (or the cursor), in BUFFER, looking for a match
with CHAR. Returns the position of the next match or nil.
::end:: */
{
    POS res;
    DECLARE1(ch, CHARP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(findcharprev(VTX(tx), VCHAR(ch), &res))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_replace_regexp(VALUE re, VALUE tplt, VALUE pos, VALUE tx, VALUE nocase_p);
DEFUN("replace-regexp", cmd_replace_regexp, subr_replace_regexp, (VALUE re, VALUE tplt, VALUE pos, VALUE tx, VALUE nocase_p), V_Subr5, DOC_replace_regexp) /*
::doc:replace_regexp::
replace-regexp REGEXP TEMPLATE [POS] [BUFFER] [IGNORE-CASE-P]

If the text at POS or the cursor, matches REGEXP replace it with TEMPLATE,
this is a string that can have the following escape characters,
  \0, \&   whole string matched by REGEXP
  \N	   N'th parenthensized expression (1 <= N <= 9)

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    POS res;
    DECLARE1(re, STRINGP);
    DECLARE2(tplt, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(!read_only(VTX(tx)) && replaceit(VTX(tx), VSTR(re), VSTR(tplt),
				       &res, !NILP(nocase_p)))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_replace_string(VALUE orig, VALUE new, VALUE pos, VALUE tx);
DEFUN("replace-string", cmd_replace_string, subr_replace_string, (VALUE orig, VALUE new, VALUE pos, VALUE tx), V_Subr4, DOC_replace_string) /*
::doc:replace_string::
replace-string ORIGINAL NEW [POS] [BUFFER]

If the text at POS, or the cursor, matches ORIGINAL, replace it with the
string NEW.
::end:: */
{
    POS res;
    DECLARE1(orig, STRINGP);
    DECLARE2(new, STRINGP);
    if(POSP(pos))
	res = VPOS(pos);
    else
	res = curr_vw->vw_CursorPos;
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    check_pos(VTX(tx), &res);
    if(!read_only(VTX(tx)) && replaceitstr(VTX(tx), VSTR(orig), VSTR(new), &res))
	return(make_lpos(&res));
    return(sym_nil);
}

_PR VALUE cmd_regexp_expand(VALUE re, VALUE match, VALUE tplt, VALUE nocase_p);
DEFUN("regexp-expand", cmd_regexp_expand, subr_regexp_expand, (VALUE re, VALUE match, VALUE tplt, VALUE nocase_p), V_Subr4, DOC_regexp_expand) /*
::doc:regexp_expand::
regexp-expand REGEXP MATCHSTR TEMPLATE [IGNORE-CASE-P]

If REGEXP matches MATCHSTR then return the string made by expanding the
string TEMPLATE in a similar way to in the function `replace-regexp'.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    regexp *prog;
    DECLARE1(re, STRINGP);
    DECLARE2(match, STRINGP);
    DECLARE3(tplt, STRINGP);
    prog = regcomp(VSTR(re));
    if(prog)
    {
	VALUE res = sym_nil;
	if(regexec2(prog, VSTR(match), NILP(nocase_p) ? 0 : REG_NOCASE))
	{
	    long replen = regsublen(prog, VSTR(tplt));
	    if(replen && (res = make_string(replen)))
		regsub(prog, VSTR(tplt), VSTR(res));
	    else if(!replen)
		res = string_dup("");
	    update_last_match(VSTR(match), prog, -1);
	}
	free(prog);
	return(res);
    }
    return(NULL);
}

_PR VALUE cmd_regexp_match(VALUE re, VALUE str, VALUE nocase_p);
DEFUN("regexp-match", cmd_regexp_match, subr_regexp_match, (VALUE re, VALUE str, VALUE nocase_p), V_Subr3, DOC_regexp_match) /*
::doc:regexp_match::
regexp-match REGEXP STRING [IGNORE-CASE-P]

Return t if REGEXP matches STRING.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    regexp *prog;
    DECLARE1(re, STRINGP);
    DECLARE2(str, STRINGP);
    prog = regcomp(VSTR(re));
    if(prog)
    {
	VALUE res;
	if(regexec2(prog, VSTR(str), NILP(nocase_p) ? 0 : REG_NOCASE))
	{
	    update_last_match(VSTR(str), prog, -1);
	    res = sym_t;
	}
	else
	    res = sym_nil;
	free(prog);
	return(res);
    }
    return(NULL);
}

_PR VALUE cmd_regexp_expand_line(VALUE re, VALUE tplt, VALUE pos, VALUE tx, VALUE nocase_p);
DEFUN("regexp-expand-line", cmd_regexp_expand_line, subr_regexp_expand_line, (VALUE re, VALUE tplt, VALUE pos, VALUE tx, VALUE nocase_p), V_Subr5, DOC_regexp_expand_line) /*
::doc:regexp_expand_line::
regexp-expand REGEXP TEMPLATE [POS] [BUFFER] [IGNORE-CASE-P]

If REGEXP matches the line at POS in BUFFER then return the string made
by expanding the string TEMPLATE in a similar way to in the function
`replace-regexp' and the variables `find-last-start-pos' and
`find-last-end-pos' are set to the start and end of the match.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    POS linepos;
    regexp *prog;
    DECLARE1(re, STRINGP);
    DECLARE2(tplt, STRINGP);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(pos))
	linepos = VPOS(pos);
    else
	linepos = *get_tx_cursor(VTX(tx));
    if(check_line(VTX(tx), &linepos) && (prog = regcomp(VSTR(re))))
    {
	VALUE res = sym_nil;
	u_char *line = VTX(tx)->tx_Lines[linepos.pos_Line].ln_Line;
	if(regexec2(prog, line, NILP(nocase_p) ? 0 : REG_NOCASE))
	{
	    long replen;
	    update_last_match(line, prog, linepos.pos_Line);
	    replen = regsublen(prog, VSTR(tplt));
	    if(replen && (res = make_string(replen)))
		regsub(prog, VSTR(tplt), VSTR(res));
	    else if(!replen)
		res = string_dup("");
	}
	free(prog);
	return(res);
    }
    return(NULL);
}

_PR VALUE cmd_regexp_match_line(VALUE re, VALUE pos, VALUE tx, VALUE nocase_p);
DEFUN("regexp-match-line", cmd_regexp_match_line, subr_regexp_match_line, (VALUE re, VALUE pos, VALUE tx, VALUE nocase_p), V_Subr4, DOC_regexp_match_line) /*
::doc:regexp_match_line::
regexp-match-line REGEXP [LINE-POS] [BUFFER] [IGNORE-CASE-P]

Attempts to match the regular-expression REGEXP to the line pointed to by
LINE-POS and BUFFER. If the match succeeds t is returned and the variables
`find-last-start-pos' and `find-last-end-pos' are set to the start and end
of the match.

When IGNORE-CASE-P is non-nil the case of matched strings are ignored. Note
that character classes are still case-significant.
::end:: */
{
    POS linepos;
    regexp *prog;
    DECLARE1(re, STRINGP);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(pos))
	linepos = VPOS(pos);
    else
	linepos = *get_tx_cursor(VTX(tx));
    if(check_line(VTX(tx), &linepos) && (prog = regcomp(VSTR(re))))
    {
	VALUE res;
	u_char *line = VTX(tx)->tx_Lines[linepos.pos_Line].ln_Line;
	if(regexec2(prog, line, NILP(nocase_p) ? 0 : REG_NOCASE))
	{
	    update_last_match(line, prog, linepos.pos_Line);
	    res = sym_t;
	}
	else
	    res = sym_nil;
	free(prog);
	return(res);
    }
    return(NULL);
}

_PR VALUE cmd_looking_at(VALUE re, VALUE vpos, VALUE tx, VALUE nocase_p);
DEFUN("looking-at", cmd_looking_at, subr_looking_at, (VALUE re, VALUE vpos, VALUE tx, VALUE nocase_p), V_Subr4, DOC_looking_at) /*
::doc:looking_at::
looking-at REGEXP [POS] [BUFFER] [IGNORE-CASE-P]

Returns t if REGEXP matches the text at POS. Only the text from POS to the
end of the line is matched against.
::end:: */
{
    POS pos;
    regexp *prog;
    DECLARE1(re, STRINGP);
    if(!BUFFERP(tx))
	tx = VAL(curr_vw->vw_Tx);
    if(POSP(vpos))
	pos = VPOS(vpos);
    else
	pos = *get_tx_cursor(VTX(tx));
    if(check_line(VTX(tx), &pos) && (prog = regcomp(VSTR(re))))
    {
	VALUE res;
	u_char *line = VTX(tx)->tx_Lines[pos.pos_Line].ln_Line + pos.pos_Col;
	if(regexec2(prog, line, ((NILP(nocase_p) ? 0 : REG_NOCASE)
				 | ((pos.pos_Col == 0) ? 0 : REG_NOTBOL)))
	   && (prog->startp[0] == (char *)line))
	{
	    update_last_match(VTX(tx)->tx_Lines[pos.pos_Line].ln_Line,
			      prog, pos.pos_Line);
	    res = sym_t;
	}
	else
	    res = sym_nil;
	free(prog);
	return(res);
    }
    return(NULL);
}

_PR VALUE cmd_match_start(VALUE exp);
DEFUN("match-start", cmd_match_start, subr_match_start, (VALUE exp), V_Subr1, DOC_match_start) /*
::doc:match_start::
match-start [EXPRESSION-INDEX]

Return the position which the EXPRESSION-INDEX'th parenthesised expression
started at in the last successful regexp match. If EXPRESSION-INDEX is
nil or 0 the start of the whole match is returned instead.
The returned value will either be a position if the last match was in a
buffer, or an integer if the last match was in a string (i.e. regexp-match).
::end:: */
{
    long i;
    if(NUMBERP(exp))
    {
	i = VNUM(exp);
	if((i >= NSUBEXP) || (i < 0))
	    return(signal_arg_error(exp, 1));
    }
    else
	i = 0;
    if(last_start[i] == NULL)
	return(sym_nil);
    i = last_start[i] - last_string;
    if(last_line == -1)
	return(make_number(i));
    else
	return(make_lpos2(i, last_line));
}
	
_PR VALUE cmd_match_end(VALUE exp);
DEFUN("match-end", cmd_match_end, subr_match_end, (VALUE exp), V_Subr1, DOC_match_end) /*
::doc:match_end::
match-end [EXPRESSION-INDEX]

Return the position which the EXPRESSION-INDEX'th parenthesised expression
ended at in the last successful regexp match. If EXPRESSION-INDEX is
nil or 0 the end of the whole match is returned instead.
The returned value will either be a position if the last match was in a
buffer, or an integer if the last match was in a string (i.e. regexp-match).
::end:: */
{
    long i;
    if(NUMBERP(exp))
    {
	i = VNUM(exp);
	if((i >= NSUBEXP) || (i < 0))
	    return(signal_arg_error(exp, 1));
    }
    else
	i = 0;
    if(last_end[i] == NULL)
	return(sym_nil);
    i = last_end[i] - last_string;
    if(last_line == -1)
	return(make_number(i));
    else
	return(make_lpos2(i, last_line));
}

_PR VALUE cmd_regexp_quote(VALUE str);
DEFUN("regexp-quote", cmd_regexp_quote, subr_regexp_quote, (VALUE str), V_Subr1, DOC_regexp_quote) /*
::doc:regexp_quote::
regexp-quote STRING

Returns a new version of STRING, any characters which the regexp routines
treat specially (asterisks, square brackets, etc...) is quoted by the escape
character `\'. If the STRING does not contain any regexp meta-characters
it is returned as-is (un-copied).
::end:: */
{
    u_char *buf, *s;
    int buflen = 128, slen, i = 0;
    bool quoted = FALSE;
    VALUE res = NULL;
    DECLARE1(str, STRINGP);
    s = VSTR(str);
    slen = STRING_LEN(str);
    buf = str_alloc(buflen);
    if(!buf)
	goto error;
    while(slen-- > 0)
    {
	u_char c;
	/* Ensure string is long enough, this saves doing this twice. */
	if(i + 2 >= buflen)
	{
	    int newlen = buflen * 2;
	    u_char *newbuf = str_alloc(newlen);
	    if(!newbuf)
		goto error;
	    memcpy(newbuf, buf, i);
	    str_free(buf);
	    buf = newbuf;
	    buflen = newlen;
	}
	switch(c = *s++)
	{
	case '*':
	case '+':
	case '?':
	case '.':
	case '[':
	case ']':
	case '(':
	case ')':
	case '|':
	case '^':
	case '$':
	case '\\':	 /* do I want to do this? */
	    /* quote this character */
	    buf[i++] = '\\';
	    buf[i++] = c;
	    quoted = TRUE;
	    break;
	default:
	    buf[i++] = c;
	    break;
	}
    }
    if(!quoted)
	res = str;
    else
	res = string_dupn(buf, i);
error:
    if(buf)
	str_free(buf);
    return(res);
}

bool
findnext(TX *tx, u_char * re, POS *pos, bool nocase)
{
    bool rc = FALSE;
    regexp *prog;
    prog = regcomp(re);
    if(prog)
    {
	LINE *line = tx->tx_Lines + pos->pos_Line;
	long index;
	if(pos->pos_Col >= line->ln_Strlen)
	    index = line->ln_Strlen - 1;
	else
	    index = pos->pos_Col;
	while(!rc && (tx->tx_NumLines > pos->pos_Line))
	{
	    if(regexec2(prog, line->ln_Line + index, (index ? REG_NOTBOL : 0) | (nocase ? REG_NOCASE : 0)))
		rc = TRUE;
	    else
	    {
		pos->pos_Line++;
		line++;
		index = 0;
	    }
	}
	if(rc)
	{
	    update_last_match(line->ln_Line, prog, pos->pos_Line);
	    pos->pos_Col = prog->startp[0] - ((char *)line->ln_Line);
	}
	free(prog);
    }
    return(rc);
}

bool
findprev(TX *tx, u_char *re, POS *pos, bool nocase)
{
    bool rc = FALSE;
    regexp *prog = regcomp(re);
    if(prog)
    {
	LINE *line = tx->tx_Lines + pos->pos_Line;
	long index;
	u_char *found = NULL;
	regexp last_match;		/* holds last match positions */
	if(pos->pos_Col >= line->ln_Strlen)
	    pos->pos_Col = line->ln_Strlen - 1;
	/*
	 * This makes the forward searching regexp matcher go backwards.
	 * we simply find as many matches as we can on a line, then return
	 * the last (taking the "start" position into account)
	 */
	while(!rc && (pos->pos_Line >= 0))
	{
	    index = 0;
	    while(regexec2(prog, line->ln_Line + index,
			   (index ? REG_NOTBOL : 0) | (nocase ? REG_NOCASE : 0)))
	    {
		long oindex = index;
		if(((prog->startp)[0] - (char *)line->ln_Line) > pos->pos_Col)
		    break;
		found = (prog->startp)[0];
		memcpy(&last_match, prog, sizeof(last_match));
		rc = TRUE;
		index = (prog->endp)[0] - (char *)line->ln_Line;
		if(oindex >= index)
		{
		    if(index >= line->ln_Strlen)
			break;
		    else
			index = oindex + 1;
		}
	    }
	    if(!found)
	    {
		line--;
		pos->pos_Line--;
		pos->pos_Col = line->ln_Strlen - 1;
		index = 0;
	    }
	}
	if(rc)
	{
	    update_last_match(line->ln_Line, &last_match, pos->pos_Line);
	    pos->pos_Col = found - line->ln_Line;
	}
	free(prog);
    }
    return(rc);
}

bool
findstrnext(TX *tx, u_char *str, POS *pos)
{
    LINE *line = tx->tx_Lines + pos->pos_Line;
    if(pos->pos_Col >= line->ln_Strlen)
    {
	pos->pos_Col = 0;
	pos->pos_Line++;
	line++;
    }
    while(tx->tx_NumLines > pos->pos_Line)
    {
	u_char *match = strstr(line->ln_Line + pos->pos_Col, str);
	if(match)
	{
	    pos->pos_Col = match - line->ln_Line;
	    return(TRUE);
	}
	pos->pos_Line++;
	pos->pos_Col = 0;
	line++;
    }
    return(FALSE);
}

bool
findstrprev(TX *tx, u_char *str, POS *pos)
{
    LINE *line = tx->tx_Lines + pos->pos_Line;
    if(pos->pos_Col >= line->ln_Strlen)
	pos->pos_Col = line->ln_Strlen - 1;
    while(pos->pos_Line >= 0)
    {
	u_char *match = mystrrstrn(line->ln_Line, str, pos->pos_Col + 1);
	if(match)
	{
	    pos->pos_Col = match - line->ln_Line;
	    return(TRUE);
	}
	line--;
	pos->pos_Line--;
	pos->pos_Col = line->ln_Strlen - 1;
    }
    return(FALSE);
}

bool
findcharnext(TX *tx, u_char c, POS *pos)
{
    LINE *line = tx->tx_Lines + pos->pos_Line;
    if(pos->pos_Col >= line->ln_Strlen)
    {
	pos->pos_Col = 0;
	pos->pos_Line++;
	line++;
    }
    while(tx->tx_NumLines > pos->pos_Line)
    {
	u_char *match = strchr(line->ln_Line + pos->pos_Col, c);
	if(match)
	{
	    pos->pos_Col = match - line->ln_Line;
	    return(TRUE);
	}
	pos->pos_Line++;
	pos->pos_Col = 0;
	line++;
    }
    return(FALSE);
}

bool
findcharprev(TX *tx, u_char c, POS *pos)
{
    LINE *line = tx->tx_Lines + pos->pos_Line;
    if(pos->pos_Col >= line->ln_Strlen)
	pos->pos_Col = line->ln_Strlen - 1;
    while(pos->pos_Line >= 0)
    {
	u_char *match = mystrrchrn(line->ln_Line, c, pos->pos_Col + 1);
	if(match)
	{
	    pos->pos_Col = match - line->ln_Line;
	    return(TRUE);
	}
	line--;
	pos->pos_Line--;
	pos->pos_Col = line->ln_Strlen - 1;;
    }
    return(FALSE);
}

bool
replaceit(TX *tx, u_char *re, u_char *str, POS *pos, bool nocase)
{
    bool rc = FALSE;
    regexp *prog;
    if((prog = regcomp(re)) && pad_pos(tx, pos))
    {
	u_char *line = tx->tx_Lines[pos->pos_Line].ln_Line;
	u_char *posstr = line + pos->pos_Col;
	if(((*re != '^') || (pos->pos_Col == 0))
	   && regexec2(prog, posstr, nocase ? REG_NOCASE : 0)
	   && (pos->pos_Col == (prog->startp[0] - ((char *)line))))
	{
	    long replen = regsublen(prog, str);
	    u_char *repstr;
	    if(replen && (repstr = str_alloc(replen)))
	    {
		POS end;
		regsub(prog, str, repstr);
		end.pos_Line = pos->pos_Line;
		end.pos_Col = pos->pos_Col + (prog->endp[0] - prog->startp[0]);
		undo_record_deletion(tx, pos, &end);
		delete_chars(tx, pos, prog->endp[0] - prog->startp[0]);
		flag_deletion(tx, pos, &end);
		end.pos_Col = pos->pos_Col + (replen - 1);
		undo_record_insertion(tx, pos, &end);
		flag_insertion(tx, pos, &end);
		rc = insert_str(tx, repstr, pos);
		str_free(repstr);
	    }
	    else if(replen)
		mem_error();
	}
	else
	    cmd_signal(sym_error, LIST_1(MKSTR("Regexp doesn't match replace position")));
	free(prog);
    }
    return(rc);
}

bool
replaceitstr(TX *tx, u_char *orig, u_char *new, POS *pos)
{
    bool rc = FALSE;
    u_char *line;
    line = tx->tx_Lines[pos->pos_Line].ln_Line + pos->pos_Col;
    if(mystrcmp(orig, line))
    {
	POS end;
	end.pos_Line = pos->pos_Line;
	end.pos_Col = pos->pos_Col + strlen(orig);
	undo_record_deletion(tx, pos, &end);
	delete_chars(tx, pos, strlen(orig));
	flag_deletion(tx, pos, &end);
	end.pos_Col = pos->pos_Col + strlen(new);
	undo_record_insertion(tx, pos, &end);
	flag_insertion(tx, pos, &end);
	rc = insert_str(tx, new, pos);
    }
    else
	cmd_signal(sym_error, LIST_1(MKSTR("String doesn't match replace position")));
    return(rc);
}

void
regerror(char *err)
{
#if 0
    message(err);
#else
    cmd_signal(sym_regexp_error,
	       list_2(MKSTR("Regular-expression"), string_dup(err)));
#endif
}

/*
 * These functions return TRUE if the strings match, if str1 ends before
 * str2 they are still considered to be matching.
 */
bool
mystrcmp(u_char *str1, u_char *str2)
{
    while(*str2 && *str1)
    {
	if(*str2++ != *str1++)
	    return(FALSE);
    }
    if(*str2 || (*str2 == *str1))
	return(TRUE);
    return(FALSE);
}

/*
 * find last occurrence of str2 in str1
 */
u_char *
mystrrstrn(u_char *str1, u_char *str2, int str1Len)
{
    int str2len = strlen(str2);
    register int i;
    u_char c = str2[str2len - 1];
    for(i = str1Len - 1; i >= str2len; i--)
    {
	if(str1[i] == c)
	{
	    u_char *tmp = (str1 + i) - (str2len - 1);
	    if(mystrcmp(str2, tmp))
		return(tmp);
	}
    }
    return(NULL);
}

u_char *
mystrrchrn(u_char *str, u_char c, int strLen)
{
    register u_char *s = str + strLen;
    while(s != str)
    {
	if(*(--s) == c)
	    return(s);
    }
    return(NULL);
}

void
find_init(void)
{
    INTERN(sym_regexp_error, "regexp-error");
    cmd_put(sym_regexp_error, sym_error_message, MKSTR("Regexp error"));
    ADD_SUBR(subr_find_next_regexp);
    ADD_SUBR(subr_find_prev_regexp);
    ADD_SUBR(subr_find_next_string);
    ADD_SUBR(subr_find_prev_string);
    ADD_SUBR(subr_find_next_char);
    ADD_SUBR(subr_find_prev_char);
    ADD_SUBR(subr_replace_string);
    ADD_SUBR(subr_replace_regexp);
    ADD_SUBR(subr_regexp_expand);
    ADD_SUBR(subr_regexp_match);
    ADD_SUBR(subr_regexp_expand_line);
    ADD_SUBR(subr_regexp_match_line);
    ADD_SUBR(subr_looking_at);
    ADD_SUBR(subr_match_start);
    ADD_SUBR(subr_match_end);
    ADD_SUBR(subr_regexp_quote);
}
