2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: search.c,v 8.31 1993/12/19 16:17:24 bostic Exp $ (Berkeley) $Date: 1993/12/19 16:17:24 $";
12 #include <sys/types.h>
22 static int check_delta
__P((SCR
*, EXF
*, long, recno_t
));
23 static int ctag_conv
__P((SCR
*, char **, int *));
24 static int get_delta
__P((SCR
*, char **, long *, u_int
*));
25 static int resetup
__P((SCR
*, regex_t
**, enum direction
,
26 char *, char **, long *, u_int
*));
27 static void search_intr
__P((int));
31 * Set up a search for a regular expression.
34 resetup(sp
, rep
, dir
, ptrn
, epp
, deltap
, flagp
)
43 int delim
, eval
, re_flags
, replaced
;
46 /* Set return information the default. */
50 * Use saved pattern if no pattern supplied, or if only a delimiter
51 * character is supplied. Only the pattern was saved, historic vi
52 * did not reuse any delta supplied.
57 if (ptrn
[1] == '\0') {
62 if (ptrn
[0] == ptrn
[1] && ptrn
[2] == '\0') {
65 prev
: if (!F_ISSET(sp
, S_SRE_SET
)) {
66 msgq(sp
, M_ERR
, "No previous search pattern.");
71 /* Empty patterns set the direction. */
72 if (LF_ISSET(SEARCH_SET
)) {
80 re_flags
= 0; /* Set RE flags. */
81 if (O_ISSET(sp
, O_EXTENDED
))
82 re_flags
|= REG_EXTENDED
;
83 if (O_ISSET(sp
, O_IGNORECASE
))
84 re_flags
|= REG_ICASE
;
86 if (LF_ISSET(SEARCH_PARSE
)) { /* Parse the string. */
90 /* Find terminating delimiter, handling escaped delimiters. */
91 for (p
= t
= ptrn
;;) {
92 if (p
[0] == '\0' || p
[0] == delim
) {
98 if (p
[1] == delim
&& p
[0] == '\\')
104 * If characters after the terminating delimiter, it may
105 * be an error, or may be an offset. In either case, we
106 * return the end of the string, whatever it may be.
109 if (get_delta(sp
, &p
, deltap
, flagp
))
111 if (*p
&& LF_ISSET(SEARCH_TERM
)) {
113 "Characters after search string and/or delta.");
120 /* Check for "/ " or other such silliness. */
124 if (re_conv(sp
, &ptrn
, &replaced
))
126 } else if (LF_ISSET(SEARCH_TAG
)) {
127 if (ctag_conv(sp
, &ptrn
, &replaced
))
129 re_flags
&= ~(REG_EXTENDED
| REG_ICASE
);
132 /* Compile the RE. */
133 if (eval
= regcomp(*rep
, ptrn
, re_flags
))
134 re_error(sp
, eval
, *rep
);
135 else if (LF_ISSET(SEARCH_SET
)) {
136 F_SET(sp
, S_SRE_SET
);
141 /* Free up any extra memory. */
143 FREE_SPACE(sp
, ptrn
, 0);
149 * Convert a tags search path into something that the POSIX
150 * 1003.2 RE functions can handle.
153 ctag_conv(sp
, ptrnp
, replacedp
)
164 len
= strlen(p
= *ptrnp
);
166 /* Max memory usage is 2 times the length of the string. */
167 GET_SPACE_RET(sp
, bp
, blen
, len
* 2);
171 /* The last charcter is a '/' or '?', we just strip it. */
172 if (p
[len
- 1] == '/' || p
[len
- 1] == '?')
175 /* The next-to-last character is a '$', and it's magic. */
176 if (p
[len
- 2] == '$') {
182 /* The first character is a '/' or '?', we just strip it. */
183 if (p
[0] == '/' || p
[0] == '?')
186 /* The second character is a '^', and it's magic. */
191 * Escape every other magic character we can find, stripping the
192 * backslashes ctags inserts to escape the search delimiter
196 /* Ctags escapes the search delimiter characters. */
197 if (p
[0] == '\\' && (p
[1] == '/' || p
[1] == '?'))
199 else if (strchr("^.[]$*", p
[0]))
215 * ISIG turns on VINTR, VQUIT and VSUSP. We want VINTR to interrupt the
216 * search, so we install a handler. VQUIT is ignored by main() because
217 * nvi never wants to catch it. A handler for VSUSP should have been
218 * installed by the screen code.
220 #define SET_UP_INTERRUPTS { \
221 act.sa_handler = search_intr; \
222 sigemptyset(&act.sa_mask); \
224 if (isig = !sigaction(SIGINT, &act, &oact)) { \
225 istate = F_ISSET(sp, S_INTERRUPTIBLE); \
226 F_CLR(sp, S_INTERRUPTED); \
227 F_SET(sp, S_INTERRUPTIBLE); \
228 if (tcgetattr(STDIN_FILENO, &term)) { \
229 msgq(sp, M_SYSERR, "tcgetattr"); \
233 nterm.c_lflag |= ISIG; \
234 if (tcsetattr(STDIN_FILENO, \
235 TCSANOW | TCSASOFT, &nterm)) { \
236 msgq(sp, M_SYSERR, "tcsetattr"); \
242 #define TEAR_DOWN_INTERRUPTS { \
244 if (sigaction(SIGINT, &oact, NULL)) \
245 msgq(sp, M_SYSERR, "signal"); \
246 if (tcsetattr(STDIN_FILENO, TCSANOW | TCSASOFT, &term)) \
247 msgq(sp, M_SYSERR, "tcsetattr"); \
248 F_CLR(sp, S_INTERRUPTED); \
250 F_CLR(sp, S_INTERRUPTIBLE); \
256 * Set the interrupt bit in any screen that is interruptible.
259 * In the future this may be a problem. The user should be able to move to
260 * another screen and keep typing while this runs. If so, and the user has
261 * more than one search/global (see ex/ex_global.c) running, it will be hard
262 * to decide which one to stop.
270 for (sp
= __global_list
->dq
.cqh_first
;
271 sp
!= (void *)&__global_list
->dq
; sp
= sp
->q
.cqe_next
)
272 if (F_ISSET(sp
, S_INTERRUPTIBLE
))
273 F_SET(sp
, S_INTERRUPTED
);
276 #define EMPTYMSG "File empty; nothing to search."
277 #define EOFMSG "Reached end-of-file without finding the pattern."
278 #define NOTFOUND "Pattern not found."
279 #define SOFMSG "Reached top-of-file without finding the pattern."
280 #define WRAPMSG "Search wrapped."
283 f_search(sp
, ep
, fm
, rm
, ptrn
, eptrn
, flagp
)
290 struct sigaction act
, oact
;
291 struct termios nterm
, term
;
298 int eval
, isig
, rval
, wrapped
;
301 if (file_lline(sp
, ep
, &lno
))
305 if (LF_ISSET(SEARCH_MSG
))
306 msgq(sp
, M_INFO
, EMPTYMSG
);
311 if (resetup(sp
, &re
, FORWARD
, ptrn
, eptrn
, &delta
, flagp
))
315 * Start searching immediately after the cursor. If at the end of the
316 * line, start searching on the next line. This is incompatible (read
317 * bug fix) with the historic vi -- searches for the '$' pattern never
318 * moved forward, and "-t foo" didn't work if "foo" was the first thing
321 if (LF_ISSET(SEARCH_FILE
)) {
325 if ((l
= file_gline(sp
, ep
, fm
->lno
, &len
)) == NULL
) {
326 GETLINE_ERR(sp
, fm
->lno
);
329 if (fm
->cno
+ 1 >= len
) {
330 if (fm
->lno
== lno
) {
331 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
332 if (LF_ISSET(SEARCH_MSG
))
333 msgq(sp
, M_INFO
, EOFMSG
);
347 * Set up busy message, interrupts.
349 * F_search is called from the ex_tagfirst() routine, which runs
350 * before the screen really exists. Make sure we don't step on
353 if (sp
->s_position
!= NULL
)
354 busy_on(sp
, 1, "Searching...");
356 if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
359 for (rval
= 1, wrapped
= 0;; ++lno
, coff
= 0) {
360 if (F_ISSET(sp
, S_INTERRUPTED
)) {
361 msgq(sp
, M_INFO
, "Interrupted.");
364 if (wrapped
&& lno
> fm
->lno
||
365 (l
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
367 if (LF_ISSET(SEARCH_MSG
))
368 msgq(sp
, M_INFO
, NOTFOUND
);
371 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
372 if (LF_ISSET(SEARCH_MSG
))
373 msgq(sp
, M_INFO
, EOFMSG
);
381 /* If already at EOL, just keep going. */
382 if (len
&& coff
== len
)
385 /* Set the termination. */
386 match
[0].rm_so
= coff
;
387 match
[0].rm_eo
= len
;
389 #if defined(DEBUG) && 0
390 TRACE(sp
, "F search: %lu from %u to %u\n",
391 lno
, coff
, len
? len
- 1 : len
);
393 /* Search the line. */
394 eval
= regexec(re
, l
, 1, match
,
395 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) | REG_STARTEND
);
396 if (eval
== REG_NOMATCH
)
399 re_error(sp
, eval
, re
);
403 /* Warn if wrapped. */
404 if (wrapped
&& O_ISSET(sp
, O_WARN
) && LF_ISSET(SEARCH_MSG
))
405 msgq(sp
, M_INFO
, WRAPMSG
);
408 * If an offset, see if it's legal. It's possible to match
409 * past the end of the line with $, so check for that case.
412 if (check_delta(sp
, ep
, delta
, lno
))
414 rm
->lno
= delta
+ lno
;
417 #if defined(DEBUG) && 0
418 TRACE(sp
, "found: %qu to %qu\n",
419 match
[0].rm_so
, match
[0].rm_eo
);
422 rm
->cno
= match
[0].rm_so
;
425 * If a change command, it's possible to move beyond
426 * the end of a line. Historic vi generally got this
427 * wrong (try "c?$<cr>"). Not all that sure this gets
428 * it right, there are lots of strange cases.
430 if (!LF_ISSET(SEARCH_EOL
) && rm
->cno
>= len
)
431 rm
->cno
= len
? len
- 1 : 0;
437 /* Turn off busy message, interrupts. */
438 if (sp
->s_position
!= NULL
)
441 if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
442 TEAR_DOWN_INTERRUPTS
;
448 b_search(sp
, ep
, fm
, rm
, ptrn
, eptrn
, flagp
)
455 struct sigaction act
, oact
;
456 struct termios nterm
, term
;
460 size_t coff
, len
, last
;
463 int eval
, isig
, rval
, wrapped
;
466 if (file_lline(sp
, ep
, &lno
))
470 if (LF_ISSET(SEARCH_MSG
))
471 msgq(sp
, M_INFO
, EMPTYMSG
);
476 if (resetup(sp
, &re
, BACKWARD
, ptrn
, eptrn
, &delta
, flagp
))
479 /* If in the first column, start searching on the previous line. */
482 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
483 if (LF_ISSET(SEARCH_MSG
))
484 msgq(sp
, M_INFO
, SOFMSG
);
492 /* Turn on busy message, interrupts. */
493 busy_on(sp
, 1, "Searching...");
495 if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
498 for (rval
= 1, wrapped
= 0, coff
= fm
->cno
;; --lno
, coff
= 0) {
499 if (F_ISSET(sp
, S_INTERRUPTED
)) {
500 msgq(sp
, M_INFO
, "Interrupted.");
503 if (wrapped
&& lno
< fm
->lno
|| lno
== 0) {
505 if (LF_ISSET(SEARCH_MSG
))
506 msgq(sp
, M_INFO
, NOTFOUND
);
509 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
510 if (LF_ISSET(SEARCH_MSG
))
511 msgq(sp
, M_INFO
, SOFMSG
);
514 if (file_lline(sp
, ep
, &lno
))
517 if (LF_ISSET(SEARCH_MSG
))
518 msgq(sp
, M_INFO
, EMPTYMSG
);
526 if ((l
= file_gline(sp
, ep
, lno
, &len
)) == NULL
)
529 /* Set the termination. */
531 match
[0].rm_eo
= coff
? coff
: len
;
533 #if defined(DEBUG) && 0
534 TRACE(sp
, "B search: %lu from 0 to %qu\n", lno
, match
[0].rm_eo
);
536 /* Search the line. */
537 eval
= regexec(re
, l
, 1, match
,
538 (match
[0].rm_eo
== len
? 0 : REG_NOTEOL
) | REG_STARTEND
);
539 if (eval
== REG_NOMATCH
)
542 re_error(sp
, eval
, re
);
546 /* Warn if wrapped. */
547 if (wrapped
&& O_ISSET(sp
, O_WARN
) && LF_ISSET(SEARCH_MSG
))
548 msgq(sp
, M_INFO
, WRAPMSG
);
551 if (check_delta(sp
, ep
, delta
, lno
))
553 rm
->lno
= delta
+ lno
;
556 #if defined(DEBUG) && 0
557 TRACE(sp
, "found: %qu to %qu\n",
558 match
[0].rm_so
, match
[0].rm_eo
);
561 * Find the last acceptable one in this line. This
562 * is really painful, we need a cleaner interface to
563 * regexec to make this possible.
566 last
= match
[0].rm_so
;
567 match
[0].rm_so
= match
[0].rm_eo
+ 1;
568 if (match
[0].rm_so
>= len
||
569 coff
&& match
[0].rm_so
>= coff
)
571 match
[0].rm_eo
= coff
? coff
: len
;
572 eval
= regexec(re
, l
, 1, match
,
573 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) |
575 if (eval
== REG_NOMATCH
)
578 re_error(sp
, eval
, re
);
584 /* See comment in f_search(). */
585 if (!LF_ISSET(SEARCH_EOL
) && last
>= len
)
586 rm
->cno
= len
? len
- 1 : 0;
594 /* Turn off busy message, interrupts. */
597 if (F_ISSET(sp
->gp
, G_ISFROMTTY
))
598 TEAR_DOWN_INTERRUPTS
;
605 * Convert vi's regular expressions into something that the
606 * the POSIX 1003.2 RE functions can handle.
608 * There are three conversions we make to make vi's RE's (specifically
609 * the global, search, and substitute patterns) work with POSIX RE's.
611 * 1: If O_MAGIC is not set, strip backslashes from the magic character
612 * set (.[]*~) that have them, and add them to the ones that don't.
613 * 2: If O_MAGIC is not set, the string "\~" is replaced with the text
614 * from the last substitute command's replacement string. If O_MAGIC
615 * is set, it's the string "~".
616 * 3: The pattern \<ptrn\> does "word" searches, convert it to use the
620 re_conv(sp
, ptrnp
, replacedp
)
625 size_t blen
, needlen
;
630 * First pass through, we figure out how much space we'll need.
631 * We do it in two passes, on the grounds that most of the time
632 * the user is doing a search and won't have magic characters.
633 * That way we can skip the malloc and memmove's.
635 for (p
= *ptrnp
, magic
= 0, needlen
= 0; *p
!= '\0'; ++p
)
641 needlen
+= sizeof(RE_WSTART
);
645 needlen
+= sizeof(RE_WSTOP
);
648 if (!O_ISSET(sp
, O_MAGIC
)) {
650 needlen
+= sp
->repl_len
;
657 if (!O_ISSET(sp
, O_MAGIC
)) {
667 if (O_ISSET(sp
, O_MAGIC
)) {
669 needlen
+= sp
->repl_len
;
676 if (!O_ISSET(sp
, O_MAGIC
)) {
692 * Get enough memory to hold the final pattern.
695 * It's nul-terminated, for now.
697 GET_SPACE_RET(sp
, bp
, blen
, needlen
+ 1);
699 for (p
= *ptrnp
, t
= bp
; *p
!= '\0'; ++p
)
704 memmove(t
, RE_WSTART
, sizeof(RE_WSTART
) - 1);
705 t
+= sizeof(RE_WSTART
) - 1;
708 memmove(t
, RE_WSTOP
, sizeof(RE_WSTOP
) - 1);
709 t
+= sizeof(RE_WSTOP
) - 1;
712 if (O_ISSET(sp
, O_MAGIC
))
715 memmove(t
, sp
->repl
, sp
->repl_len
);
723 if (O_ISSET(sp
, O_MAGIC
))
733 if (O_ISSET(sp
, O_MAGIC
)) {
734 memmove(t
, sp
->repl
, sp
->repl_len
);
743 if (!O_ISSET(sp
, O_MAGIC
))
760 * Get a line delta. The trickiness is that the delta can be pretty
761 * complicated, i.e. "+3-2+3++- ++" is allowed.
764 * In historic vi, if you had a delta on a search pattern which was used as
765 * a motion command, the command became a line mode command regardless of the
766 * cursor positions. A fairly common trick is to use a delta of "+0" to make
767 * the command a line mode command. This is the only place that knows about
768 * delta's, so we set the return flag information here.
771 get_delta(sp
, dp
, valp
, flagp
)
780 for (tval
= 0, p
= *dp
; *p
!= '\0'; *flagp
|= SEARCH_DELTA
) {
785 if (*p
== '+' || *p
== '-') {
786 if (!isdigit(*(p
+ 1))) {
788 if (tval
== LONG_MAX
)
792 if (tval
== LONG_MIN
)
804 val
= strtol(p
, &p
, 10);
805 if (errno
== ERANGE
) {
807 overflow
: msgq(sp
, M_ERR
, "Delta value overflow.");
808 else if (val
== LONG_MIN
)
809 underflow
: msgq(sp
, M_ERR
, "Delta value underflow.");
811 msgq(sp
, M_SYSERR
, NULL
);
815 if (LONG_MAX
- val
< tval
)
818 if (-(LONG_MIN
- tval
) > val
)
829 * Check a line delta to see if it's legal.
832 check_delta(sp
, ep
, delta
, lno
)
838 /* A delta can overflow a record number. */
840 if (lno
< LONG_MAX
&& delta
>= (long)lno
) {
841 msgq(sp
, M_ERR
, "Search offset before line 1.");
845 if (ULONG_MAX
- lno
< delta
) {
846 msgq(sp
, M_ERR
, "Delta value overflow.");
849 if (file_gline(sp
, ep
, lno
+ delta
, NULL
) == NULL
) {
850 msgq(sp
, M_ERR
, "Search offset past end-of-file.");
859 * Report a regular expression error.
862 re_error(sp
, errcode
, preg
)
870 s
= regerror(errcode
, preg
, "", 0);
871 if ((oe
= malloc(s
)) == NULL
)
872 msgq(sp
, M_SYSERR
, NULL
);
874 (void)regerror(errcode
, preg
, oe
, s
);
875 msgq(sp
, M_ERR
, "RE error: %s", oe
);