File: [local] / src / usr.bin / mg / search.c (download)
Revision 1.44, Thu Mar 19 21:22:15 2015 UTC (9 years, 2 months ago) by bcallah
Branch: MAIN
CVS Tags: OPENBSD_6_1_BASE, OPENBSD_6_1, OPENBSD_6_0_BASE, OPENBSD_6_0, OPENBSD_5_9_BASE, OPENBSD_5_9, OPENBSD_5_8_BASE, OPENBSD_5_8 Changes since 1.43: +6 -3 lines
Clean up the includes in mg.
This does the following:
Moves all POSIX headers from sysdef.h into the individual .c files so that
each file now only includes what it needs. All headers are properly sorted.
Moves the remainder of sysdef.h to other files (mostly def.h) and deletes
sysdef.h now that it's no longer contains anything.
Tweak a comment that references sysdef.h so that it no longer does that.
ok florian@
|
/* $OpenBSD: search.c,v 1.44 2015/03/19 21:22:15 bcallah 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 <sys/queue.h>
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include "def.h"
#include "macro.h"
#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)
struct srchcom {
int s_code;
struct line *s_dotp;
int s_doto;
int s_dotline;
};
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 struct 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) {
dobeep();
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) {
dobeep();
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) {
dobeep();
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
return (TRUE);
}
if (srch_lastdir == SRCH_BACK) {
if (backsrch() == FALSE) {
dobeep();
ewprintf("Search failed: \"%s\"", pat);
return (FALSE);
}
return (TRUE);
}
dobeep();
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)
{
if (macrodef || inmacro)
/* We can't isearch in macro. Use search instead */
return (forwsearch(f,n));
else
return (isearch(SRCH_FORW));
}
/*
* Use incremental searching, initially in the reverse direction.
* isearch ignores any explicit arguments.
*/
/* ARGSUSED */
int
backisearch(int f, int n)
{
if (macrodef || inmacro)
/* We can't isearch in macro. Use search instead */
return (backsearch(f,n));
else
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)
{
struct line *clp; /* Saved line pointer */
int c;
int cbo; /* Saved offset */
int success;
int pptr;
int firstc;
int xcase;
int i;
char opat[NPAT];
int cdotline; /* Saved line number */
if (macrodef) {
dobeep();
ewprintf("Can't isearch in macro");
return (FALSE);
}
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;
cdotline = curwp->w_dotline;
is_lpush();
is_cpush(SRCH_BEGIN);
success = TRUE;
is_prompt(dir, TRUE, success);
for (;;) {
update(CMODE);
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;
curwp->w_markline = cdotline;
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_dotline = cdotline;
curwp->w_rflag |= WFMOVE;
srch_lastdir = dir;
(void)ctrlg(FFRAND, 0);
(void)strlcpy(pat, opat, sizeof(pat));
return (ABORT);
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 */
curwp->w_dotp = bfirstlp(curbp);
curwp->w_doto = 0;
curwp->w_dotline = 1;
if (is_find(dir) != FALSE) {
is_cpush(SRCH_MARK);
success = TRUE;
}
ewprintf("Overwrapped I-search: %s", pat);
break;
}
is_lpush();
pptr = strlen(pat);
if (forwchar(FFRAND, 1) == FALSE) {
dobeep();
success = FALSE;
ewprintf("Failed I-search: %s", pat);
} else {
if (is_find(SRCH_FORW) != FALSE)
is_cpush(SRCH_MARK);
else {
(void)backchar(FFRAND, 1);
dobeep();
success = FALSE;
ewprintf("Failed I-search: %s", pat);
}
}
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 */
curwp->w_dotp = blastlp(curbp);
curwp->w_doto = llength(curwp->w_dotp);
curwp->w_dotline = curwp->w_bufp->b_lines;
if (is_find(dir) != FALSE) {
is_cpush(SRCH_MARK);
success = TRUE;
}
ewprintf("Overwrapped I-search: %s", pat);
break;
}
is_lpush();
pptr = strlen(pat);
if (backchar(FFRAND, 1) == FALSE) {
dobeep();
success = FALSE;
} else {
if (is_find(SRCH_BACK) != FALSE)
is_cpush(SRCH_MARK);
else {
(void)forwchar(FFRAND, 1);
dobeep();
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 (pptr == -1)
pptr = 0;
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)))
break;
if (pptr == NPAT - 1) {
dobeep();
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_rflag |= WFMOVE;
update(CMODE);
}
}
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;
curwp->w_markline = cdotline;
ewprintf("Mark set");
curwp->w_rflag |= WFMOVE;
return (TRUE);
}
/* FALLTHRU */
case CCHR('I'):
case CCHR('J'):
addchar:
if (pptr == -1)
pptr = 0;
if (pptr == 0)
success = TRUE;
if (pptr == NPAT - 1)
dobeep();
else {
pat[pptr++] = c;
pat[pptr] = '\0';
}
is_lpush();
if (success != FALSE) {
if (is_find(dir) != FALSE)
is_cpush(c);
else {
success = FALSE;
dobeep();
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;
cmds[ctp].s_dotline = curwp->w_dotline;
}
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_dotline = cmds[cip].s_dotline;
curwp->w_rflag |= 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;
break;
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, odotline;
struct line *odotp;
odoto = curwp->w_doto;
odotp = curwp->w_dotp;
odotline = curwp->w_dotline;
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;
curwp->w_dotline = odotline;
return (FALSE);
}
return (TRUE);
}
if (dir == SRCH_BACK) {
(void)forwchar(FFARG | FFRAND, plen);
if (backsrch() == FALSE) {
curwp->w_doto = odoto;
curwp->w_dotp = odotp;
curwp->w_dotline = odotline;
return (FALSE);
}
return (TRUE);
}
dobeep();
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 */
if (macrodef) {
dobeep();
ewprintf("Can't query replace in macro");
return (FALSE);
}
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(CMODE);
switch (getkey(FALSE)) {
case 'y':
case ' ':
if (lreplace((RSIZE)plen, news) == FALSE)
return (FALSE);
rcnt++;
break;
case '.':
if (lreplace((RSIZE)plen, news) == FALSE)
return (FALSE);
rcnt++;
goto stopsearch;
/* ^G, CR or ESC */
case CCHR('G'):
(void)ctrlg(FFRAND, 0);
goto stopsearch;
case CCHR('['):
case CCHR('M'):
goto stopsearch;
case '!':
do {
if (lreplace((RSIZE)plen, news) == FALSE)
return (FALSE);
rcnt++;
} while (forwsrch() == TRUE);
goto stopsearch;
case 'n':
case CCHR('H'):
/* To not replace */
case CCHR('?'):
break;
default:
ewprintf("y/n or <SP>/<DEL>: replace/don't, [.] repl-end, [!] repl-rest, <CR>/<ESC> quit");
goto retry;
}
}
stopsearch:
curwp->w_rflag |= WFFULL;
update(CMODE);
if (rcnt == 1)
ewprintf("Replaced 1 occurrence");
else
ewprintf("Replaced %d occurrences", 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(CMODE);
if (lreplace((RSIZE)plen, news) == FALSE)
return (FALSE);
rcnt++;
}
curwp->w_rflag |= WFFULL;
update(CMODE);
if (rcnt == 1)
ewprintf("Replaced 1 occurrence");
else
ewprintf("Replaced %d occurrences", 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)
{
struct line *clp, *tlp;
int cbo, tbo, c, i, xcase = 0;
char *pp;
int nline;
clp = curwp->w_dotp;
cbo = curwp->w_doto;
nline = curwp->w_dotline;
for (i = 0; pat[i]; i++)
if (ISUPPER(CHARMASK(pat[i])))
xcase = 1;
for (;;) {
if (cbo == llength(clp)) {
if ((clp = lforw(clp)) == curbp->b_headp)
break;
nline++;
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_headp)
goto fail;
tbo = 0;
c = CCHR('J');
if (eq(c, *pp++, xcase) == FALSE)
goto fail;
nline++;
} else {
c = lgetc(tlp, tbo++);
if (eq(c, *pp++, xcase) == FALSE)
goto fail;
}
}
curwp->w_dotp = tlp;
curwp->w_doto = tbo;
curwp->w_dotline = nline;
curwp->w_rflag |= 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)
{
struct line *clp, *tlp;
int cbo, tbo, c, i, xcase = 0;
char *epp, *pp;
int nline, pline;
for (epp = &pat[0]; epp[1] != 0; ++epp);
clp = curwp->w_dotp;
cbo = curwp->w_doto;
nline = curwp->w_dotline;
for (i = 0; pat[i]; i++)
if (ISUPPER(CHARMASK(pat[i])))
xcase = 1;
for (;;) {
if (cbo == 0) {
clp = lback(clp);
if (clp == curbp->b_headp)
return (FALSE);
nline--;
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;
pline = nline;
while (pp != &pat[0]) {
if (tbo == 0) {
tlp = lback(tlp);
if (tlp == curbp->b_headp)
goto fail;
nline--;
tbo = llength(tlp) + 1;
}
if (--tbo == llength(tlp))
c = CCHR('J');
else
c = lgetc(tlp, tbo);
if (eq(c, *--pp, xcase) == FALSE) {
nline = pline;
goto fail;
}
}
curwp->w_dotp = tlp;
curwp->w_doto = tbo;
curwp->w_dotline = nline;
curwp->w_rflag |= 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);
}