1 |
/*- |
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. |
6 |
* |
7 |
* See the LICENSE file for redistribution information. |
8 |
*/ |
9 |
|
10 |
#include "config.h" |
11 |
|
12 |
#ifndef lint |
13 |
static const char sccsid[] = "$Id: search.c,v 10.26 2011/07/04 20:16:26 zy Exp $"; |
14 |
#endif /* not lint */ |
15 |
|
16 |
#include <sys/types.h> |
17 |
#include <sys/queue.h> |
18 |
#include <sys/time.h> |
19 |
|
20 |
#include <bitstring.h> |
21 |
#include <ctype.h> |
22 |
#include <errno.h> |
23 |
#include <limits.h> |
24 |
#include <stdio.h> |
25 |
#include <stdlib.h> |
26 |
#include <string.h> |
27 |
#include <unistd.h> |
28 |
|
29 |
#include "common.h" |
30 |
|
31 |
typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t; |
32 |
|
33 |
static void search_msg __P((SCR *, smsg_t)); |
34 |
static int search_init __P((SCR *, dir_t, CHAR_T *, size_t, CHAR_T **, u_int)); |
35 |
|
36 |
/* |
37 |
* search_init -- |
38 |
* Set up a search. |
39 |
*/ |
40 |
static int |
41 |
search_init( |
42 |
SCR *sp, |
43 |
dir_t dir, |
44 |
CHAR_T *ptrn, |
45 |
size_t plen, |
46 |
CHAR_T **epp, |
47 |
u_int flags) |
48 |
{ |
49 |
recno_t lno; |
50 |
int delim; |
51 |
CHAR_T *p, *t; |
52 |
|
53 |
/* If the file is empty, it's a fast search. */ |
54 |
if (sp->lno <= 1) { |
55 |
if (db_last(sp, &lno)) |
56 |
return (1); |
57 |
if (lno == 0) { |
58 |
if (LF_ISSET(SEARCH_MSG)) |
59 |
search_msg(sp, S_EMPTY); |
60 |
return (1); |
61 |
} |
62 |
} |
63 |
|
64 |
if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */ |
65 |
/* |
66 |
* Use the saved pattern if no pattern specified, or if only |
67 |
* one or two delimiter characters specified. |
68 |
* |
69 |
* !!! |
70 |
* Historically, only the pattern itself was saved, vi didn't |
71 |
* preserve addressing or delta information. |
72 |
*/ |
73 |
if (ptrn == NULL) |
74 |
goto prev; |
75 |
if (plen == 1) { |
76 |
if (epp != NULL) |
77 |
*epp = ptrn + 1; |
78 |
goto prev; |
79 |
} |
80 |
if (ptrn[0] == ptrn[1]) { |
81 |
if (epp != NULL) |
82 |
*epp = ptrn + 2; |
83 |
|
84 |
/* Complain if we don't have a previous pattern. */ |
85 |
prev: if (sp->re == NULL) { |
86 |
search_msg(sp, S_NOPREV); |
87 |
return (1); |
88 |
} |
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, |
92 |
RE_C_SEARCH | |
93 |
(LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT))) |
94 |
return (1); |
95 |
|
96 |
/* Set the search direction. */ |
97 |
if (LF_ISSET(SEARCH_SET)) |
98 |
sp->searchdir = dir; |
99 |
return (0); |
100 |
} |
101 |
|
102 |
/* |
103 |
* Set the delimiter, and move forward to the terminating |
104 |
* delimiter, handling escaped delimiters. |
105 |
* |
106 |
* QUOTING NOTE: |
107 |
* Only discard an escape character if it escapes a delimiter. |
108 |
*/ |
109 |
for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) { |
110 |
if (--plen == 0 || p[0] == delim) { |
111 |
if (plen != 0) |
112 |
++p; |
113 |
break; |
114 |
} |
115 |
if (plen > 1 && p[0] == '\\' && p[1] == delim) { |
116 |
++p; |
117 |
--plen; |
118 |
} |
119 |
} |
120 |
if (epp != NULL) |
121 |
*epp = p; |
122 |
|
123 |
plen = t - ptrn; |
124 |
} |
125 |
|
126 |
/* Compile the RE. */ |
127 |
if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c, |
128 |
RE_C_SEARCH | |
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))) |
132 |
return (1); |
133 |
|
134 |
/* Set the search direction. */ |
135 |
if (LF_ISSET(SEARCH_SET)) |
136 |
sp->searchdir = dir; |
137 |
|
138 |
return (0); |
139 |
} |
140 |
|
141 |
/* |
142 |
* f_search -- |
143 |
* Do a forward search. |
144 |
* |
145 |
* PUBLIC: int f_search __P((SCR *, |
146 |
* PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int)); |
147 |
*/ |
148 |
int |
149 |
f_search( |
150 |
SCR *sp, |
151 |
MARK *fm, |
152 |
MARK *rm, |
153 |
CHAR_T *ptrn, |
154 |
size_t plen, |
155 |
CHAR_T **eptrn, |
156 |
u_int flags) |
157 |
{ |
158 |
busy_t btype; |
159 |
recno_t lno; |
160 |
regmatch_t match[1]; |
161 |
size_t coff, len; |
162 |
int cnt, eval, rval, wrapped; |
163 |
CHAR_T *l; |
164 |
|
165 |
if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags)) |
166 |
return (1); |
167 |
|
168 |
if (LF_ISSET(SEARCH_FILE)) { |
169 |
lno = 1; |
170 |
coff = 0; |
171 |
} else { |
172 |
if (db_get(sp, fm->lno, DBG_FATAL, &l, &len)) |
173 |
return (1); |
174 |
lno = fm->lno; |
175 |
|
176 |
/* |
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. |
180 |
* |
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 |
186 |
* the file. |
187 |
*/ |
188 |
if (LF_ISSET(SEARCH_INCR)) { |
189 |
if ((coff = fm->cno) != 0) |
190 |
--coff; |
191 |
} else if (fm->cno + 1 >= len) { |
192 |
coff = 0; |
193 |
lno = fm->lno + 1; |
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); |
198 |
return (1); |
199 |
} |
200 |
lno = 1; |
201 |
} |
202 |
} else |
203 |
coff = fm->cno + 1; |
204 |
} |
205 |
|
206 |
btype = BUSY_ON; |
207 |
for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; ++lno, coff = 0) { |
208 |
if (cnt-- == 0) { |
209 |
if (INTERRUPTED(sp)) |
210 |
break; |
211 |
if (LF_ISSET(SEARCH_MSG)) { |
212 |
search_busy(sp, btype); |
213 |
btype = BUSY_UPDATE; |
214 |
} |
215 |
cnt = INTERRUPT_CHECK; |
216 |
} |
217 |
if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) { |
218 |
if (wrapped) { |
219 |
if (LF_ISSET(SEARCH_MSG)) |
220 |
search_msg(sp, S_NOTFOUND); |
221 |
break; |
222 |
} |
223 |
if (!O_ISSET(sp, O_WRAPSCAN)) { |
224 |
if (LF_ISSET(SEARCH_MSG)) |
225 |
search_msg(sp, S_EOF); |
226 |
break; |
227 |
} |
228 |
lno = 0; |
229 |
wrapped = 1; |
230 |
continue; |
231 |
} |
232 |
|
233 |
/* If already at EOL, just keep going. */ |
234 |
if (len != 0 && coff == len) |
235 |
continue; |
236 |
|
237 |
/* Set the termination. */ |
238 |
match[0].rm_so = coff; |
239 |
match[0].rm_eo = len; |
240 |
|
241 |
#if defined(DEBUG) && 0 |
242 |
TRACE(sp, "F search: %lu from %u to %u\n", |
243 |
lno, coff, len != 0 ? len - 1 : len); |
244 |
#endif |
245 |
/* Search the line. */ |
246 |
eval = regexec(&sp->re_c, l, 1, match, |
247 |
(match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); |
248 |
if (eval == REG_NOMATCH) |
249 |
continue; |
250 |
if (eval != 0) { |
251 |
if (LF_ISSET(SEARCH_MSG)) |
252 |
re_error(sp, eval, &sp->re_c); |
253 |
else |
254 |
(void)sp->gp->scr_bell(sp); |
255 |
break; |
256 |
} |
257 |
|
258 |
/* Warn if the search wrapped. */ |
259 |
if (wrapped && LF_ISSET(SEARCH_WMSG)) |
260 |
search_msg(sp, S_WRAP); |
261 |
|
262 |
#if defined(DEBUG) && 0 |
263 |
TRACE(sp, "F search: %qu to %qu\n", |
264 |
match[0].rm_so, match[0].rm_eo); |
265 |
#endif |
266 |
rm->lno = lno; |
267 |
rm->cno = match[0].rm_so; |
268 |
|
269 |
/* |
270 |
* If a change command, it's possible to move beyond the end |
271 |
* of a line. Historic vi generally got this wrong (e.g. try |
272 |
* "c?$<cr>"). Not all that sure this gets it right, there |
273 |
* are lots of strange cases. |
274 |
*/ |
275 |
if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len) |
276 |
rm->cno = len != 0 ? len - 1 : 0; |
277 |
|
278 |
rval = 0; |
279 |
break; |
280 |
} |
281 |
|
282 |
if (LF_ISSET(SEARCH_MSG)) |
283 |
search_busy(sp, BUSY_OFF); |
284 |
return (rval); |
285 |
} |
286 |
|
287 |
/* |
288 |
* b_search -- |
289 |
* Do a backward search. |
290 |
* |
291 |
* PUBLIC: int b_search __P((SCR *, |
292 |
* PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int)); |
293 |
*/ |
294 |
int |
295 |
b_search( |
296 |
SCR *sp, |
297 |
MARK *fm, |
298 |
MARK *rm, |
299 |
CHAR_T *ptrn, |
300 |
size_t plen, |
301 |
CHAR_T **eptrn, |
302 |
u_int flags) |
303 |
{ |
304 |
busy_t btype; |
305 |
recno_t lno; |
306 |
regmatch_t match[1]; |
307 |
size_t coff, last, len; |
308 |
int cnt, eval, rval, wrapped; |
309 |
CHAR_T *l; |
310 |
|
311 |
if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags)) |
312 |
return (1); |
313 |
|
314 |
/* |
315 |
* If doing incremental search, set the "starting" position past the |
316 |
* current column, so that we search a minimal distance and still |
317 |
* match special patterns, e.g., \> for the end of a word. This is |
318 |
* safe when the cursor is at the end of a line because we only use |
319 |
* it for comparison with the location of the match. |
320 |
* |
321 |
* Otherwise, start searching immediately before the cursor. If in |
322 |
* the first column, start search on the previous line. |
323 |
*/ |
324 |
if (LF_ISSET(SEARCH_INCR)) { |
325 |
lno = fm->lno; |
326 |
coff = fm->cno + 1; |
327 |
} else { |
328 |
if (fm->cno == 0) { |
329 |
if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) { |
330 |
if (LF_ISSET(SEARCH_MSG)) |
331 |
search_msg(sp, S_SOF); |
332 |
return (1); |
333 |
} |
334 |
lno = fm->lno - 1; |
335 |
} else |
336 |
lno = fm->lno; |
337 |
coff = fm->cno; |
338 |
} |
339 |
|
340 |
btype = BUSY_ON; |
341 |
for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) { |
342 |
if (cnt-- == 0) { |
343 |
if (INTERRUPTED(sp)) |
344 |
break; |
345 |
if (LF_ISSET(SEARCH_MSG)) { |
346 |
search_busy(sp, btype); |
347 |
btype = BUSY_UPDATE; |
348 |
} |
349 |
cnt = INTERRUPT_CHECK; |
350 |
} |
351 |
if ((wrapped && lno < fm->lno) || lno == 0) { |
352 |
if (wrapped) { |
353 |
if (LF_ISSET(SEARCH_MSG)) |
354 |
search_msg(sp, S_NOTFOUND); |
355 |
break; |
356 |
} |
357 |
if (!O_ISSET(sp, O_WRAPSCAN)) { |
358 |
if (LF_ISSET(SEARCH_MSG)) |
359 |
search_msg(sp, S_SOF); |
360 |
break; |
361 |
} |
362 |
if (db_last(sp, &lno)) |
363 |
break; |
364 |
if (lno == 0) { |
365 |
if (LF_ISSET(SEARCH_MSG)) |
366 |
search_msg(sp, S_EMPTY); |
367 |
break; |
368 |
} |
369 |
++lno; |
370 |
wrapped = 1; |
371 |
continue; |
372 |
} |
373 |
|
374 |
if (db_get(sp, lno, 0, &l, &len)) |
375 |
break; |
376 |
|
377 |
/* Set the termination. */ |
378 |
match[0].rm_so = 0; |
379 |
match[0].rm_eo = len; |
380 |
|
381 |
#if defined(DEBUG) && 0 |
382 |
TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo); |
383 |
#endif |
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) |
388 |
continue; |
389 |
if (eval != 0) { |
390 |
if (LF_ISSET(SEARCH_MSG)) |
391 |
re_error(sp, eval, &sp->re_c); |
392 |
else |
393 |
(void)sp->gp->scr_bell(sp); |
394 |
break; |
395 |
} |
396 |
|
397 |
/* Check for a match starting past the cursor. */ |
398 |
if (coff != 0 && match[0].rm_so >= coff) |
399 |
continue; |
400 |
|
401 |
/* Warn if the search wrapped. */ |
402 |
if (wrapped && LF_ISSET(SEARCH_WMSG)) |
403 |
search_msg(sp, S_WRAP); |
404 |
|
405 |
#if defined(DEBUG) && 0 |
406 |
TRACE(sp, "B found: %qu to %qu\n", |
407 |
match[0].rm_so, match[0].rm_eo); |
408 |
#endif |
409 |
/* |
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 |
413 |
* to make this work. |
414 |
*/ |
415 |
for (;;) { |
416 |
last = match[0].rm_so++; |
417 |
if (match[0].rm_so >= len) |
418 |
break; |
419 |
match[0].rm_eo = len; |
420 |
eval = regexec(&sp->re_c, l, 1, match, |
421 |
(match[0].rm_so == 0 ? 0 : REG_NOTBOL) | |
422 |
REG_STARTEND); |
423 |
if (eval == REG_NOMATCH) |
424 |
break; |
425 |
if (eval != 0) { |
426 |
if (LF_ISSET(SEARCH_MSG)) |
427 |
re_error(sp, eval, &sp->re_c); |
428 |
else |
429 |
(void)sp->gp->scr_bell(sp); |
430 |
goto err; |
431 |
} |
432 |
if (coff && match[0].rm_so >= coff) |
433 |
break; |
434 |
} |
435 |
rm->lno = lno; |
436 |
|
437 |
/* See comment in f_search(). */ |
438 |
if (!LF_ISSET(SEARCH_EOL) && last >= len) |
439 |
rm->cno = len != 0 ? len - 1 : 0; |
440 |
else |
441 |
rm->cno = last; |
442 |
rval = 0; |
443 |
break; |
444 |
} |
445 |
|
446 |
err: if (LF_ISSET(SEARCH_MSG)) |
447 |
search_busy(sp, BUSY_OFF); |
448 |
return (rval); |
449 |
} |
450 |
|
451 |
/* |
452 |
* search_msg -- |
453 |
* Display one of the search messages. |
454 |
*/ |
455 |
static void |
456 |
search_msg( |
457 |
SCR *sp, |
458 |
smsg_t msg) |
459 |
{ |
460 |
switch (msg) { |
461 |
case S_EMPTY: |
462 |
msgq(sp, M_ERR, "072|File empty; nothing to search"); |
463 |
break; |
464 |
case S_EOF: |
465 |
msgq(sp, M_ERR, |
466 |
"073|Reached end-of-file without finding the pattern"); |
467 |
break; |
468 |
case S_NOPREV: |
469 |
msgq(sp, M_ERR, "074|No previous search pattern"); |
470 |
break; |
471 |
case S_NOTFOUND: |
472 |
msgq(sp, M_ERR, "075|Pattern not found"); |
473 |
break; |
474 |
case S_SOF: |
475 |
msgq(sp, M_ERR, |
476 |
"076|Reached top-of-file without finding the pattern"); |
477 |
break; |
478 |
case S_WRAP: |
479 |
msgq(sp, M_ERR, "077|Search wrapped"); |
480 |
break; |
481 |
default: |
482 |
abort(); |
483 |
} |
484 |
} |
485 |
|
486 |
/* |
487 |
* search_busy -- |
488 |
* Put up the busy searching message. |
489 |
* |
490 |
* PUBLIC: void search_busy __P((SCR *, busy_t)); |
491 |
*/ |
492 |
void |
493 |
search_busy( |
494 |
SCR *sp, |
495 |
busy_t btype) |
496 |
{ |
497 |
sp->gp->scr_busy(sp, "078|Searching...", btype); |
498 |
} |