* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)search.c 8.32 (Berkeley) 1/9/94";
static int check_delta
__P((SCR
*, EXF
*, long, recno_t
));
static int ctag_conv
__P((SCR
*, char **, int *));
static int get_delta
__P((SCR
*, char **, long *, u_int
*));
static int resetup
__P((SCR
*, regex_t
**, enum direction
,
char *, char **, long *, u_int
*));
static void search_intr
__P((int));
* Set up a search for a regular expression.
resetup(sp
, rep
, dir
, ptrn
, epp
, deltap
, flagp
)
int delim
, eval
, re_flags
, replaced
;
/* Set return information the default. */
* Use saved pattern if no pattern supplied, or if only a delimiter
* character is supplied. Only the pattern was saved, historic vi
* did not reuse any delta supplied.
if (ptrn
[0] == ptrn
[1] && ptrn
[2] == '\0') {
prev
: if (!F_ISSET(sp
, S_SRE_SET
)) {
msgq(sp
, M_ERR
, "No previous search pattern.");
/* Empty patterns set the direction. */
if (LF_ISSET(SEARCH_SET
)) {
re_flags
= 0; /* Set RE flags. */
if (O_ISSET(sp
, O_EXTENDED
))
re_flags
|= REG_EXTENDED
;
if (O_ISSET(sp
, O_IGNORECASE
))
if (LF_ISSET(SEARCH_PARSE
)) { /* Parse the string. */
/* Find terminating delimiter, handling escaped delimiters. */
if (p
[0] == '\0' || p
[0] == delim
) {
if (p
[1] == delim
&& p
[0] == '\\')
* If characters after the terminating delimiter, it may
* be an error, or may be an offset. In either case, we
* return the end of the string, whatever it may be.
if (get_delta(sp
, &p
, deltap
, flagp
))
if (*p
&& LF_ISSET(SEARCH_TERM
)) {
"Characters after search string and/or delta.");
/* Check for "/ " or other such silliness. */
if (re_conv(sp
, &ptrn
, &replaced
))
} else if (LF_ISSET(SEARCH_TAG
)) {
if (ctag_conv(sp
, &ptrn
, &replaced
))
re_flags
&= ~(REG_EXTENDED
| REG_ICASE
);
if (eval
= regcomp(*rep
, ptrn
, re_flags
))
re_error(sp
, eval
, *rep
);
else if (LF_ISSET(SEARCH_SET
)) {
/* Free up any extra memory. */
* Convert a tags search path into something that the POSIX
* 1003.2 RE functions can handle.
ctag_conv(sp
, ptrnp
, replacedp
)
len
= strlen(p
= *ptrnp
);
/* Max memory usage is 2 times the length of the string. */
GET_SPACE_RET(sp
, bp
, blen
, len
* 2);
/* The last charcter is a '/' or '?', we just strip it. */
if (p
[len
- 1] == '/' || p
[len
- 1] == '?')
/* The next-to-last character is a '$', and it's magic. */
/* The first character is a '/' or '?', we just strip it. */
if (p
[0] == '/' || p
[0] == '?')
/* The second character is a '^', and it's magic. */
* Escape every other magic character we can find, stripping the
* backslashes ctags inserts to escape the search delimiter
/* Ctags escapes the search delimiter characters. */
if (p
[0] == '\\' && (p
[1] == '/' || p
[1] == '?'))
else if (strchr("^.[]$*", p
[0]))
* Set the interrupt bit in any screen that is interruptible.
* In the future this may be a problem. The user should be able to move to
* another screen and keep typing while this runs. If so, and the user has
* more than one search/global (see ex/ex_global.c) running, it will be hard
* to decide which one to stop.
for (sp
= __global_list
->dq
.cqh_first
;
sp
!= (void *)&__global_list
->dq
; sp
= sp
->q
.cqe_next
)
if (F_ISSET(sp
, S_INTERRUPTIBLE
))
F_SET(sp
, S_INTERRUPTED
);
#define EMPTYMSG "File empty; nothing to search."
#define EOFMSG "Reached end-of-file without finding the pattern."
#define NOTFOUND "Pattern not found."
#define SOFMSG "Reached top-of-file without finding the pattern."
#define WRAPMSG "Search wrapped."
f_search(sp
, ep
, fm
, rm
, ptrn
, eptrn
, flagp
)
if (file_lline(sp
, ep
, &lno
))
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, EMPTYMSG
);
if (resetup(sp
, &re
, FORWARD
, ptrn
, eptrn
, &delta
, flagp
))
* Start searching immediately after the cursor. If at the end of the
* line, start searching on the next line. This is incompatible (read
* bug fix) with the historic vi -- searches for the '$' pattern never
* moved forward, and "-t foo" didn't work if "foo" was the first thing
if (LF_ISSET(SEARCH_FILE
)) {
if ((l
= file_gline(sp
, ep
, fm
->lno
, &len
)) == NULL
) {
GETLINE_ERR(sp
, fm
->lno
);
if (fm
->cno
+ 1 >= len
) {
if (!O_ISSET(sp
, O_WRAPSCAN
)) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, EOFMSG
);
* Set up busy message, interrupts.
* F_search is called from the ex_tagfirst() routine, which runs
* before the screen really exists. Make sure we don't step on
if (sp
->s_position
!= NULL
)
busy_on(sp
, 1, "Searching...");
SET_UP_INTERRUPTS(search_intr
);
for (rval
= 1, wrapped
= 0;; ++lno
, coff
= 0) {
if (F_ISSET(sp
, S_INTERRUPTED
)) {
msgq(sp
, M_INFO
, "Interrupted.");
if (wrapped
&& lno
> fm
->lno
||
(l
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, NOTFOUND
);
if (!O_ISSET(sp
, O_WRAPSCAN
)) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, EOFMSG
);
/* If already at EOL, just keep going. */
/* Set the termination. */
TRACE(sp
, "F search: %lu from %u to %u\n",
lno
, coff
, len
? len
- 1 : len
);
eval
= regexec(re
, l
, 1, match
,
(match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) | REG_STARTEND
);
if (wrapped
&& O_ISSET(sp
, O_WARN
) && LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, WRAPMSG
);
* If an offset, see if it's legal. It's possible to match
* past the end of the line with $, so check for that case.
if (check_delta(sp
, ep
, delta
, lno
))
TRACE(sp
, "found: %qu to %qu\n",
match
[0].rm_so
, match
[0].rm_eo
);
rm
->cno
= match
[0].rm_so
;
* If a change command, it's possible to move beyond
* the end of a line. Historic vi generally got this
* wrong (try "c?$<cr>"). Not all that sure this gets
* it right, there are lots of strange cases.
if (!LF_ISSET(SEARCH_EOL
) && rm
->cno
>= len
)
rm
->cno
= len
? len
- 1 : 0;
/* Turn off busy message, interrupts. */
if (sp
->s_position
!= NULL
)
b_search(sp
, ep
, fm
, rm
, ptrn
, eptrn
, flagp
)
if (file_lline(sp
, ep
, &lno
))
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, EMPTYMSG
);
if (resetup(sp
, &re
, BACKWARD
, ptrn
, eptrn
, &delta
, flagp
))
/* If in the first column, start searching on the previous line. */
if (!O_ISSET(sp
, O_WRAPSCAN
)) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, SOFMSG
);
/* Turn on busy message, interrupts. */
busy_on(sp
, 1, "Searching...");
if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
SET_UP_INTERRUPTS(search_intr
);
for (rval
= 1, wrapped
= 0, coff
= fm
->cno
;; --lno
, coff
= 0) {
if (F_ISSET(sp
, S_INTERRUPTED
)) {
msgq(sp
, M_INFO
, "Interrupted.");
if (wrapped
&& lno
< fm
->lno
|| lno
== 0) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, NOTFOUND
);
if (!O_ISSET(sp
, O_WRAPSCAN
)) {
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, SOFMSG
);
if (file_lline(sp
, ep
, &lno
))
if (LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, EMPTYMSG
);
if ((l
= file_gline(sp
, ep
, lno
, &len
)) == NULL
)
/* Set the termination. */
match
[0].rm_eo
= coff
? coff
: len
;
TRACE(sp
, "B search: %lu from 0 to %qu\n", lno
, match
[0].rm_eo
);
eval
= regexec(re
, l
, 1, match
,
(match
[0].rm_eo
== len
? 0 : REG_NOTEOL
) | REG_STARTEND
);
if (wrapped
&& O_ISSET(sp
, O_WARN
) && LF_ISSET(SEARCH_MSG
))
msgq(sp
, M_INFO
, WRAPMSG
);
if (check_delta(sp
, ep
, delta
, lno
))
TRACE(sp
, "found: %qu to %qu\n",
match
[0].rm_so
, match
[0].rm_eo
);
* Find the last acceptable one in this line. This
* is really painful, we need a cleaner interface to
* regexec to make this possible.
match
[0].rm_so
= match
[0].rm_eo
+ 1;
if (match
[0].rm_so
>= len
||
coff
&& match
[0].rm_so
>= coff
)
match
[0].rm_eo
= coff
? coff
: len
;
eval
= regexec(re
, l
, 1, match
,
(match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) |
/* See comment in f_search(). */
if (!LF_ISSET(SEARCH_EOL
) && last
>= len
)
rm
->cno
= len
? len
- 1 : 0;
/* Turn off busy message, interrupts. */
if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
* Convert vi's regular expressions into something that the
* the POSIX 1003.2 RE functions can handle.
* There are three conversions we make to make vi's RE's (specifically
* the global, search, and substitute patterns) work with POSIX RE's.
* 1: If O_MAGIC is not set, strip backslashes from the magic character
* set (.[]*~) that have them, and add them to the ones that don't.
* 2: If O_MAGIC is not set, the string "\~" is replaced with the text
* from the last substitute command's replacement string. If O_MAGIC
* is set, it's the string "~".
* 3: The pattern \<ptrn\> does "word" searches, convert it to use the
re_conv(sp
, ptrnp
, replacedp
)
* First pass through, we figure out how much space we'll need.
* We do it in two passes, on the grounds that most of the time
* the user is doing a search and won't have magic characters.
* That way we can skip the malloc and memmove's.
for (p
= *ptrnp
, magic
= 0, needlen
= 0; *p
!= '\0'; ++p
)
needlen
+= sizeof(RE_WSTART
);
needlen
+= sizeof(RE_WSTOP
);
if (!O_ISSET(sp
, O_MAGIC
)) {
if (!O_ISSET(sp
, O_MAGIC
)) {
if (O_ISSET(sp
, O_MAGIC
)) {
if (!O_ISSET(sp
, O_MAGIC
)) {
* Get enough memory to hold the final pattern.
* It's nul-terminated, for now.
GET_SPACE_RET(sp
, bp
, blen
, needlen
+ 1);
for (p
= *ptrnp
, t
= bp
; *p
!= '\0'; ++p
)
memmove(t
, RE_WSTART
, sizeof(RE_WSTART
) - 1);
t
+= sizeof(RE_WSTART
) - 1;
memmove(t
, RE_WSTOP
, sizeof(RE_WSTOP
) - 1);
t
+= sizeof(RE_WSTOP
) - 1;
if (O_ISSET(sp
, O_MAGIC
))
memmove(t
, sp
->repl
, sp
->repl_len
);
if (O_ISSET(sp
, O_MAGIC
))
if (O_ISSET(sp
, O_MAGIC
)) {
memmove(t
, sp
->repl
, sp
->repl_len
);
if (!O_ISSET(sp
, O_MAGIC
))
* Get a line delta. The trickiness is that the delta can be pretty
* complicated, i.e. "+3-2+3++- ++" is allowed.
* In historic vi, if you had a delta on a search pattern which was used as
* a motion command, the command became a line mode command regardless of the
* cursor positions. A fairly common trick is to use a delta of "+0" to make
* the command a line mode command. This is the only place that knows about
* delta's, so we set the return flag information here.
get_delta(sp
, dp
, valp
, flagp
)
for (tval
= 0, p
= *dp
; *p
!= '\0'; *flagp
|= SEARCH_DELTA
) {
if (*p
== '+' || *p
== '-') {
if (!isdigit(*(p
+ 1))) {
overflow
: msgq(sp
, M_ERR
, "Delta value overflow.");
else if (val
== LONG_MIN
)
underflow
: msgq(sp
, M_ERR
, "Delta value underflow.");
msgq(sp
, M_SYSERR
, NULL
);
if (LONG_MAX
- val
< tval
)
if (-(LONG_MIN
- tval
) > val
)
* Check a line delta to see if it's legal.
check_delta(sp
, ep
, delta
, lno
)
/* A delta can overflow a record number. */
if (lno
< LONG_MAX
&& delta
>= (long)lno
) {
msgq(sp
, M_ERR
, "Search offset before line 1.");
if (ULONG_MAX
- lno
< delta
) {
msgq(sp
, M_ERR
, "Delta value overflow.");
if (file_gline(sp
, ep
, lno
+ delta
, NULL
) == NULL
) {
msgq(sp
, M_ERR
, "Search offset past end-of-file.");
* Report a regular expression error.
re_error(sp
, errcode
, preg
)
s
= regerror(errcode
, preg
, "", 0);
if ((oe
= malloc(s
)) == NULL
)
msgq(sp
, M_SYSERR
, NULL
);
(void)regerror(errcode
, preg
, oe
, s
);
msgq(sp
, M_ERR
, "RE error: %s", oe
);