home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-387-Vol-3of3.iso
/
x
/
xvisrc.zoo
/
search.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-07-28
|
27KB
|
1,234 lines
/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)search.c 2.1 (Chris & John Downey) 7/29/92";
#endif
/***
* program name:
xvi
* function:
PD version of UNIX "vi" editor, with extensions.
* module name:
search.c
* module function:
Regular expression searching, including global command.
* history:
STEVIE - ST Editor for VI Enthusiasts, Version 3.10
Originally by Tim Thompson (twitch!tjt)
Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
Heavily modified by Chris & John Downey
***/
#include "xvi.h"
#include "regexp.h" /* Henry Spencer's regular expression routines */
#include "regmagic.h" /* Henry Spencer's regular expression routines */
#ifdef MEGAMAX
overlay "search"
#endif
/*
* String searches
*
* The actual searches are done using Henry Spencer's regular expression
* library.
*/
/*
* Names of values for the P_regextype enumerated parameter.
*/
char *rt_strings[] =
{
"tags",
"grep",
"egrep",
NULL
};
/*
* Used by g/re/p to remember where we are and what we are doing.
*/
static Line *curline;
static Line *lastline;
static long curnum;
static bool_t greptype;
static Posn *bcksearch P((Xviwin *, Line *, int, bool_t));
static Posn *fwdsearch P((Xviwin *, Line *, int, bool_t));
static char *mapstring P((char **, int));
static char *compile P((char *, int, bool_t));
static char *grep_line P((void));
static long substitute P((Xviwin *, Line *, Line *, char *, char *));
/*
* Convert a regular expression to egrep syntax: the source string can
* be either tags compatible (only ^ and $ are significant), vi
* compatible or egrep compatible (but also using \< and \>)
*
* Our first parameter here is the address of a pointer, which we
* point to the closing delimiter character if we found one, otherwise
* the closing '\0'.
*/
static char *
mapstring(sp, delim)
char **sp; /* pointer to pointer to pattern string */
int delim; /* delimiter character */
{
static Flexbuf ns;
int rxtype; /* can be rt_TAGS, rt_GREP or rt_EGREP */
register enum {
m_normal, /* nothing special */
m_startccl, /* just after [ */
m_negccl, /* just after [^ */
m_ccl, /* between [... or [^... and ] */
m_escape /* just after \ */
} state = m_normal;
register char *s;
rxtype = Pn(P_regextype);
flexclear(&ns);
for (s = *sp; *s != '\0' && (*s != delim || state != m_normal); s++) {
switch (state) {
case m_normal:
switch (*s) {
case '\\':
state = m_escape;
break;
case '(': case ')': case '+': case '?': case '|':
/* egrep metacharacters */
if (rxtype != rt_EGREP)
(void) flexaddch(&ns, '\\');
(void) flexaddch(&ns, *s);
break;
case '*': case '.': case '[':
/* grep metacharacters */
if (rxtype == rt_TAGS) {
(void) flexaddch(&ns, '\\');
} else if (*s == '[') {
/* start of character class */
state = m_startccl;
}
/* fall through ... */
default:
(void) flexaddch(&ns, *s);
}
break;
case m_startccl:
case m_negccl:
(void) flexaddch(&ns, *s);
state = (*s == '^' && state == m_startccl) ? m_negccl : m_ccl;
break;
case m_ccl:
(void) flexaddch(&ns, *s);
if (*s == ']')
state = m_normal;
break;
case m_escape:
switch (*s) {
case '(': /* bracket conversion */
case ')':
if (rxtype != rt_GREP)
(void) flexaddch(&ns, '\\');
(void) flexaddch(&ns, *s);
break;
case '.': /* egrep metacharacters */
case '\\':
case '[':
case '*':
case '?':
case '+':
case '^':
case '$':
case '|':
(void) lformat(&ns, "\\%c", *s);
break;
default: /* a normal character */
if (*s != delim)
(void) flexaddch(&ns, '\\');
(void) flexaddch(&ns, *s);
}
state = m_normal;
}
}
*sp = s;
/*
* This is horrible, but the real vi does it, so ...
*/
if (state == m_escape) {
(void) lformat(&ns, "\\\\");
}
return flexgetstr(&ns);
}
/**********************************************************
* *
* Abstract type definition. *
* *
* Regular expression node, with pointer reference count. *
* *
* We need this for global substitute commands. *
* *
**********************************************************/
typedef struct {
regexp *rn_ptr;
int rn_count;
} Rnode;
/*
* Node for last successfully compiled regular expression.
*/
static Rnode *lastprogp = NULL;
/*
* Last regular expression used in a substitution.
*/
static Rnode *last_lhs = NULL;
/*
* Last rhs for a substitution.
*/
static char *last_rhs = NULL;
/*
* rn_new(), rn_delete() & rn_duplicate() perform operations on Rnodes
* which are respectively analogous to open(), close() & dup() for
* Unix file descriptors.
*/
/*
* Make a new Rnode, given a pattern string.
*/
static Rnode *
rn_new(str)
char *str;
{
Rnode *retp;
if ((retp = (Rnode *) alloc(sizeof (Rnode))) == NULL)
return NULL;
if ((retp->rn_ptr = regcomp(str)) == NULL) {
free ((char *) retp);
return NULL;
}
retp->rn_count = 1;
return retp;
}
/*
* Make a copy of an Rnode pointer & increment the Rnode's reference
* count.
*/
#define rn_duplicate(s) ((s) ? ((s)->rn_count++, (s)) : NULL)
/*
* Decrement an Rnode's reference count, freeing it if there are no
* more pointers pointing to it.
*
* In C++, this would be a destructor for an Rnode.
*/
static void
rn_delete(rp)
Rnode *rp;
{
if (rp != NULL && --rp->rn_count <= 0) {
free((char *) rp->rn_ptr);
free((char *) rp);
}
}
#if 0
/*
* Increment the reference count for the current prog,
* and return it to the caller.
*/
static Rnode *
inccount()
{
if (lastprogp != NULL) {
lastprogp->rn_count++;
}
return(lastprogp);
}
#endif
#define cur_prog() (lastprogp->rn_ptr)
/*
* Compile given regular expression from string.
*
* The opening delimiter for the regular expression is supplied; the
* end of it is marked by an unescaped matching delimiter or, if
* delim_only is FALSE, by a '\0' character. We return a pointer to
* the terminating '\0' or to the character following the closing
* delimiter, or NULL if we failed.
*
* If, after we've found a delimiter, we have an empty pattern string,
* we use the last compiled expression if there is one.
*
* The regular expression is converted to egrep syntax by mapstring(),
* which also finds the closing delimiter. The actual compilation is
* done by regcomp(), from Henry Spencer's regexp routines.
*
* If we're successful, the compiled regular expression will be
* pointed to by lastprogp->rn_ptr, & lastprogp->rn_count will be > 0.
*/
static char *
compile(pat, delimiter, delim_only)
char *pat;
int delimiter;
bool_t delim_only;
{
Rnode *progp;
if (pat == NULL) {
return(NULL);
}
/*
* If we get an empty regular expression, we just use the last
* one we compiled (if there was one).
*/
if (*pat == '\0') {
return((delim_only || lastprogp == NULL) ? NULL : pat);
}
if (*pat == delimiter) {
return((lastprogp == NULL) ? NULL : &pat[1]);
}
progp = rn_new(mapstring(&pat, delimiter));
if (progp == NULL) {
return(NULL);
}
if (*pat == '\0') {
if (delim_only) {
rn_delete(progp);
return(NULL);
}
} else {
pat++;
}
rn_delete(lastprogp);
lastprogp = progp;
return(pat);
}
Posn *
search(window, startline, startindex, dir, strp)
Xviwin *window;
Line *startline;
int startindex;
int dir; /* FORWARD or BACKWARD */
char **strp;
{
Posn *pos;
Posn *(*sfunc) P((Xviwin *, Line *, int, bool_t));
char *str;
str = compile(*strp, (dir == FORWARD) ? '/' : '?', FALSE);
if (str == NULL) {
return(NULL);
}
*strp = str;
if (dir == BACKWARD) {
sfunc = bcksearch;
} else {
sfunc = fwdsearch;
}
pos = (*sfunc)(window, startline, startindex, Pb(P_wrapscan));
return(pos);
}
/*
* Search for the given expression, ignoring regextype, without
* wrapscan & and without using the compiled regular expression for
* anything else (so 'n', 'N', etc., aren't affected). We do, however,
* cache the compiled form for the last string we were given.
*/
Posn *
nsearch(window, startline, startindex, dir, str)
Xviwin *window;
Line *startline;
int startindex;
int dir;
char *str;
{
static Rnode *progp = NULL;
static char *last_str = NULL;
Rnode *old_progp;
Posn *pos;
Posn *(*sfunc) P((Xviwin *, Line *, int, bool_t));
if (str == NULL) {
return(NULL);
}
if (str != last_str &&
(last_str == NULL || strcmp(str, last_str) != 0)) {
if (progp) {
rn_delete(progp);
}
progp = rn_new(str);
last_str = str;
}
if (progp == NULL) {
last_str = NULL;
return(NULL);
}
if (dir == BACKWARD) {
sfunc = bcksearch;
} else {
sfunc = fwdsearch;
}
old_progp = lastprogp;
lastprogp = progp;
pos = (*sfunc)(window, startline, startindex, FALSE);
lastprogp = old_progp;
return(pos);
}
/*
* Perform line-based search, returning a pointer to the first line
* (forwards or backwards) on which a match is found, or NULL if there
* is none in the buffer specified.
*/
Line *
linesearch(window, dir, strp)
Xviwin *window;
int dir;
char **strp;
{
Posn pos;
Posn *newpos;
pos = *(window->w_cursor);
if (dir == FORWARD) {
/*
* We don't want a match to occur on the current line,
* but setting the starting position to the next line
* is wrong because we will not match a pattern at the
* start of the line. So go to the end of this line.
*/
if (gchar(&pos) != '\0') {
while (inc(&pos) == mv_SAMELINE) {
;
}
}
} else {
pos.p_index = 0;
}
newpos = search(window, pos.p_line, pos.p_index, dir, strp);
return((newpos != NULL) ? newpos->p_line : NULL);
}
/*
* regerror - called by regexp routines when errors are detected.
*/
void
regerror(s)
char *s;
{
if (echo & e_REGERR) {
show_error(curwin, "%s", s);
}
echo &= ~(e_REGERR | e_NOMATCH);
}
/*
* Find a match at or after "ind" on the given "line"; return
* pointer to Posn of match, or NULL if no match was found.
*/
static Posn *
match(line, ind)
Line *line;
int ind;
{
static Posn matchposn;
char *s;
regexp *prog;
s = line->l_text + ind;
prog = cur_prog();
if (regexec(prog, s, (ind == 0))) {
matchposn.p_line = line;
matchposn.p_index = (int) (prog->startp[0] - line->l_text);
/*
* If the match is after the end of the line,
* move it to the last character of the line,
* unless the line has no characters at all.
*/
if (line->l_text[matchposn.p_index] == '\0' &&
matchposn.p_index > 0) {
matchposn.p_index -= 1;
}
return(&matchposn);
} else {
return(NULL);
}
}
/*
* Like match(), but returns the last available match on the given
* line which is before the index given in maxindex.
*/
static Posn *
rmatch(line, ind, maxindex)
Line *line;
register int ind;
int maxindex;
{
register int lastindex = -1;
Posn *pos;
register char *ltp;
ltp = line->l_text;
for (; (pos = match(line, ind)) != NULL; ind++) {
ind = pos->p_index;
if (ind >= maxindex)
break;
/*
* If we've found a match on the last
* character of the line, return it here or
* we could get into an infinite loop.
*/
if (ltp[lastindex = ind] == '\0' || ltp[ind + 1] == '\0')
break;
}
if (lastindex >= 0) {
static Posn lastmatch;
lastmatch.p_index = lastindex;
lastmatch.p_line = line;
return &lastmatch;
} else {
return NULL;
}
}
/*
* Search forwards through the buffer for a match of the last
* pattern compiled.
*/
static Posn *
fwdsearch(window, startline, startindex, wrapscan)
Xviwin *window;
Line *startline;
int startindex;
bool_t wrapscan;
{
static Posn *pos; /* location of found string */
Line *lp; /* current line */
Line *last;
last = window->w_buffer->b_lastline;
/*
* First, search for a match on the current line
* after the cursor position.
*/
pos = match(startline, startindex + 1);
if (pos != NULL) {
return(pos);
}
/*
* Now search all the lines from here to the end of the file,
* and from the start of the file back to here if (wrapscan).
*/
for (lp = startline->l_next; lp != startline; lp = lp->l_next) {
/*
* Wrap around to the start of the file.
*/
if (lp == last) {
if (wrapscan) {
lp = window->w_buffer->b_line0;
continue;
}
/* else */
return(NULL);
}
pos = match(lp, 0);
if (pos != NULL) {
return(pos);
}
}
/*
* Finally, search from the start of the cursor line
* up to the cursor position. (Wrapscan was set if
* we got here.)
*/
pos = match(startline, 0);
if (pos != NULL) {
if (pos->p_index <= startindex) {
return(pos);
}
}
return(NULL);
}
/*
* Search backwards through the buffer for a match of the last
* pattern compiled.
*
* Because we're searching backwards, we have to return the
* last match on a line if there is more than one, so we call
* rmatch() instead of match().
*/
static Posn *
bcksearch(window, startline, startindex, wrapscan)
Xviwin *window;
Line *startline;
int startindex;
bool_t wrapscan;
{
Posn *pos; /* location of found string */
Line *lp; /* current line */
Line *line0;
/*
* First, search for a match on the current line before the
* current cursor position; if "begword" is set, it must be
* before the current cursor position minus one.
*/
pos = rmatch(startline, 0, startindex);
if (pos != NULL) {
return(pos);
}
/*
* Search all lines back to the start of the buffer,
* and then from the end of the buffer back to the
* line after the cursor line if wrapscan is set.
*/
line0 = window->w_buffer->b_line0;
for (lp = startline->l_prev; lp != startline; lp = lp->l_prev) {
if (lp == line0) {
if (wrapscan) {
/*
* Note we do a continue here so that
* the loop control works properly.
*/
lp = window->w_buffer->b_lastline;
continue;
} else {
return(NULL);
}
}
pos = rmatch(lp, 0, INT_MAX);
if (pos != NULL)
return pos;
}
/*
* Finally, try for a match on the cursor line
* after (or at) the cursor position.
*/
pos = rmatch(startline, startindex, INT_MAX);
if (pos != NULL) {
return(pos);
}
return(NULL);
}
/*
* Execute a global command of the form:
*
* g/pattern/X
*
* where 'x' is a command character, currently one of the following:
*
* d Delete all matching lines
* l List all matching lines
* p Print all matching lines
* s Perform substitution
* & Repeat last substitution
* ~ Apply last right-hand side used in a substitution to last
* regular expression used
*
* The command character (as well as the trailing slash) is optional, and
* is assumed to be 'p' if missing.
*
* The "lp" and "up" parameters are the first line to be considered, and
* the last line to be considered. If these are NULL, the whole buffer is
* considered; if only up is NULL, we consider the single line "lp".
*
* The "matchtype" parameter says whether we are doing 'g' or 'v'.
*/
void
do_global(window, lp, up, cmd, matchtype)
Xviwin *window;
Line *lp, *up;
char *cmd;
bool_t matchtype;
{
Rnode *globprogp;
regexp *prog; /* compiled pattern */
long ndone; /* number of matches */
register char cmdchar = '\0'; /* what to do with matching lines */
/*
* compile() compiles the pattern up to the first unescaped
* delimiter: we place the character after the delimiter in
* cmdchar. If there is no such character, we default to 'p'.
*/
if (*cmd == '\0' || (cmd = compile(&cmd[1], *cmd, FALSE)) == NULL) {
regerror(matchtype ?
"Usage: :g/search pattern/command" :
"Usage: :v/search pattern/command");
return;
}
/*
* Check we can do the command before starting.
*/
switch (cmdchar = *cmd) {
case '\0':
cmdchar = 'p';
/* fall through ... */
case 'l':
case 'p':
break;
case 's':
case '&':
case '~':
cmd++; /* cmd points at char after modifier */
/* fall through ... */
case 'd':
if (!start_command(window)) {
return;
}
break;
default:
regerror("Invalid command character");
return;
}
ndone = 0;
/*
* If no range was given, do every line.
* If only one line was given, just do that one.
* Ensure that "up" points at the line after the
* last one in the range, to make the loop easier.
*/
if (lp == NULL) {
lp = window->w_buffer->b_file;
up = window->w_buffer->b_lastline;
} else if (up == NULL) {
up = lp->l_next;
} else {
up = up->l_next;
}
/*
* If we are going to print lines, it is sensible
* to find out the line number of the first line in
* the range before we start, and increment it as
* we go rather than finding out the line number
* for each line as it is printed.
*/
switch (cmdchar) {
case 'p':
case 'l':
curnum = lineno(window->w_buffer, lp);
curline = lp;
lastline = up;
greptype = matchtype;
disp_init(window, grep_line, (int) Columns,
(cmdchar == 'l'));
return;
}
/*
* This is tricky. do_substitute() might default to
* using cur_prog(), if the command is of the form
*
* :g/pattern/s//.../
*
* so cur_prog() must still reference the expression we
* compiled. On the other hand, it may compile a
* different regular expression, so we have to be able
* to maintain a separate one (which is what globprogp
* is for). Moreover, if it does compile a different
* expression, one of them has to be freed afterwards.
*
* This is why we use Rnodes, which contain
* reference counts. An Rnode, & the compiled
* expression it points to, are only freed when its
* reference count is decremented to 0.
*/
globprogp = rn_duplicate(lastprogp);
prog = cur_prog();
/*
* Place the cursor at bottom left of the window,
* so the user knows what we are doing.
* It is safe not to put the cursor back, because
* we are going to produce some more output anyway.
*/
gotocmd(window, FALSE);
flush_output();
/*
* Try every line from lp up to (but not including) up.
*/
ndone = 0;
while (lp != up) {
if (matchtype == regexec(prog, lp->l_text, TRUE)) {
Line *thisline;
/*
* Move the cursor to the line before
* doing anything. Also move the line
* pointer on one before calling any
* functions which might alter or delete
* the line.
*/
move_cursor(window, lp, 0);
thisline = lp;
lp = lp->l_next;
switch (cmdchar) {
case 'd': /* delete the line */
repllines(window, thisline, 1L,
(Line *) NULL);
ndone++;
break;
case 's': /* perform substitution */
case '&':
case '~':
{
register long (*func) P((Xviwin *, Line *, Line *, char *));
unsigned savecho;
switch (cmdchar) {
case 's':
func = do_substitute;
break;
case '&':
func = do_ampersand;
break;
case '~':
func = do_tilde;
}
savecho = echo;
echo &= ~e_NOMATCH;
ndone += (*func)
(window, thisline, thisline, cmd);
echo = savecho;
break;
}
}
} else {
lp = lp->l_next;
}
}
/*
* If globprogp is still the current prog, this should just
* decrement its reference count to 1: otherwise, if
* do_substitute() has compiled a different pattern, then that
* counts as the last compiled pattern, globprogp's reference
* count should be decremented to 0, & it should be freed.
*/
rn_delete(globprogp);
switch (cmdchar) {
case 'd':
case 's':
case '&':
case '~':
end_command(window);
if (ndone) {
update_buffer(window->w_buffer);
cursupdate(window);
begin_line(window, TRUE);
if (ndone >= Pn(P_report)) {
show_message(window,
(cmdchar == 'd') ?
"%ld fewer line%c" :
"%ld substitution%c",
ndone,
(ndone > 1) ?
's' : ' ');
}
}
}
if (ndone == 0 && (echo & e_NOMATCH)) {
regerror("No match");
}
}
static char *
grep_line()
{
static Flexbuf b;
regexp *prog;
prog = cur_prog();
for ( ; curline != lastline; curline = curline->l_next, curnum++) {
if (greptype == regexec(prog, curline->l_text, TRUE)) {
flexclear(&b);
if (Pb(P_number)) {
(void) lformat(&b, NUM_FMT, curnum);
}
(void) lformat(&b, "%s", curline->l_text);
break;
}
}
if (curline == lastline) {
return(NULL);
} else {
curline = curline->l_next;
curnum++;
return(flexgetstr(&b));
}
}
/*
* regsubst - perform substitutions after a regexp match
*
* Adapted from a routine from Henry Spencer's regexp package. The
* original copyright notice for all these routines is in regexp.c,
* which is distributed herewith.
*/
#ifndef CHARBITS
# define UCHARAT(p) ((int)*(unsigned char *)(p))
#else
# define UCHARAT(p) ((int)*(p)&CHARBITS)
#endif
static void
regsubst(prog, src, dest)
register regexp *prog;
register char *src;
Flexbuf *dest;
{
register int c;
if (prog == NULL || src == NULL || dest == NULL) {
regerror("NULL parameter to regsubst");
return;
}
if (UCHARAT(prog->program) != MAGIC) {
regerror("Damaged regexp fed to regsubst");
return;
}
while ((c = *src++) != '\0') {
register int no;
/*
* First check for metacharacters.
*/
if (c == '&') {
no = 0;
} else if (c == '\\' && '0' <= *src && *src <= '9') {
no = *src++ - '0';
} else {
no = -1;
}
if (no < 0) {
/*
* It's an ordinary character.
*/
if (c == '\\' && *src != '\0')
c = *src++;
(void) flexaddch(dest, c);
} else if (prog->startp[no] != NULL && prog->endp[no] != NULL) {
register char *bracketp;
/*
* It isn't an ordinary character, but a reference
* to a string matched on the lhs. Notice that we
* just do nothing if we find a reference to a null
* match, or one that doesn't exist; we can't tell
* the difference at this stage.
*/
for (bracketp = prog->startp[no]; bracketp < prog->endp[no];
bracketp++) {
if (*bracketp == '\0') {
regerror("Damaged match string");
return;
} else {
(void) flexaddch(dest, *bracketp);
}
}
}
}
}
/*
* do_substitute(window, lp, up, cmd)
*
* Perform a substitution from line 'lp' up to (but not including)
* line 'up' using the command pointed to by 'cmd' which should be
* of the form:
*
* /pattern/substitution/g
*
* The trailing 'g' is optional and, if present, indicates that multiple
* substitutions should be performed on each line, if applicable.
* The usual escapes are supported as described in the regexp docs.
*/
long
do_substitute(window, lp, up, command)
Xviwin *window;
Line *lp, *up;
char *command;
{
char *copy; /* for copy of command */
regexp *prog;
char *sub;
char *cp;
char delimiter;
long nsubs;
copy = alloc((unsigned) strlen(command) + 1);
if (copy == NULL) {
return(0);
}
(void) strcpy(copy, command);
delimiter = *copy;
if (delimiter == '\0' ||
(cp = compile(©[1], delimiter, TRUE)) == NULL) {
regerror("Usage: :s/search pattern/replacement/");
free(copy);
return(0);
}
sub = cp;
prog = cur_prog();
/*
* Scan past the rhs to the flags, if any.
*/
for (; *cp != '\0'; cp++) {
if (*cp == '\\') {
if (*++cp == '\0') {
break;
}
} else if (*cp == delimiter) {
*cp++ = '\0';
break;
}
}
/*
* Save the regular expression for do_ampersand().
*/
if (last_lhs) {
rn_delete(last_lhs);
}
last_lhs = rn_duplicate(lastprogp);
/*
* Save the rhs.
*/
if (last_rhs != NULL) {
free(last_rhs);
}
last_rhs = strsave(sub);
nsubs = substitute(window, lp, up, sub, cp);
free(copy);
return(nsubs);
}
/*
* Repeat last substitution.
*
* For vi compatibility, this also changes the value of the last
* regular expression used.
*/
long
do_ampersand(window, lp, up, flags)
Xviwin *window;
Line *lp, *up;
char *flags;
{
long nsubs;
if (last_lhs == NULL || last_rhs == NULL) {
show_error(window, "No substitute to repeat!");
return(0);
}
rn_delete(lastprogp);
lastprogp = rn_duplicate(last_lhs);
nsubs = substitute(window, lp, up, last_rhs, flags);
return(nsubs);
}
/*
* Apply last right-hand side used in a substitution to last regular
* expression used.
*
* For vi compatibility, this also changes the value of the last
* substitution.
*/
long
do_tilde(window, lp, up, flags)
Xviwin *window;
Line *lp, *up;
char *flags;
{
long nsubs;
if (lastprogp == NULL || last_rhs == NULL) {
show_error(window, "No substitute to repeat!");
return(0);
}
if (last_lhs) {
rn_delete(last_lhs);
}
last_lhs = rn_duplicate(lastprogp);
nsubs = substitute(window, lp, up, last_rhs, flags);
return(nsubs);
}
static long
substitute(window, lp, up, sub, flags)
Xviwin *window;
Line *lp, *up;
char *sub;
char *flags;
{
long nsubs;
Flexbuf ns;
regexp *prog;
bool_t do_all; /* true if 'g' was specified */
if (!start_command(window)) {
return(0);
}
prog = cur_prog();
do_all = (*flags == 'g');
nsubs = 0;
/*
* If no range was given, do the current line.
* If only one line was given, just do that one.
* Ensure that "up" points at the line after the
* last one in the range, to make the loop easier.
*/
if (lp == NULL) {
lp = window->w_cursor->p_line;
}
if (up == NULL) {
up = lp->l_next;
} else {
up = up->l_next;
}
flexnew(&ns);
for (; lp != up; lp = lp->l_next) {
if (regexec(prog, lp->l_text, TRUE)) {
char *p, *matchp;
/*
* Save the line that was last changed for the final
* cursor position (just like the real vi).
*/
move_cursor(window, lp, 0);
flexclear(&ns);
p = lp->l_text;
do {
/*
* Copy up to the part that matched.
*/
while (p < prog->startp[0]) {
(void) flexaddch(&ns, *p);
p++;
}
regsubst(prog, sub, &ns);
/*
* Continue searching after the match.
*
* Watch out for null matches - we
* don't want to go into an endless
* loop here.
*/
matchp = p = prog->endp[0];
if (prog->startp[0] >= p) {
if (*p == '\0') {
/*
* End of the line.
*/
break;
} else {
matchp++;
}
}
} while (do_all && regexec(prog, matchp, FALSE));
/*
* Copy the rest of the line, that didn't match.
*/
(void) lformat(&ns, "%s", p);
replchars(window, lp, 0, strlen(lp->l_text),
flexgetstr(&ns));
nsubs++;
}
}
flexdelete(&ns); /* free the temp buffer */
end_command(window);
if (!nsubs && (echo & e_NOMATCH)) {
regerror("No match");
}
return(nsubs);
}