]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/nvi/common/search.c
Optionally bind ktls threads to NUMA domains
[FreeBSD/FreeBSD.git] / contrib / nvi / common / search.c
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
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] == '\\' && p[1] == delim) {
107                                 ++p;
108                                 --plen;
109                         }
110                 }
111                 if (epp != NULL)
112                         *epp = p;
113
114                 plen = t - ptrn;
115         }
116
117         /* Compile the RE. */
118         if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c,
119             RE_C_SEARCH |
120             (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) |
121             (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0) |
122             (LF_ISSET(SEARCH_CSCOPE) ? RE_C_CSCOPE : 0)))
123                 return (1);
124
125         /* Set the search direction. */
126         if (LF_ISSET(SEARCH_SET))
127                 sp->searchdir = dir;
128
129         return (0);
130 }
131
132 /*
133  * f_search --
134  *      Do a forward search.
135  *
136  * PUBLIC: int f_search(SCR *,
137  * PUBLIC:    MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int);
138  */
139 int
140 f_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen,
141     CHAR_T **eptrn, u_int flags)
142 {
143         busy_t btype;
144         recno_t lno;
145         regmatch_t match[1];
146         size_t coff, len;
147         int cnt, eval, rval, wrapped = 0;
148         CHAR_T *l;
149
150         if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags))
151                 return (1);
152
153         if (LF_ISSET(SEARCH_FILE)) {
154                 lno = 1;
155                 coff = 0;
156         } else {
157                 if (db_get(sp, fm->lno, DBG_FATAL, &l, &len))
158                         return (1);
159                 lno = fm->lno;
160
161                 /*
162                  * If doing incremental search, start searching at the previous
163                  * column, so that we search a minimal distance and still match
164                  * special patterns, e.g., \< for beginning of a word.
165                  *
166                  * Otherwise, start searching immediately after the cursor.  If
167                  * at the end of the line, start searching on the next line.
168                  * This is incompatible (read bug fix) with the historic vi --
169                  * searches for the '$' pattern never moved forward, and the
170                  * "-t foo" didn't work if the 'f' was the first character in
171                  * the file.
172                  */
173                 if (LF_ISSET(SEARCH_INCR)) {
174                         if ((coff = fm->cno) != 0)
175                                 --coff;
176                 } else if (fm->cno + 1 >= len) {
177                         coff = 0;
178                         lno = fm->lno + 1;
179                         if (db_get(sp, lno, 0, &l, &len)) {
180                                 if (!O_ISSET(sp, O_WRAPSCAN)) {
181                                         if (LF_ISSET(SEARCH_MSG))
182                                                 search_msg(sp, S_EOF);
183                                         return (1);
184                                 }
185                                 lno = 1;
186                                 wrapped = 1;
187                         }
188                 } else
189                         coff = fm->cno + 1;
190         }
191
192         btype = BUSY_ON;
193         for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) {
194                 if (cnt-- == 0) {
195                         if (INTERRUPTED(sp))
196                                 break;
197                         if (LF_ISSET(SEARCH_MSG)) {
198                                 search_busy(sp, btype);
199                                 btype = BUSY_UPDATE;
200                         }
201                         cnt = INTERRUPT_CHECK;
202                 }
203                 if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) {
204                         if (wrapped) {
205                                 if (LF_ISSET(SEARCH_MSG))
206                                         search_msg(sp, S_NOTFOUND);
207                                 break;
208                         }
209                         if (!O_ISSET(sp, O_WRAPSCAN)) {
210                                 if (LF_ISSET(SEARCH_MSG))
211                                         search_msg(sp, S_EOF);
212                                 break;
213                         }
214                         lno = 0;
215                         wrapped = 1;
216                         continue;
217                 }
218
219                 /* If already at EOL, just keep going. */
220                 if (len != 0 && coff == len)
221                         continue;
222
223                 /* Set the termination. */
224                 match[0].rm_so = coff;
225                 match[0].rm_eo = len;
226
227 #if defined(DEBUG) && 0
228                 TRACE(sp, "F search: %lu from %u to %u\n",
229                     lno, coff, len != 0 ? len - 1 : len);
230 #endif
231                 /* Search the line. */
232                 eval = regexec(&sp->re_c, l, 1, match,
233                     (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND);
234                 if (eval == REG_NOMATCH)
235                         continue;
236                 if (eval != 0) {
237                         if (LF_ISSET(SEARCH_MSG))
238                                 re_error(sp, eval, &sp->re_c);
239                         else
240                                 (void)sp->gp->scr_bell(sp);
241                         break;
242                 }
243
244                 /* Warn if the search wrapped. */
245                 if (wrapped && LF_ISSET(SEARCH_WMSG))
246                         search_msg(sp, S_WRAP);
247
248 #if defined(DEBUG) && 0
249                 TRACE(sp, "F search: %qu to %qu\n",
250                     match[0].rm_so, match[0].rm_eo);
251 #endif
252                 rm->lno = lno;
253                 rm->cno = match[0].rm_so;
254
255                 /*
256                  * If a change command, it's possible to move beyond the end
257                  * of a line.  Historic vi generally got this wrong (e.g. try
258                  * "c?$<cr>").  Not all that sure this gets it right, there
259                  * are lots of strange cases.
260                  */
261                 if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len)
262                         rm->cno = len != 0 ? len - 1 : 0;
263
264                 rval = 0;
265                 break;
266         }
267
268         if (LF_ISSET(SEARCH_MSG))
269                 search_busy(sp, BUSY_OFF);
270         return (rval);
271 }
272
273 /*
274  * b_search --
275  *      Do a backward search.
276  *
277  * PUBLIC: int b_search(SCR *,
278  * PUBLIC:    MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int);
279  */
280 int
281 b_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen,
282     CHAR_T **eptrn, u_int flags)
283 {
284         busy_t btype;
285         recno_t lno;
286         regmatch_t match[1];
287         size_t coff, last, len;
288         int cnt, eval, rval, wrapped;
289         CHAR_T *l;
290
291         if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags))
292                 return (1);
293
294         /*
295          * If doing incremental search, set the "starting" position past the
296          * current column, so that we search a minimal distance and still
297          * match special patterns, e.g., \> for the end of a word.  This is
298          * safe when the cursor is at the end of a line because we only use
299          * it for comparison with the location of the match.
300          *
301          * Otherwise, start searching immediately before the cursor.  If in
302          * the first column, start search on the previous line.
303          */
304         if (LF_ISSET(SEARCH_INCR)) {
305                 lno = fm->lno;
306                 coff = fm->cno + 1;
307         } else {
308                 if (fm->cno == 0) {
309                         if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) {
310                                 if (LF_ISSET(SEARCH_MSG))
311                                         search_msg(sp, S_SOF);
312                                 return (1);
313                         }
314                         lno = fm->lno - 1;
315                 } else
316                         lno = fm->lno;
317                 coff = fm->cno;
318         }
319
320         btype = BUSY_ON;
321         for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) {
322                 if (cnt-- == 0) {
323                         if (INTERRUPTED(sp))
324                                 break;
325                         if (LF_ISSET(SEARCH_MSG)) {
326                                 search_busy(sp, btype);
327                                 btype = BUSY_UPDATE;
328                         }
329                         cnt = INTERRUPT_CHECK;
330                 }
331                 if ((wrapped && lno < fm->lno) || lno == 0) {
332                         if (wrapped) {
333                                 if (LF_ISSET(SEARCH_MSG))
334                                         search_msg(sp, S_NOTFOUND);
335                                 break;
336                         }
337                         if (!O_ISSET(sp, O_WRAPSCAN)) {
338                                 if (LF_ISSET(SEARCH_MSG))
339                                         search_msg(sp, S_SOF);
340                                 break;
341                         }
342                         if (db_last(sp, &lno))
343                                 break;
344                         if (lno == 0) {
345                                 if (LF_ISSET(SEARCH_MSG))
346                                         search_msg(sp, S_EMPTY);
347                                 break;
348                         }
349                         ++lno;
350                         wrapped = 1;
351                         continue;
352                 }
353
354                 if (db_get(sp, lno, 0, &l, &len))
355                         break;
356
357                 /* Set the termination. */
358                 match[0].rm_so = 0;
359                 match[0].rm_eo = len;
360
361 #if defined(DEBUG) && 0
362                 TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo);
363 #endif
364                 /* Search the line. */
365                 eval = regexec(&sp->re_c, l, 1, match,
366                     (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND);
367                 if (eval == REG_NOMATCH)
368                         continue;
369                 if (eval != 0) {
370                         if (LF_ISSET(SEARCH_MSG))
371                                 re_error(sp, eval, &sp->re_c);
372                         else
373                                 (void)sp->gp->scr_bell(sp);
374                         break;
375                 }
376
377                 /* Check for a match starting past the cursor. */
378                 if (coff != 0 && match[0].rm_so >= coff)
379                         continue;
380
381                 /* Warn if the search wrapped. */
382                 if (wrapped && LF_ISSET(SEARCH_WMSG))
383                         search_msg(sp, S_WRAP);
384
385 #if defined(DEBUG) && 0
386                 TRACE(sp, "B found: %qu to %qu\n",
387                     match[0].rm_so, match[0].rm_eo);
388 #endif
389                 /*
390                  * We now have the first match on the line.  Step through the
391                  * line character by character until find the last acceptable
392                  * match.  This is painful, we need a better interface to regex
393                  * to make this work.
394                  */
395                 for (;;) {
396                         last = match[0].rm_so++;
397                         if (match[0].rm_so >= len)
398                                 break;
399                         match[0].rm_eo = len;
400                         eval = regexec(&sp->re_c, l, 1, match,
401                             (match[0].rm_so == 0 ? 0 : REG_NOTBOL) |
402                             REG_STARTEND);
403                         if (eval == REG_NOMATCH)
404                                 break;
405                         if (eval != 0) {
406                                 if (LF_ISSET(SEARCH_MSG))
407                                         re_error(sp, eval, &sp->re_c);
408                                 else
409                                         (void)sp->gp->scr_bell(sp);
410                                 goto err;
411                         }
412                         if (coff && match[0].rm_so >= coff)
413                                 break;
414                 }
415                 rm->lno = lno;
416
417                 /* See comment in f_search(). */
418                 if (!LF_ISSET(SEARCH_EOL) && last >= len)
419                         rm->cno = len != 0 ? len - 1 : 0;
420                 else
421                         rm->cno = last;
422                 rval = 0;
423                 break;
424         }
425
426 err:    if (LF_ISSET(SEARCH_MSG))
427                 search_busy(sp, BUSY_OFF);
428         return (rval);
429 }
430
431 /*
432  * search_msg --
433  *      Display one of the search messages.
434  */
435 static void
436 search_msg(SCR *sp, smsg_t msg)
437 {
438         switch (msg) {
439         case S_EMPTY:
440                 msgq(sp, M_ERR, "072|File empty; nothing to search");
441                 break;
442         case S_EOF:
443                 msgq(sp, M_ERR,
444                     "073|Reached end-of-file without finding the pattern");
445                 break;
446         case S_NOPREV:
447                 msgq(sp, M_ERR, "074|No previous search pattern");
448                 break;
449         case S_NOTFOUND:
450                 msgq(sp, M_ERR, "075|Pattern not found");
451                 break;
452         case S_SOF:
453                 msgq(sp, M_ERR,
454                     "076|Reached top-of-file without finding the pattern");
455                 break;
456         case S_WRAP:
457                 msgq(sp, M_ERR, "077|Search wrapped");
458                 break;
459         default:
460                 abort();
461         }
462 }
463
464 /*
465  * search_busy --
466  *      Put up the busy searching message.
467  *
468  * PUBLIC: void search_busy(SCR *, busy_t);
469  */
470 void
471 search_busy(SCR *sp, busy_t btype)
472 {
473         sp->gp->scr_busy(sp, "078|Searching...", btype);
474 }