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