This is search.c in view mode; [Download] [Up]
/* search.c */
/* Copyright 1995 by Steve Kirkendall */
char id_search[] = "$Id: search.c,v 2.17 1996/07/17 01:32:27 steve Exp $";
#include "elvis.h"
#if USE_PROTOTYPES
static RESULT searchenter(WINDOW win);
static RESULT forward(WINDOW win);
static RESULT backward(WINDOW win);
#endif
#define REGEXSIZE 150
/* This is the previous regular expression. We need to remember it after
* it has been used, so we can implement the <n> and <shift-N> commands.
*/
regexp *searchre; /* the regular expression itself */
BOOLEAN searchforward; /* is the searching being done in a forward direction? */
BOOLEAN searchhasdelta; /* is the regular expression followed by a line delta? */
long searchdelta; /* if searchhasdelta, then this is the line offset value */
/* This function is called when the user hits <Enter> after typing in a
* regular expression for the visual </> and <?> commands. It compiles
* the regular expression, and then matches it in either a forward or
* backward direction.
*/
static RESULT searchenter(win)
WINDOW win; /* window where a regexp search line has been entered */
{
CHAR regex[REGEXSIZE]; /* holds regular expression, as string */
CHAR delim; /* delimiter, either '?' or '/' */
CHAR *cp; /* used when copying */
STATE *state = win->state;
int i;
assert(markoffset(state->top) <= markoffset(state->cursor));
assert(markoffset(state->cursor) <= markoffset(state->bottom));
/* copy the command line into a buffer */
for (scanalloc(&cp, state->top), i = 0;
cp != NULL && i < markoffset(state->bottom) - markoffset(state->top);
scannext(&cp), i++)
{
regex[i] = *cp;
}
regex[i] = '\0';
scanfree(&cp);
/* skip leading '/' or '?' character, but remember which */
delim = regex[0];
if (delim != '/' && delim != '?')
{
msg(MSG_ERROR, "bad search");
return RESULT_ERROR;
}
/* locate terminating delimiter, if any */
for (cp = ®ex[1]; *cp && *cp != delim; cp++)
{
/* skip backslash-quoted characters */
if (*cp == '\\' && *(cp + 1))
{
cp++;
}
}
/* If there was a terminator, then mark the end of the regular
* expression with a '\0' and leave cp pointing to whatever came
* after the regular expression.
*/
if (*cp)
{
*cp++ = '\0';
}
/* Compile the regular expression. An empty regular expression is
* identical to the current regular expression, so we don't need to
* compile it in that case. If there are any errors, then fail.
* The regcomp function will have already output an error message.
*/
if (regex[1])
{
/* free the previous regular expression, if any */
if (searchre)
{
safefree((void *)searchre);
}
/* compile the new one */
searchre = regcomp(®ex[1], win->cursor);
}
else if (!searchre)
{
msg(MSG_ERROR, "no previous RE");
}
if (!searchre)
{
return RESULT_ERROR;
}
/* '/' implies forward search, '?' implies backward search */
searchforward = (BOOLEAN)(delim == '/');
/* Check for 'v' to force "autoselect" on, or 'n' to force it off.
* Also force it off if it was turned on by a previous 'v' but never
* explicitly addressed by the :set command, and there is no 'v'.
*/
switch (*cp)
{
case 'v':
o_autoselect = True;
cp++;
break;
case 'n':
o_autoselect = False;
cp++;
break;
default:
if ((optflags(o_autoselect) & OPT_SET) == 0)
{
o_autoselect = False;
}
}
/* check for a line delta after the regular expression */
if (*cp == '+' || *cp == '-')
{
searchhasdelta = True;
searchdelta = atol(tochar8(cp));
}
else
{
searchhasdelta = False;
}
/* Now we can do this search exactly like an <n> search command, except
* that it'll be applied to the previous stratum instead of this one.
*/
return RESULT_COMPLETE;
}
/* search forward from the cursor position for the previously compiled
* regular expression.
*/
static RESULT forward(win)
WINDOW win; /* the window to search in */
{
MARKBUF sbuf; /* buffer, holds search mark */
long lnum; /* line number -- used for scanning through buffer */
BLKNO bi; /* bufinfo block of the buffer being searched */
BOOLEAN bol; /* are we at the beginning of a line? */
BOOLEAN wrapped;/* True if we've wrapped at the end of the buffer */
long start; /* line where we started (detects failure after wrap) */
/* setup: determine the line number, whether the cursor is at the
* beginning of the line, and where the buffer ends.
*/
sbuf = *win->state->cursor;
if (markoffset(&sbuf) + 1 >= o_bufchars(markbuffer(&sbuf)))
marksetoffset(&sbuf, 0L);
else
markaddoffset(&sbuf, 1);
bi = bufbufinfo(markbuffer(&sbuf));
lnum = markline(&sbuf);
bol = (BOOLEAN)(lowline(bi, lnum) == markoffset(&sbuf));
wrapped = False;
start = lnum;
/* search */
for (;
!regexec(searchre, &sbuf, bol);
marksetoffset(&sbuf, searchre->nextlinep), bol = True)
{
/* If user gives up, then fail */
if (guipoll(False))
{
return RESULT_ERROR;
}
/* Advance to next line. Maybe wrap at the bottom of the
* buffer. This is also where failed searches are detected.
*/
lnum++;
if (wrapped && lnum > start)
{
msg(MSG_ERROR, "no match");
return RESULT_ERROR;
}
else if (lnum > o_buflines(markbuffer(&sbuf)))
{
if (o_wrapscan)
{
lnum = 1;
searchre->nextlinep = 0L;
wrapped = True;
}
else
{
msg(MSG_ERROR, "no match below");
return RESULT_ERROR;
}
}
}
/* if we get here, then the search succeded */
assert(searchre->leavep >= 0);
marksetoffset(win->state->cursor, searchre->leavep);
if (wrapped)
msg(MSG_STATUS, "wrapped");
return RESULT_COMPLETE;
}
/* search backward from the cursor position for the previously compiled
* regular expression.
*/
static RESULT backward(win)
WINDOW win; /* the window to search in */
{
MARKBUF sbuf; /* buffer, holds search mark */
BLKNO bi; /* bufinfo block of the buffer being searched */
long lnum; /* line number -- used for scanning through buffer */
BOOLEAN wrapped;/* True if we've wrapped at the end of the buffer */
long start; /* line where we started (detects failure after wrap) */
long endln; /* offset of the end of a line (really start of next line) */
long last; /* offset of last match found in a line */
long laststartp;/* offset of the start of the last match in a line */
long lastendp;/* offset of the end of the last match in a line */
CHAR *cp; /* used for scanning for newline characters */
/* setup: determine the line number, whether the cursor is at the
* beginning of the line, and where the buffer ends.
*/
sbuf = *win->state->cursor;
bi = bufbufinfo(markbuffer(&sbuf));
lnum = markline(&sbuf);
marksetoffset(&sbuf, lowline(bi, lnum));
endln = (lnum == o_buflines(markbuffer(&sbuf))
? o_bufchars(markbuffer(&sbuf)) : lowline(bi, lnum + 1));
wrapped = False;
start = lnum;
/* check for match in same line, to left of cursor */
if (regexec(searchre, &sbuf, True)
&& searchre->leavep < markoffset(win->state->cursor))
{
/* find the last match in the line that occurs before the
* current position.
*/
do
{
last = searchre->leavep;
laststartp = searchre->startp[0];
lastendp = searchre->endp[0];
marksetoffset(&sbuf, last + 1);
} while (last + 1 < endln
&& regexec(searchre, &sbuf, False)
&& searchre->leavep < markoffset(win->state->cursor));
marksetoffset(win->state->cursor, last);
searchre->startp[0] = laststartp;
searchre->endp[0] = lastendp;
return RESULT_COMPLETE;
}
/* search for some other line with a match */
scanalloc(&cp, &sbuf);
do
{
/* If user gives up, then fail */
if (guipoll(False))
{
scanfree(&cp);
return RESULT_ERROR;
}
/* Advance to preceding line. Maybe wrap at the top of the
* buffer. This is also where failed searches are detected.
*/
lnum--;
if (wrapped && lnum < start)
{
scanfree(&cp);
msg(MSG_ERROR, "no match");
return RESULT_ERROR;
}
else if (lnum < 1)
{
if (o_wrapscan)
{
lnum = o_buflines(markbuffer(&sbuf));
endln = o_bufchars(markbuffer(&sbuf));
wrapped = True;
}
else
{
scanfree(&cp);
msg(MSG_ERROR, "no match above");
return RESULT_ERROR;
}
/* find the start of the last line */
marksetoffset(&sbuf, lowline(bi, lnum));
scanseek(&cp, &sbuf);
}
else
{
endln = markoffset(&sbuf);
scanprev(&cp);
assert(cp && *cp == '\n');
do
{
scanprev(&cp);
} while (cp && *cp != '\n');
if (cp)
{
scannext(&cp);
sbuf = *scanmark(&cp);
}
else
{
marksetoffset(&sbuf, 0L);
scanseek(&cp, &sbuf);
}
}
} while (!regexec(searchre, &sbuf, True));
scanfree(&cp);
/* If we get here, then the search succeded -- meaning we have found
* a line which contains at least one match. Now we need to locate
* the LAST match in that line.
*/
do
{
last = searchre->leavep;
laststartp = searchre->startp[0];
lastendp = searchre->endp[0];
marksetoffset(&sbuf, last + 1);
} while (last + 1 < endln
&& regexec(searchre, &sbuf, False));
/* move to the last match */
marksetoffset(win->state->cursor, last);
searchre->startp[0] = laststartp;
searchre->endp[0] = lastendp;
if (wrapped)
msg(MSG_STATUS, "wrapped");
return RESULT_COMPLETE;
}
RESULT m_search(win, vinf)
WINDOW win; /* window where operation should take place */
VIINFO *vinf; /* the command to execute */
{
BOOLEAN fwd; /* is this search going to go forward? */
BOOLEAN hint; /* show a "/" or "?" during the search? */
RESULT rc; /* return code */
VIINFO vinfbuf;/* used for constructing a vi command for selections */
assert(vinf->command == 'n' || vinf->command == 'N'
|| vinf->command == '/' || vinf->command == '?'
|| vinf->command == ELVCTRL('A'));
/* If repeating an operator with / or ?, then use n instead. (Since
* search commands don't change the buffer themselves, the only way <.>
* would repeat one is in conjunction with an operator. So we don't
* need do explicitly check for an operator; we KNOW there is one.)
*/
if ((vinf->tweak & TWEAK_DOTTING) != 0
&& (vinf->command == '/' || vinf->command == '?'))
{
vinf->command = 'n';
}
hint = False;
switch (vinf->command)
{
case 'n':
fwd = searchforward;
hint = True;
break;
case 'N':
/* reverse the direction of the search */
fwd = (BOOLEAN)!searchforward;
hint = True;
break;
case ELVCTRL('A'):
/* Free the previous regular expression, if any */
if (searchre)
{
safefree((void *)searchre);
}
/* Compile the regexp /\<\@\>/ */
searchre = regcomp(toCHAR("\\<\\@\\>"), win->cursor);
if (!searchre)
return RESULT_ERROR;
/* always search in the forward direction */
fwd = searchforward = True;
searchhasdelta = False;
searchdelta = 0L;
hint = True;
break;
default:
/* There are two modes of operation here. If the "more"
* flag is not set, then we need to push a new stratum for
* reading a regular expression to search for. If "more"
* is set, then we act just like 'n'.
*/
if (win->state->flags & ELVIS_MORE)
{
win->state->flags &= ~ELVIS_MORE;
fwd = searchforward;
break;
}
statestratum(win, toCHAR(REGEXP_BUF), vinf->command, searchenter);
o_internal(markbuffer(win->state->cursor)) = True;
return RESULT_MORE;
}
/* If we get here, we need to do an 'n' search */
/* This'll only work if we have a regexp */
if (!searchre)
{
msg(MSG_ERROR, "no previous search");
return RESULT_ERROR;
}
/* give a hint that we're searching, if necessary */
if (hint)
{
msg(MSG_STATUS, fwd ? "/" : "?");
}
/* do the search */
if (fwd)
{
rc = forward(win);
}
else
{
rc = backward(win);
}
/* if the search was successful, take care of autoselect & line delta */
if (rc == RESULT_COMPLETE)
{
/* autoselect, maybe with line delta */
if (o_autoselect)
{
/* Cancel any previous selection */
vinfbuf.command = ELVCTRL('[');
(void)v_visible(win, &vinfbuf);
/* Move to the end of the matched text, and start
* marking characters from there. Note that since
* visible selections include the last character, and
* endp[0] contains the offset of the first character
* AFTER the matched text, we usually want to subtract
* 1 from endp[0]. However, if the matching text is
* is zero-length, we select nothing and give a warning.
*/
if (searchre->startp[0] < searchre->endp[0])
{
marksetoffset(win->state->cursor, searchre->endp[0] - 1);
vinfbuf.command = (searchhasdelta ? 'V' : 'v');
(void)v_visible(win, &vinfbuf);
}
else if (searchhasdelta)
{
marksetoffset(win->state->cursor, searchre->endp[0] - 1);
vinfbuf.command = (searchhasdelta ? 'V' : 'v');
(void)v_visible(win, &vinfbuf);
}
else
{
msg(MSG_INFO, "match is zero-length");
}
/* Move back to the beginning of the matched text,
* stretching the selected region to follow the cursor.
* This is also a good time to factor in the line-delta
* if any.
*/
if (searchhasdelta)
{
/* non-zero deltas move the cursor to the front
* of a relative line.
*/
if (searchdelta != 0)
{
/* move the cursor some number of whole lines */
marksetoffset(win->state->cursor,
markoffset(dispmove(win, searchdelta, 0)));
/* leave the cursor at the front of that line */
vinf->tweak |= TWEAK_FRONT;
}
(void)v_visible(win, NULL);
}
else if (searchre->startp[0] < searchre->endp[0])
{
marksetoffset(win->state->cursor, searchre->startp[0]);
(void)v_visible(win, NULL);
}
/* else we already gave a warning instead of selecting text */
}
else
/* no autoselect, but maybe still a line delta */
if (searchhasdelta)
{
/* All line deltas have the side effect of making the
* regexp search command be a line-oriented command
* instead of a character-oriented command.
*/
vinf->tweak |= TWEAK_LINE;
/* non-zero deltas move the cursor to the front
* of a relative line.
*/
if (searchdelta != 0)
{
/* move the cursor some number of whole lines */
marksetoffset(win->state->cursor,
markoffset(dispmove(win, searchdelta, 0)));
/* leave the cursor at the front of that line */
vinf->tweak |= TWEAK_FRONT;
}
}
}
return rc;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.