
/*
 * FIND.C
 * (c) 1993 J.Harper
 */

#include "jed.h"
#include "jed_protos.h"

Prototype   VALUE * cmd_find		(LONG, VALUE *);
Prototype   VALUE * cmd_replace		(LONG, VALUE *);
Prototype   BOOL    findcol0		(STRPTR);

Local UBYTE SearchBuff[100];
Local UBYTE ReplaceBuff[100];
Local BOOL  WildSearch;
Local BOOL  CaseSearch;

#ifndef NOREGEXP
Local BOOL  RESearch;
Local BOOL  findREprev(VOID);
Local BOOL  replaceitRE(VOID);
Local BOOL  findREnext(VOID);
#endif

Local BOOL  findnext(VOID);
Local BOOL  findprev(VOID);
Local BOOL  replaceit(VOID);
Local BOOL  mystrcmp(STRPTR, STRPTR);
Local BOOL  mystricmp(STRPTR, STRPTR);

/*
 * (find `s' searchstring)
 * (find `w' bool)
 * (find `c' bool)
 * (find `n')
 * (find `p')
 * (find `r' bool)
 */
VALUE *
cmd_find(LONG argc, VALUE *argv)
{
    if(TPLATE2(VTF_STRING, VTF_ANY))
    {
	switch(*(ARG1.val_Value.String))
	{
	    case 's':
		if(ARG2.val_Type == VTF_STRING)
		{
		    strncpy(SearchBuff, ARG2.val_Value.String, 99);
		    setnumres(TRUE);
		}
		else
		    settitle("syntax error: argument 2 should be a string");
		break;
	    case 'w':
		WildSearch = ARG2.val_Value.Number;
		setnumres(TRUE);
		break;
	    case 'c':
		CaseSearch = ARG2.val_Value.Number;
		setnumres(TRUE);
		break;
	    case 'n':
#ifndef NOREGEXP
		setnumres(RESearch ? findREnext() : findnext());
#else
		setnumres(findnext());
#endif
		resyncxy();
		break;
	    case 'p':
#ifndef NOREGEXP
		setnumres(RESearch ? findREprev() : findprev());
#else
		setnumres(findprev());
#endif
		resyncxy();
		break;
#ifndef NOREGEXP
	    case 'r':
		RESearch = ARG2.val_Value.Number;
		setnumres(TRUE);
		break;
#endif
	    default:
		settitlefmt("error: incorrect find argument %s", (LONG)ARG1.val_Value.String);
		break;
	}
    }
    return(&RES);
}

/*
 * (replace `s' repstring)
 * (replace `r')
 */
VALUE *
cmd_replace(LONG argc, VALUE *argv)
{
    if(TPLATE2(VTF_STRING, VTF_ANY))
    {
	switch(*(ARG1.val_Value.String))
	{
	    case 's':
		if(ARG2.val_Type == VTF_STRING)
		{
		    strncpy(ReplaceBuff, ARG2.val_Value.String, 99);
		    setnumres(TRUE);
		}
		else
		    settitle("syntax error: argument 2 should be a string");
		break;
	    case 'r':
		if(padcursor()) /* primarily to update undo-buffer */
		{
#ifndef NOREGEXP
		    setnumres(RESearch ? replaceitRE() : replaceit());
#else
		    setnumres(replaceit());
#endif
		    resyncxy();
		}
		else
		    setnumres(FALSE);
		break;
	}
    }
    return(&RES);
}

Local BOOL
findnext(VOID)
{
    UBYTE parsed[202];
    STRPTR find;
    VW *vw = CurrVW;
    LONG linenum = vw->vw_CursorPos.pos_Line;
    LINE *line = vw->vw_Tx->tx_Lines + linenum;
    LONG i = vw->vw_CursorPos.pos_Col + 1;
    LONG (*cmpfunc)(STRPTR, STRPTR);

    if(WildSearch)
    {
	LONG prc;
	if(CaseSearch)
	{
	    prc = ParsePattern(SearchBuff, parsed, 202);
	    cmpfunc = MatchPattern;
	}
	else
	{
	    prc = ParsePatternNoCase(SearchBuff, parsed, 202);
	    cmpfunc = MatchPatternNoCase;
	}
	if(prc < 0)
	{
	    settitle("error: can't parse pattern for wildcard search");
	    return(FALSE);
	}
	find = parsed;
    }
    else
    {
	find = SearchBuff;
	cmpfunc = CaseSearch ? mystrcmp : mystricmp;
    }
    while(linenum < vw->vw_Tx->tx_NumLines)
    {
	while(i < line->ln_Strlen)
	{
	    if(cmpfunc(find, line->ln_Line + i))
	    {
		setautomark();
		vw->vw_CursorPos.pos_Col = i;
		vw->vw_CursorPos.pos_Line = linenum;
		return(TRUE);
	    }
	    i++;
	}
	line++;
	linenum++;
	i = 0;
    }
    settitlefmt("can't find %s", (LONG)SearchBuff);
    return(FALSE);
}

Local BOOL
findprev(VOID)
{
    UBYTE parsed[202];
    STRPTR find;
    VW *vw = CurrVW;
    LONG linenum = vw->vw_CursorPos.pos_Line;
    LINE *line = vw->vw_Tx->tx_Lines + linenum;
    LONG i = vw->vw_CursorPos.pos_Col - 1;
    LONG (*cmpfunc)(STRPTR, STRPTR);

    if(WildSearch)
    {
	LONG prc;
	if(CaseSearch)
	{
	    prc = ParsePattern(SearchBuff, parsed, 202);
	    cmpfunc = MatchPattern;
	}
	else
	{
	    prc = ParsePatternNoCase(SearchBuff, parsed, 202);
	    cmpfunc = MatchPatternNoCase;
	}
	if(prc < 0)
	{
	    settitle("error: can't parse pattern for wildcard search");
	    return(FALSE);
	}
	find = parsed;
    }
    else
    {
	find = SearchBuff;
	cmpfunc = CaseSearch ? mystrcmp : mystricmp;
    }
    while(linenum >= 0)
    {
	while(i >= 0)
	{
	    if(cmpfunc(find, line->ln_Line + i))
	    {
		setautomark();
		vw->vw_CursorPos.pos_Col = i;
		vw->vw_CursorPos.pos_Line = linenum;
		return(TRUE);
	    }
	    i--;
	}
	line--;
	linenum--;
	i = line->ln_Strlen - 2;
    }
    settitlefmt("can't find %s", (LONG)SearchBuff);
    return(FALSE);
}

#ifndef NOREGEXP
Local BOOL
findREnext(VOID)
{
    VW *vw = CurrVW;
    BOOL rc = FALSE;
    if(vw->vw_LastRE.prog)
	free(vw->vw_LastRE.prog);
    if(vw->vw_LastRE.prog = regcomp(SearchBuff))
    {
	LONG linenum = vw->vw_CursorPos.pos_Line;
	LINE *line = vw->vw_Tx->tx_Lines + linenum;
	STRPTR tstr = line->ln_Line;
	if(vw->vw_CursorPos.pos_Col >= line->ln_Strlen)
	    tstr += line->ln_Strlen;
	else
	    tstr += vw->vw_CursorPos.pos_Col + 1;
	if(*SearchBuff == '^')
	{
	    line++;
	    linenum++;
	    tstr = line->ln_Line;
	}
	while(!rc && (vw->vw_Tx->tx_NumLines > linenum))
	{
	    if(regexec(vw->vw_LastRE.prog, tstr))
		rc = TRUE;
	    else
	    {
		linenum++;
		line++;
		tstr = line->ln_Line;
	    }
	}
	if(rc)
	{
	    vw->vw_CursorPos.pos_Line = linenum;
	    vw->vw_CursorPos.pos_Col = vw->vw_LastRE.prog->startp[0] - line->ln_Line;
	    vw->vw_LastRE.matchpos = vw->vw_CursorPos;
	    vw->vw_LastRE.changes = vw->vw_Tx->tx_Changes;
	}
	else
	{
	    free(vw->vw_LastRE.prog);
	    vw->vw_LastRE.prog = NULL;
	    settitle("can't find match");
	}
    }
    return(rc);
}

Local BOOL
findREprev(VOID)
{
    VW *vw = CurrVW;
    BOOL rc = FALSE;
    if(vw->vw_LastRE.prog)
	free(vw->vw_LastRE.prog);
    if(vw->vw_LastRE.prog = regcomp(SearchBuff))
    {
	LONG linenum = vw->vw_CursorPos.pos_Line;
	LINE *line = vw->vw_Tx->tx_Lines + linenum;
	STRPTR tstr = line->ln_Line;
	if(vw->vw_CursorPos.pos_Col >= line->ln_Strlen)
	    tstr += line->ln_Strlen;
	else
	    tstr += vw->vw_CursorPos.pos_Col + 1;
	if(*SearchBuff == '^')
	{
	    line--;
	    linenum--;
	    tstr = line->ln_Line;
	}
	while(!rc && (linenum > 0))
	{
	    if(regexec(vw->vw_LastRE.prog, tstr))
		rc = TRUE;
	    else
	    {
		linenum--;
		line--;
		tstr = line->ln_Line;
	    }
	}
	if(rc)
	{
	    vw->vw_CursorPos.pos_Line = linenum;
	    vw->vw_CursorPos.pos_Col = vw->vw_LastRE.prog->startp[0] - line->ln_Line;
	    vw->vw_LastRE.matchpos = vw->vw_CursorPos;
	    vw->vw_LastRE.changes = vw->vw_Tx->tx_Changes;
	}
	else
	{
	    free(vw->vw_LastRE.prog);
	    vw->vw_LastRE.prog = NULL;
	    settitle("can't find match");
	}
    }
    return(rc);
}
#endif /* NOREGEXP */

BOOL
findcol0(STRPTR str)
{
    VW *vw = CurrVW;
    TX *tx = vw->vw_Tx;
    LINE *line = tx->tx_Lines;
    LONG linenum = 0;

    while(linenum < tx->tx_NumLines)
    {
	if(mystrcmp(str, line->ln_Line))
	{
	    setautomark();
	    vw->vw_CursorPos.pos_Col = 0;
	    vw->vw_CursorPos.pos_Line = linenum;
	    vw->vw_StartLine = linenum;
	    vw->vw_RefreshType |= RFF_ALL;
	    return(TRUE);
	}
	linenum++;
	line++;
    }
    return(FALSE);
}

Local BOOL
replaceit(VOID)
{
    VW *vw = CurrVW;
    LINE *line = vw->vw_Tx->tx_Lines + vw->vw_CursorPos.pos_Line;
    WORD x = vw->vw_CursorPos.pos_Col;
    BOOL rc = FALSE;
    if((x < line->ln_Strlen) && (mystricmp(SearchBuff, line->ln_Line + x)))
    {
	WORD serlen = strlen(SearchBuff);
	setrefresh(RFF_LINEFROM, x, vw->vw_CursorPos.pos_Line);
	deletechars(strlen(SearchBuff));
	rc = insertstr(ReplaceBuff);
	vw->vw_Tx->tx_Changes++;
    }
    else
	settitle("string under cursor doesn't match string in search buffer");
    return(rc);
}

#ifndef NOREGEXP
Local BOOL
replaceitRE(VOID)
{
    VW *vw = CurrVW;
    BOOL rc = FALSE;
    if(vw->vw_LastRE.prog)
    {
	if(vw->vw_LastRE.changes == vw->vw_Tx->tx_Changes)
	{
	    if((vw->vw_CursorPos.pos_Line == vw->vw_LastRE.matchpos.pos_Line) && (vw->vw_CursorPos.pos_Col == vw->vw_LastRE.matchpos.pos_Col))
	    {
		LONG replen = regsublen(vw->vw_LastRE.prog, ReplaceBuff);
		STRPTR repstr;
		setrefresh(RFF_LINEFROM, vw->vw_CursorPos.pos_Col, vw->vw_CursorPos.pos_Line);
		deletechars(vw->vw_LastRE.prog->endp[0] - vw->vw_LastRE.prog->startp[0]);
		if(replen && (repstr = AllocVec(replen, 0)))
		{
		    regsub(vw->vw_LastRE.prog, ReplaceBuff, repstr);
		    rc = insertstr(repstr);
		    FreeVec(repstr);
		}
		else if(replen)
		    settitle(NoMemMsg);
		vw->vw_Tx->tx_Changes++;
	    }
	    else
		settitle("error: cursor not at match position");
	}
	else
	{
	    free(vw->vw_LastRE.prog);
	    vw->vw_LastRE.prog = NULL;
	    settitle("error: file has changed since last RE match");
	}
    }
    else
	settitle("error: must match the RE before replacing it");
    return(rc);
}

void
regerror(char *err)
{
    settitle(err);
}
#endif /* NOREGEXP */

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

Local BOOL
mystricmp(STRPTR str1, STRPTR str2)
{
    while(*str1 && ((toupper(*str1) == toupper(*str2++))))
	str1++;
    if(!(*str1))
	return(TRUE);
    return(FALSE);
}
