File: [local] / src / usr.bin / mg / search.c (download)
Revision 1.22, Tue Aug 9 00:53:48 2005 UTC (18 years, 10 months ago) by kjell
Branch: MAIN
CVS Tags: OPENBSD_3_8_BASE, OPENBSD_3_8 Changes since 1.21: +4 -4 lines
Clean up eread handling in mg. (basically, fallout from the 'enter often
means abort' behaviour added during the hackathon). Eliminates
redundant ereply function, fixes miscellaneous cores when aborting,
and move a number of assumed pathnames into the prompt text, since
they are used there anyway. All changes consistent with emacs behavior
ok beck@ many, many moons ago.
|
/* $OpenBSD: search.c,v 1.22 2005/08/09 00:53:48 kjell Exp $ */
/* This file is in the public domain. */
/*
* Search commands.
* The functions in this file implement the search commands (both plain and
* incremental searches are supported) and the query-replace command.
*
* The plain old search code is part of the original MicroEMACS "distribution".
* The incremental search code and the query-replace code is by Rich Ellison.
*/
#include "def.h"
#include <ctype.h>
#ifndef NO_MACRO
#include "macro.h"
#endif /* !NO_MACRO */
#define SRCH_BEGIN (0) /* Search sub-codes. */
#define SRCH_FORW (-1)
#define SRCH_BACK (-2)
#define SRCH_NOPR (-3)
#define SRCH_ACCM (-4)
#define SRCH_MARK (-5)
typedef struct {
int s_code;
LINE *s_dotp;
int s_doto;
} SRCHCOM;
static int isearch(int);
static void is_cpush(int);
static void is_lpush(void);
static void is_pop(void);
static int is_peek(void);
static void is_undo(int *, int *);
static int is_find(int);
static void is_prompt(int, int, int);
static void is_dspl(char *, int);
static int eq(int, int, int);
static SRCHCOM cmds[NSRCH];
static int cip;
int srch_lastdir = SRCH_NOPR; /* Last search flags. */
/*
* Search forward. Get a search string from the user, and search for it
* starting at ".". If found, "." gets moved to just after the matched
* characters, and display does all the hard stuff. If not found, it just
* prints a message.
*/
/* ARGSUSED */
int
forwsearch(int f, int n)
{
int s;
if ((s = readpattern("Search")) != TRUE)
return (s);
if (forwsrch() == FALSE) {
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
srch_lastdir = SRCH_FORW;
return (TRUE);
}
/*
* Reverse search. Get a search string from the user, and search, starting
* at "." and proceeding toward the front of the buffer. If found "." is
* left pointing at the first character of the pattern [the last character
* that was matched].
*/
/* ARGSUSED */
int
backsearch(int f, int n)
{
int s;
if ((s = readpattern("Search backward")) != TRUE)
return (s);
if (backsrch() == FALSE) {
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
srch_lastdir = SRCH_BACK;
return (TRUE);
}
/*
* Search again, using the same search string and direction as the last
* search command. The direction has been saved in "srch_lastdir", so you
* know which way to go.
*/
/* ARGSUSED */
int
searchagain(int f, int n)
{
if (srch_lastdir == SRCH_FORW) {
if (forwsrch() == FALSE) {
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
return (TRUE);
}
if (srch_lastdir == SRCH_BACK) {
if (backsrch() == FALSE) {
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
return (TRUE);
}
ewprintf("No last search");
return (FALSE);
}
/*
* Use incremental searching, initially in the forward direction.
* isearch ignores any explicit arguments.
*/
/* ARGSUSED */
int
forwisearch(int f, int n)
{
return (isearch(SRCH_FORW));
}
/*
* Use incremental searching, initially in the reverse direction.
* isearch ignores any explicit arguments.
*/
/* ARGSUSED */
int
backisearch(int f, int n)
{
return (isearch(SRCH_BACK));
}
/*
* Incremental Search.
* dir is used as the initial direction to search.
* ^S switch direction to forward
* ^R switch direction to reverse
* ^Q quote next character (allows searching for ^N etc.)
* <ESC> exit from Isearch
* <DEL> undoes last character typed. (tricky job to do this correctly).
* other ^ exit search, don't set mark
* else accumulate into search string
*/
static int
isearch(int dir)
{
LINE *clp;
int c;
int cbo;
int success;
int pptr;
int firstc;
int xcase;
int i;
char opat[NPAT];
#ifndef NO_MACRO
if (macrodef) {
ewprintf("Can't isearch in macro");
return (FALSE);
}
#endif /* !NO_MACRO */
for (cip = 0; cip < NSRCH; cip++)
cmds[cip].s_code = SRCH_NOPR;
(void)strlcpy(opat, pat, sizeof(opat));
cip = 0;
pptr = -1;
clp = curwp->w_dotp;
cbo = curwp->w_doto;
is_lpush();
is_cpush(SRCH_BEGIN);
success = TRUE;
is_prompt(dir, TRUE, success);
for (;;) {
update();
switch (c = getkey(FALSE)) {
case CCHR('['):
/*
* If new characters come in the next 300 msec,
* we can assume that they belong to a longer
* escaped sequence so we should ungetkey the
* ESC to avoid writing out garbage.
*/
if (ttwait(300) == FALSE)
ungetkey(c);
srch_lastdir = dir;
curwp->w_markp = clp;
curwp->w_marko = cbo;
ewprintf("Mark set");
return (TRUE);
case CCHR('G'):
if (success != TRUE) {
while (is_peek() == SRCH_ACCM)
is_undo(&pptr, &dir);
success = TRUE;
is_prompt(dir, pptr < 0, success);
break;
}
curwp->w_dotp = clp;
curwp->w_doto = cbo;
curwp->w_flag |= WFMOVE;
srch_lastdir = dir;
(void)ctrlg(FFRAND, 0);
(void)strlcpy(pat, opat, sizeof(pat));
return (ABORT);
case CCHR(']'):
case CCHR('S'):
if (dir == SRCH_BACK) {
dir = SRCH_FORW;
is_lpush();
is_cpush(SRCH_FORW);
success = TRUE;
}
if (success == FALSE && dir == SRCH_FORW) {
/* wrap the search to beginning */
clp = lforw(curbp->b_linep);
curwp->w_dotp = clp;
curwp->w_doto = 0;
if (is_find(dir) != FALSE) {
is_cpush(SRCH_MARK);
success = TRUE;
}
break;
}
is_lpush();
pptr = strlen(pat);
(void)forwchar(FFRAND, 1);
if (is_find(SRCH_FORW) != FALSE)
is_cpush(SRCH_MARK);
else {
(void)backchar(FFRAND, 1);
ttbeep();
success = FALSE;
}
is_prompt(dir, pptr < 0, success);
break;
case CCHR('R'):
if (dir == SRCH_FORW) {
dir = SRCH_BACK;
is_lpush();
is_cpush(SRCH_BACK);
success = TRUE;
}
if (success == FALSE && dir == SRCH_BACK) {
/* wrap the search to end */
clp = lback(curbp->b_linep);
curwp->w_dotp = clp;
curwp->w_doto =
llength(curwp->w_dotp);
if (is_find(dir) != FALSE) {
is_cpush(SRCH_MARK);
success = TRUE;
}
break;
}
is_lpush();
pptr = strlen(pat);
(void)backchar(FFRAND, 1);
if (is_find(SRCH_BACK) != FALSE)
is_cpush(SRCH_MARK);
else {
(void)forwchar(FFRAND, 1);
ttbeep();
success = FALSE;
}
is_prompt(dir, pptr < 0, success);
break;
case CCHR('W'):
/* add the rest of the current word to the pattern */
clp = curwp->w_dotp;
cbo = curwp->w_doto;
firstc = 1;
if (dir == SRCH_BACK) {
/* when isearching backwards, cbo is the start of the pattern */
cbo += pptr;
}
/* if the search is case insensitive, add to pattern using lowercase */
xcase = 0;
for (i = 0; pat[i]; i++)
if (ISUPPER(CHARMASK(pat[i])))
xcase = 1;
while (cbo < llength(clp)) {
c = lgetc(clp, cbo++);
if ((!firstc && !isalnum(c)) || pptr == NPAT)
break;
firstc = 0;
if (!xcase && ISUPPER(c))
c = TOLOWER(c);
pat[pptr++] = c;
pat[pptr] = '\0';
/* cursor only moves when isearching forwards */
if (dir == SRCH_FORW) {
curwp->w_doto = cbo;
curwp->w_flag |= WFMOVE;
update();
}
}
is_prompt(dir, pptr < 0, success);
break;
case CCHR('H'):
case CCHR('?'):
is_undo(&pptr, &dir);
if (is_peek() != SRCH_ACCM)
success = TRUE;
is_prompt(dir, pptr < 0, success);
break;
case CCHR('\\'):
case CCHR('Q'):
c = (char)getkey(FALSE);
goto addchar;
case CCHR('M'):
c = CCHR('J');
goto addchar;
default:
if (ISCTRL(c)) {
ungetkey(c);
curwp->w_markp = clp;
curwp->w_marko = cbo;
ewprintf("Mark set");
curwp->w_flag |= WFMOVE;
return (TRUE);
} /* and continue */
case CCHR('I'):
case CCHR('J'):
addchar:
if (pptr == -1)
pptr = 0;
if (pptr == 0)
success = TRUE;
pat[pptr++] = c;
if (pptr == NPAT) {
ewprintf("Pattern too long");
return (FALSE);
}
pat[pptr] = '\0';
is_lpush();
if (success != FALSE) {
if (is_find(dir) != FALSE)
is_cpush(c);
else {
success = FALSE;
ttbeep();
is_cpush(SRCH_ACCM);
}
} else
is_cpush(SRCH_ACCM);
is_prompt(dir, FALSE, success);
}
}
/* NOTREACHED */
}
static void
is_cpush(int cmd)
{
if (++cip >= NSRCH)
cip = 0;
cmds[cip].s_code = cmd;
}
static void
is_lpush(void)
{
int ctp;
ctp = cip + 1;
if (ctp >= NSRCH)
ctp = 0;
cmds[ctp].s_code = SRCH_NOPR;
cmds[ctp].s_doto = curwp->w_doto;
cmds[ctp].s_dotp = curwp->w_dotp;
}
static void
is_pop(void)
{
if (cmds[cip].s_code != SRCH_NOPR) {
curwp->w_doto = cmds[cip].s_doto;
curwp->w_dotp = cmds[cip].s_dotp;
curwp->w_flag |= WFMOVE;
cmds[cip].s_code = SRCH_NOPR;
}
if (--cip <= 0)
cip = NSRCH - 1;
}
static int
is_peek(void)
{
return (cmds[cip].s_code);
}
/* this used to always return TRUE (the return value was checked) */
static void
is_undo(int *pptr, int *dir)
{
int redo = FALSE;
switch (cmds[cip].s_code) {
case SRCH_BEGIN:
case SRCH_NOPR:
*pptr = -1;
case SRCH_MARK:
break;
case SRCH_FORW:
*dir = SRCH_BACK;
redo = TRUE;
break;
case SRCH_BACK:
*dir = SRCH_FORW;
redo = TRUE;
break;
case SRCH_ACCM:
default:
*pptr -= 1;
if (*pptr < 0)
*pptr = 0;
pat[*pptr] = '\0';
break;
}
is_pop();
if (redo)
is_undo(pptr, dir);
}
static int
is_find(int dir)
{
int plen, odoto;
LINE *odotp;
odoto = curwp->w_doto;
odotp = curwp->w_dotp;
plen = strlen(pat);
if (plen != 0) {
if (dir == SRCH_FORW) {
(void)backchar(FFARG | FFRAND, plen);
if (forwsrch() == FALSE) {
curwp->w_doto = odoto;
curwp->w_dotp = odotp;
return (FALSE);
}
return (TRUE);
}
if (dir == SRCH_BACK) {
(void)forwchar(FFARG | FFRAND, plen);
if (backsrch() == FALSE) {
curwp->w_doto = odoto;
curwp->w_dotp = odotp;
return (FALSE);
}
return (TRUE);
}
ewprintf("bad call to is_find");
return (FALSE);
}
return (FALSE);
}
/*
* If called with "dir" not one of SRCH_FORW or SRCH_BACK, this routine used
* to print an error message. It also used to return TRUE or FALSE, depending
* on if it liked the "dir". However, none of the callers looked at the
* status, so I just made the checking vanish.
*/
static void
is_prompt(int dir, int flag, int success)
{
if (dir == SRCH_FORW) {
if (success != FALSE)
is_dspl("I-search", flag);
else
is_dspl("Failing I-search", flag);
} else if (dir == SRCH_BACK) {
if (success != FALSE)
is_dspl("I-search backward", flag);
else
is_dspl("Failing I-search backward", flag);
} else
ewprintf("Broken call to is_prompt");
}
/*
* Prompt writing routine for the incremental search. The "prompt" is just
* a string. The "flag" determines whether pat should be printed.
*/
static void
is_dspl(char *prompt, int flag)
{
if (flag != FALSE)
ewprintf("%s: ", prompt);
else
ewprintf("%s: %s", prompt, pat);
}
/*
* Query Replace.
* Replace strings selectively. Does a search and replace operation.
*/
/* ARGSUSED */
int
queryrepl(int f, int n)
{
int s;
int rcnt = 0; /* replacements made so far */
int plen; /* length of found string */
char news[NPAT], *rep; /* replacement string */
#ifndef NO_MACRO
if (macrodef) {
ewprintf("Can't query replace in macro");
return (FALSE);
}
#endif /* !NO_MACRO */
if ((s = readpattern("Query replace")) != TRUE)
return (s);
if ((rep = eread("Query replace %s with: ", news, NPAT,
EFNUL | EFNEW | EFCR, pat)) == NULL)
return (ABORT);
else if (rep[0] == '\0')
news[0] = '\0';
ewprintf("Query replacing %s with %s:", pat, news);
plen = strlen(pat);
/*
* Search forward repeatedly, checking each time whether to insert
* or not. The "!" case makes the check always true, so it gets put
* into a tighter loop for efficiency.
*/
while (forwsrch() == TRUE) {
retry:
update();
switch (getkey(FALSE)) {
case ' ':
if (lreplace((RSIZE)plen, news, f) == FALSE)
return (FALSE);
rcnt++;
break;
case '.':
if (lreplace((RSIZE)plen, news, f) == FALSE)
return (FALSE);
rcnt++;
goto stopsearch;
/* ^G or ESC */
case CCHR('G'):
(void)ctrlg(FFRAND, 0);
case CCHR('['):
goto stopsearch;
case '!':
do {
if (lreplace((RSIZE)plen, news, f) == FALSE)
return (FALSE);
rcnt++;
} while (forwsrch() == TRUE);
goto stopsearch;
case CCHR('H'):
/* To not replace */
case CCHR('?'):
break;
default:
ewprintf("<SP> replace, [.] rep-end, <DEL> don't, [!] repl rest <ESC> quit");
goto retry;
}
}
stopsearch:
curwp->w_flag |= WFHARD;
update();
if (rcnt == 0)
ewprintf("(No replacements done)");
else if (rcnt == 1)
ewprintf("(1 replacement done)");
else
ewprintf("(%d replacements done)", rcnt);
return (TRUE);
}
/*
* Replace string globally without individual prompting.
*/
/* ARGSUSED */
int
replstr(int f, int n)
{
char news[NPAT];
int s, plen, rcnt = 0;
char *r;
if ((s = readpattern("Replace string")) != TRUE)
return s;
r = eread("Replace string %s with: ", news, NPAT,
EFNUL | EFNEW | EFCR, pat);
if (r == NULL)
return (ABORT);
plen = strlen(pat);
while (forwsrch() == TRUE) {
update();
if (lreplace((RSIZE)plen, news, f) == FALSE)
return (FALSE);
rcnt++;
}
curwp->w_flag |= WFHARD;
update();
if (rcnt == 1)
ewprintf("(1 replacement done)");
else
ewprintf("(%d replacements done)", rcnt);
return (TRUE);
}
/*
* This routine does the real work of a forward search. The pattern is sitting
* in the external variable "pat". If found, dot is updated, the window system
* is notified of the change, and TRUE is returned. If the string isn't found,
* FALSE is returned.
*/
int
forwsrch(void)
{
LINE *clp, *tlp;
int cbo, tbo, c, i, xcase = 0;
char *pp;
clp = curwp->w_dotp;
cbo = curwp->w_doto;
for (i = 0; pat[i]; i++)
if (ISUPPER(CHARMASK(pat[i])))
xcase = 1;
for (;;) {
if (cbo == llength(clp)) {
if ((clp = lforw(clp)) == curbp->b_linep)
break;
cbo = 0;
c = CCHR('J');
} else
c = lgetc(clp, cbo++);
if (eq(c, pat[0], xcase) != FALSE) {
tlp = clp;
tbo = cbo;
pp = &pat[1];
while (*pp != 0) {
if (tbo == llength(tlp)) {
tlp = lforw(tlp);
if (tlp == curbp->b_linep)
goto fail;
tbo = 0;
c = CCHR('J');
} else
c = lgetc(tlp, tbo++);
if (eq(c, *pp++, xcase) == FALSE)
goto fail;
}
curwp->w_dotp = tlp;
curwp->w_doto = tbo;
curwp->w_flag |= WFMOVE;
return (TRUE);
}
fail: ;
}
return (FALSE);
}
/*
* This routine does the real work of a backward search. The pattern is
* sitting in the external variable "pat". If found, dot is updated, the
* window system is notified of the change, and TRUE is returned. If the
* string isn't found, FALSE is returned.
*/
int
backsrch(void)
{
LINE *clp, *tlp;
int cbo, tbo, c, i, xcase = 0;
char *epp, *pp;
for (epp = &pat[0]; epp[1] != 0; ++epp);
clp = curwp->w_dotp;
cbo = curwp->w_doto;
for (i = 0; pat[i]; i++)
if (ISUPPER(CHARMASK(pat[i])))
xcase = 1;
for (;;) {
if (cbo == 0) {
clp = lback(clp);
if (clp == curbp->b_linep)
return (FALSE);
cbo = llength(clp) + 1;
}
if (--cbo == llength(clp))
c = CCHR('J');
else
c = lgetc(clp, cbo);
if (eq(c, *epp, xcase) != FALSE) {
tlp = clp;
tbo = cbo;
pp = epp;
while (pp != &pat[0]) {
if (tbo == 0) {
tlp = lback(tlp);
if (tlp == curbp->b_linep)
goto fail;
tbo = llength(tlp) + 1;
}
if (--tbo == llength(tlp))
c = CCHR('J');
else
c = lgetc(tlp, tbo);
if (eq(c, *--pp, xcase) == FALSE)
goto fail;
}
curwp->w_dotp = tlp;
curwp->w_doto = tbo;
curwp->w_flag |= WFMOVE;
return (TRUE);
}
fail: ;
}
/* NOTREACHED */
}
/*
* Compare two characters. The "bc" comes from the buffer. It has its case
* folded out. The "pc" is from the pattern.
*/
static int
eq(int bc, int pc, int xcase)
{
bc = CHARMASK(bc);
pc = CHARMASK(pc);
if (bc == pc)
return (TRUE);
if (xcase)
return (FALSE);
if (ISUPPER(bc))
return (TOLOWER(bc) == pc);
if (ISUPPER(pc))
return (bc == TOLOWER(pc));
return (FALSE);
}
/*
* Read a pattern. Stash it in the external variable "pat". The "pat" is not
* updated if the user types in an empty line. If the user typed an empty
* line, and there is no old pattern, it is an error. Display the old pattern,
* in the style of Jeff Lomicka. There is some do-it-yourself control
* expansion.
*/
int
readpattern(char *prompt)
{
char tpat[NPAT], *rep;
int retval;
if (pat[0] == '\0')
rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, prompt);
else
rep = eread("%s: (default %s) ", tpat, NPAT,
EFNUL | EFNEW | EFCR, prompt, pat);
/* specified */
if (rep == NULL) {
retval = ABORT;
} else if (rep[0] != '\0') {
(void)strlcpy(pat, tpat, sizeof(pat));
retval = TRUE;
} else if (pat[0] != '\0') {
retval = TRUE;
} else
retval = FALSE;
return (retval);
}