2 * Copyright (c) 1992, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
7 * See the LICENSE file for redistribution information.
13 static const char sccsid
[] = "$Id: search.c,v 10.27 1996/12/17 14:50:10 bostic Exp $ (Berkeley) $Date: 1996/12/17 14:50:10 $";
16 #include <sys/types.h>
17 #include <sys/queue.h>
19 #include <bitstring.h>
30 typedef enum { S_EMPTY
, S_EOF
, S_NOPREV
, S_NOTFOUND
, S_SOF
, S_WRAP
} smsg_t
;
32 static void search_msg
__P((SCR
*, smsg_t
));
33 static int search_init
__P((SCR
*, dir_t
, char *, size_t, char **, u_int
));
40 search_init(sp
, dir
, ptrn
, plen
, epp
, flags
)
51 /* If the file is empty, it's a fast search. */
53 if (db_last(sp
, &lno
))
56 if (LF_ISSET(SEARCH_MSG
))
57 search_msg(sp
, S_EMPTY
);
62 if (LF_ISSET(SEARCH_PARSE
)) { /* Parse the string. */
64 * Use the saved pattern if no pattern specified, or if only
65 * one or two delimiter characters specified.
68 * Historically, only the pattern itself was saved, vi didn't
69 * preserve addressing or delta information.
78 if (ptrn
[0] == ptrn
[1]) {
82 /* Complain if we don't have a previous pattern. */
83 prev
: if (sp
->re
== NULL
) {
84 search_msg(sp
, S_NOPREV
);
87 /* Re-compile the search pattern if necessary. */
88 if (!F_ISSET(sp
, SC_RE_SEARCH
) && re_compile(sp
,
89 sp
->re
, sp
->re_len
, NULL
, NULL
, &sp
->re_c
,
90 SEARCH_CSEARCH
| SEARCH_MSG
))
93 /* Set the search direction. */
94 if (LF_ISSET(SEARCH_SET
))
100 * Set the delimiter, and move forward to the terminating
101 * delimiter, handling escaped delimiters.
104 * Only discard an escape character if it escapes a delimiter.
106 for (delim
= *ptrn
, p
= t
= ++ptrn
;; *t
++ = *p
++) {
107 if (--plen
== 0 || p
[0] == delim
) {
112 if (plen
> 1 && p
[0] == '\\' && p
[1] == delim
) {
123 /* Compile the RE. */
124 if (re_compile(sp
, ptrn
, plen
, &sp
->re
, &sp
->re_len
, &sp
->re_c
,
125 SEARCH_CSEARCH
| LF_ISSET(SEARCH_CSCOPE
| SEARCH_IC
|
126 SEARCH_LITERAL
| SEARCH_MSG
| SEARCH_TAG
)))
129 /* Set the search direction. */
130 if (LF_ISSET(SEARCH_SET
))
138 * Do a forward search.
140 * PUBLIC: int f_search __P((SCR *,
141 * PUBLIC: MARK *, MARK *, char *, size_t, char **, u_int));
144 f_search(sp
, fm
, rm
, ptrn
, plen
, eptrn
, flags
)
155 int cnt
, eval
, rval
, wrapped
;
158 if (search_init(sp
, FORWARD
, ptrn
, plen
, eptrn
, flags
))
161 /* Figure out if we're going to wrap. */
162 if (!LF_ISSET(SEARCH_NOOPT
) && O_ISSET(sp
, O_WRAPSCAN
))
165 if (LF_ISSET(SEARCH_FIRST
)) {
169 if (db_get(sp
, fm
->lno
, DBG_FATAL
, &l
, &len
))
174 * If doing incremental search, start searching at the previous
175 * column, so that we search a minimal distance and still match
176 * special patterns, e.g., \< for beginning of a word.
178 * Otherwise, start searching immediately after the cursor. If
179 * at the end of the line, start searching on the next line.
180 * This is incompatible (read bug fix) with the historic vi --
181 * searches for the '$' pattern never moved forward, and the
182 * "-t foo" didn't work if the 'f' was the first character in
185 if (LF_ISSET(SEARCH_INCR
)) {
186 if ((coff
= fm
->cno
) != 0)
188 } else if (fm
->cno
+ 1 >= len
) {
191 if (db_get(sp
, lno
, 0, &l
, &len
)) {
192 if (!LF_ISSET(SEARCH_WRAP
)) {
193 if (LF_ISSET(SEARCH_MSG
))
194 search_msg(sp
, S_EOF
);
204 for (cnt
= INTERRUPT_CHECK
, rval
= 1, wrapped
= 0;; ++lno
, coff
= 0) {
208 if (LF_ISSET(SEARCH_MSG
)) {
209 search_busy(sp
, btype
);
212 cnt
= INTERRUPT_CHECK
;
214 if (wrapped
&& lno
> fm
->lno
|| db_get(sp
, lno
, 0, &l
, &len
)) {
216 if (LF_ISSET(SEARCH_MSG
))
217 search_msg(sp
, S_NOTFOUND
);
220 if (!LF_ISSET(SEARCH_WRAP
)) {
221 if (LF_ISSET(SEARCH_MSG
))
222 search_msg(sp
, S_EOF
);
230 /* If already at EOL, just keep going. */
231 if (len
!= 0 && coff
== len
)
234 /* Set the termination. */
235 match
[0].rm_so
= coff
;
236 match
[0].rm_eo
= len
;
238 #if defined(DEBUG) && 0
239 vtrace(sp
, "F search: %lu from %u to %u\n",
240 lno
, coff
, len
!= 0 ? len
- 1 : len
);
242 /* Search the line. */
243 eval
= regexec(&sp
->re_c
, l
, 1, match
,
244 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) | REG_STARTEND
);
245 if (eval
== REG_NOMATCH
)
248 if (LF_ISSET(SEARCH_MSG
))
249 re_error(sp
, eval
, &sp
->re_c
);
251 (void)sp
->gp
->scr_bell(sp
);
255 /* Warn if the search wrapped. */
256 if (wrapped
&& LF_ISSET(SEARCH_WMSG
))
257 search_msg(sp
, S_WRAP
);
259 #if defined(DEBUG) && 0
260 vtrace(sp
, "F search: %qu to %qu\n",
261 match
[0].rm_so
, match
[0].rm_eo
);
264 rm
->cno
= match
[0].rm_so
;
267 * If a change command, it's possible to move beyond the end
268 * of a line. Historic vi generally got this wrong (e.g. try
269 * "c?$<cr>"). Not all that sure this gets it right, there
270 * are lots of strange cases.
272 if (!LF_ISSET(SEARCH_EOL
) && rm
->cno
>= len
)
273 rm
->cno
= len
!= 0 ? len
- 1 : 0;
279 if (LF_ISSET(SEARCH_MSG
))
280 search_busy(sp
, BUSY_OFF
);
286 * Do a backward search.
288 * PUBLIC: int b_search __P((SCR *,
289 * PUBLIC: MARK *, MARK *, char *, size_t, char **, u_int));
292 b_search(sp
, fm
, rm
, ptrn
, plen
, eptrn
, flags
)
302 size_t coff
, last
, len
;
303 int cnt
, eval
, rval
, wrapped
;
306 if (search_init(sp
, BACKWARD
, ptrn
, plen
, eptrn
, flags
))
309 /* Figure out if we're going to wrap. */
310 if (!LF_ISSET(SEARCH_NOOPT
) && O_ISSET(sp
, O_WRAPSCAN
))
314 * If doing incremental search, set the "starting" position past the
315 * current column, so that we search a minimal distance and still
316 * match special patterns, e.g., \> for the end of a word. This is
317 * safe when the cursor is at the end of a line because we only use
318 * it for comparison with the location of the match.
320 * Otherwise, start searching immediately before the cursor. If in
321 * the first column, start search on the previous line.
323 if (LF_ISSET(SEARCH_INCR
)) {
328 if (fm
->lno
== 1 && !LF_ISSET(SEARCH_WRAP
)) {
329 if (LF_ISSET(SEARCH_MSG
))
330 search_msg(sp
, S_SOF
);
340 for (cnt
= INTERRUPT_CHECK
, rval
= 1, wrapped
= 0;; --lno
, coff
= 0) {
344 if (LF_ISSET(SEARCH_MSG
)) {
345 search_busy(sp
, btype
);
348 cnt
= INTERRUPT_CHECK
;
350 if (wrapped
&& lno
< fm
->lno
|| lno
== 0) {
352 if (LF_ISSET(SEARCH_MSG
))
353 search_msg(sp
, S_NOTFOUND
);
356 if (!LF_ISSET(SEARCH_WRAP
)) {
357 if (LF_ISSET(SEARCH_MSG
))
358 search_msg(sp
, S_SOF
);
361 if (db_last(sp
, &lno
))
364 if (LF_ISSET(SEARCH_MSG
))
365 search_msg(sp
, S_EMPTY
);
373 if (db_get(sp
, lno
, 0, &l
, &len
))
376 /* Set the termination. */
378 match
[0].rm_eo
= len
;
380 #if defined(DEBUG) && 0
382 "B search: %lu from 0 to %qu\n", lno
, match
[0].rm_eo
);
384 /* Search the line. */
385 eval
= regexec(&sp
->re_c
, l
, 1, match
,
386 (match
[0].rm_eo
== len
? 0 : REG_NOTEOL
) | REG_STARTEND
);
387 if (eval
== REG_NOMATCH
)
390 if (LF_ISSET(SEARCH_MSG
))
391 re_error(sp
, eval
, &sp
->re_c
);
393 (void)sp
->gp
->scr_bell(sp
);
397 /* Check for a match starting past the cursor. */
398 if (coff
!= 0 && match
[0].rm_so
>= coff
)
401 /* Warn if the search wrapped. */
402 if (wrapped
&& LF_ISSET(SEARCH_WMSG
))
403 search_msg(sp
, S_WRAP
);
405 #if defined(DEBUG) && 0
406 vtrace(sp
, "B found: %qu to %qu\n",
407 match
[0].rm_so
, match
[0].rm_eo
);
410 * We now have the first match on the line. Step through the
411 * line character by character until find the last acceptable
412 * match. This is painful, we need a better interface to regex
416 last
= match
[0].rm_so
++;
417 if (match
[0].rm_so
>= len
)
419 match
[0].rm_eo
= len
;
420 eval
= regexec(&sp
->re_c
, l
, 1, match
,
421 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) |
423 if (eval
== REG_NOMATCH
)
426 if (LF_ISSET(SEARCH_MSG
))
427 re_error(sp
, eval
, &sp
->re_c
);
429 (void)sp
->gp
->scr_bell(sp
);
432 if (coff
&& match
[0].rm_so
>= coff
)
437 /* See comment in f_search(). */
438 if (!LF_ISSET(SEARCH_EOL
) && last
>= len
)
439 rm
->cno
= len
!= 0 ? len
- 1 : 0;
446 err
: if (LF_ISSET(SEARCH_MSG
))
447 search_busy(sp
, BUSY_OFF
);
453 * Display one of the search messages.
462 msgq(sp
, M_ERR
, "072|File empty; nothing to search");
466 "073|Reached end-of-file without finding the pattern");
469 msgq(sp
, M_ERR
, "074|No previous search pattern");
472 msgq(sp
, M_ERR
, "075|Pattern not found");
476 "076|Reached top-of-file without finding the pattern");
479 msgq(sp
, M_ERR
, "077|Search wrapped");
488 * Put up the busy searching message.
490 * PUBLIC: void search_busy __P((SCR *, busy_t));
493 search_busy(sp
, btype
)
497 sp
->gp
->scr_busy(sp
, "078|Searching...", btype
);