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 2015/03/13 18:41:35 zy Exp $";
16 #include <sys/types.h>
17 #include <sys/queue.h>
20 #include <bitstring.h>
31 typedef enum { S_EMPTY
, S_EOF
, S_NOPREV
, S_NOTFOUND
, S_SOF
, S_WRAP
} smsg_t
;
33 static void search_msg(SCR
*, smsg_t
);
34 static int search_init(SCR
*, dir_t
, CHAR_T
*, size_t, CHAR_T
**, u_int
);
53 /* If the file is empty, it's a fast search. */
55 if (db_last(sp
, &lno
))
58 if (LF_ISSET(SEARCH_MSG
))
59 search_msg(sp
, S_EMPTY
);
64 if (LF_ISSET(SEARCH_PARSE
)) { /* Parse the string. */
66 * Use the saved pattern if no pattern specified, or if only
67 * one or two delimiter characters specified.
70 * Historically, only the pattern itself was saved, vi didn't
71 * preserve addressing or delta information.
80 if (ptrn
[0] == ptrn
[1]) {
84 /* Complain if we don't have a previous pattern. */
85 prev
: if (sp
->re
== NULL
) {
86 search_msg(sp
, S_NOPREV
);
89 /* Re-compile the search pattern if necessary. */
90 if (!F_ISSET(sp
, SC_RE_SEARCH
) && re_compile(sp
,
91 sp
->re
, sp
->re_len
, NULL
, NULL
, &sp
->re_c
,
93 (LF_ISSET(SEARCH_MSG
) ? 0 : RE_C_SILENT
)))
96 /* Set the search direction. */
97 if (LF_ISSET(SEARCH_SET
))
103 * Set the delimiter, and move forward to the terminating
104 * delimiter, handling escaped delimiters.
107 * Only discard an escape character if it escapes a delimiter.
109 for (delim
= *ptrn
, p
= t
= ++ptrn
;; *t
++ = *p
++) {
110 if (--plen
== 0 || p
[0] == delim
) {
115 if (plen
> 1 && p
[0] == '\\' && p
[1] == delim
) {
126 /* Compile the RE. */
127 if (re_compile(sp
, ptrn
, plen
, &sp
->re
, &sp
->re_len
, &sp
->re_c
,
129 (LF_ISSET(SEARCH_MSG
) ? 0 : RE_C_SILENT
) |
130 (LF_ISSET(SEARCH_TAG
) ? RE_C_TAG
: 0) |
131 (LF_ISSET(SEARCH_CSCOPE
) ? RE_C_CSCOPE
: 0)))
134 /* Set the search direction. */
135 if (LF_ISSET(SEARCH_SET
))
143 * Do a forward search.
145 * PUBLIC: int f_search(SCR *,
146 * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int);
162 int cnt
, eval
, rval
, wrapped
= 0;
165 if (search_init(sp
, FORWARD
, ptrn
, plen
, eptrn
, flags
))
168 if (LF_ISSET(SEARCH_FILE
)) {
172 if (db_get(sp
, fm
->lno
, DBG_FATAL
, &l
, &len
))
177 * If doing incremental search, start searching at the previous
178 * column, so that we search a minimal distance and still match
179 * special patterns, e.g., \< for beginning of a word.
181 * Otherwise, start searching immediately after the cursor. If
182 * at the end of the line, start searching on the next line.
183 * This is incompatible (read bug fix) with the historic vi --
184 * searches for the '$' pattern never moved forward, and the
185 * "-t foo" didn't work if the 'f' was the first character in
188 if (LF_ISSET(SEARCH_INCR
)) {
189 if ((coff
= fm
->cno
) != 0)
191 } else if (fm
->cno
+ 1 >= len
) {
194 if (db_get(sp
, lno
, 0, &l
, &len
)) {
195 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
196 if (LF_ISSET(SEARCH_MSG
))
197 search_msg(sp
, S_EOF
);
208 for (cnt
= INTERRUPT_CHECK
, rval
= 1;; ++lno
, coff
= 0) {
212 if (LF_ISSET(SEARCH_MSG
)) {
213 search_busy(sp
, btype
);
216 cnt
= INTERRUPT_CHECK
;
218 if ((wrapped
&& lno
> fm
->lno
) || db_get(sp
, lno
, 0, &l
, &len
)) {
220 if (LF_ISSET(SEARCH_MSG
))
221 search_msg(sp
, S_NOTFOUND
);
224 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
225 if (LF_ISSET(SEARCH_MSG
))
226 search_msg(sp
, S_EOF
);
234 /* If already at EOL, just keep going. */
235 if (len
!= 0 && coff
== len
)
238 /* Set the termination. */
239 match
[0].rm_so
= coff
;
240 match
[0].rm_eo
= len
;
242 #if defined(DEBUG) && 0
243 TRACE(sp
, "F search: %lu from %u to %u\n",
244 lno
, coff
, len
!= 0 ? len
- 1 : len
);
246 /* Search the line. */
247 eval
= regexec(&sp
->re_c
, l
, 1, match
,
248 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) | REG_STARTEND
);
249 if (eval
== REG_NOMATCH
)
252 if (LF_ISSET(SEARCH_MSG
))
253 re_error(sp
, eval
, &sp
->re_c
);
255 (void)sp
->gp
->scr_bell(sp
);
259 /* Warn if the search wrapped. */
260 if (wrapped
&& LF_ISSET(SEARCH_WMSG
))
261 search_msg(sp
, S_WRAP
);
263 #if defined(DEBUG) && 0
264 TRACE(sp
, "F search: %qu to %qu\n",
265 match
[0].rm_so
, match
[0].rm_eo
);
268 rm
->cno
= match
[0].rm_so
;
271 * If a change command, it's possible to move beyond the end
272 * of a line. Historic vi generally got this wrong (e.g. try
273 * "c?$<cr>"). Not all that sure this gets it right, there
274 * are lots of strange cases.
276 if (!LF_ISSET(SEARCH_EOL
) && rm
->cno
>= len
)
277 rm
->cno
= len
!= 0 ? len
- 1 : 0;
283 if (LF_ISSET(SEARCH_MSG
))
284 search_busy(sp
, BUSY_OFF
);
290 * Do a backward search.
292 * PUBLIC: int b_search(SCR *,
293 * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int);
308 size_t coff
, last
, len
;
309 int cnt
, eval
, rval
, wrapped
;
312 if (search_init(sp
, BACKWARD
, ptrn
, plen
, eptrn
, flags
))
316 * If doing incremental search, set the "starting" position past the
317 * current column, so that we search a minimal distance and still
318 * match special patterns, e.g., \> for the end of a word. This is
319 * safe when the cursor is at the end of a line because we only use
320 * it for comparison with the location of the match.
322 * Otherwise, start searching immediately before the cursor. If in
323 * the first column, start search on the previous line.
325 if (LF_ISSET(SEARCH_INCR
)) {
330 if (fm
->lno
== 1 && !O_ISSET(sp
, O_WRAPSCAN
)) {
331 if (LF_ISSET(SEARCH_MSG
))
332 search_msg(sp
, S_SOF
);
342 for (cnt
= INTERRUPT_CHECK
, rval
= 1, wrapped
= 0;; --lno
, coff
= 0) {
346 if (LF_ISSET(SEARCH_MSG
)) {
347 search_busy(sp
, btype
);
350 cnt
= INTERRUPT_CHECK
;
352 if ((wrapped
&& lno
< fm
->lno
) || lno
== 0) {
354 if (LF_ISSET(SEARCH_MSG
))
355 search_msg(sp
, S_NOTFOUND
);
358 if (!O_ISSET(sp
, O_WRAPSCAN
)) {
359 if (LF_ISSET(SEARCH_MSG
))
360 search_msg(sp
, S_SOF
);
363 if (db_last(sp
, &lno
))
366 if (LF_ISSET(SEARCH_MSG
))
367 search_msg(sp
, S_EMPTY
);
375 if (db_get(sp
, lno
, 0, &l
, &len
))
378 /* Set the termination. */
380 match
[0].rm_eo
= len
;
382 #if defined(DEBUG) && 0
383 TRACE(sp
, "B search: %lu from 0 to %qu\n", lno
, match
[0].rm_eo
);
385 /* Search the line. */
386 eval
= regexec(&sp
->re_c
, l
, 1, match
,
387 (match
[0].rm_eo
== len
? 0 : REG_NOTEOL
) | REG_STARTEND
);
388 if (eval
== REG_NOMATCH
)
391 if (LF_ISSET(SEARCH_MSG
))
392 re_error(sp
, eval
, &sp
->re_c
);
394 (void)sp
->gp
->scr_bell(sp
);
398 /* Check for a match starting past the cursor. */
399 if (coff
!= 0 && match
[0].rm_so
>= coff
)
402 /* Warn if the search wrapped. */
403 if (wrapped
&& LF_ISSET(SEARCH_WMSG
))
404 search_msg(sp
, S_WRAP
);
406 #if defined(DEBUG) && 0
407 TRACE(sp
, "B found: %qu to %qu\n",
408 match
[0].rm_so
, match
[0].rm_eo
);
411 * We now have the first match on the line. Step through the
412 * line character by character until find the last acceptable
413 * match. This is painful, we need a better interface to regex
417 last
= match
[0].rm_so
++;
418 if (match
[0].rm_so
>= len
)
420 match
[0].rm_eo
= len
;
421 eval
= regexec(&sp
->re_c
, l
, 1, match
,
422 (match
[0].rm_so
== 0 ? 0 : REG_NOTBOL
) |
424 if (eval
== REG_NOMATCH
)
427 if (LF_ISSET(SEARCH_MSG
))
428 re_error(sp
, eval
, &sp
->re_c
);
430 (void)sp
->gp
->scr_bell(sp
);
433 if (coff
&& match
[0].rm_so
>= coff
)
438 /* See comment in f_search(). */
439 if (!LF_ISSET(SEARCH_EOL
) && last
>= len
)
440 rm
->cno
= len
!= 0 ? len
- 1 : 0;
447 err
: if (LF_ISSET(SEARCH_MSG
))
448 search_busy(sp
, BUSY_OFF
);
454 * Display one of the search messages.
463 msgq(sp
, M_ERR
, "072|File empty; nothing to search");
467 "073|Reached end-of-file without finding the pattern");
470 msgq(sp
, M_ERR
, "074|No previous search pattern");
473 msgq(sp
, M_ERR
, "075|Pattern not found");
477 "076|Reached top-of-file without finding the pattern");
480 msgq(sp
, M_ERR
, "077|Search wrapped");
489 * Put up the busy searching message.
491 * PUBLIC: void search_busy(SCR *, busy_t);
498 sp
->gp
->scr_busy(sp
, "078|Searching...", btype
);