1 /*        $NetBSD: search.c,v 1.52 2024/06/30 16:26:30 christos Exp $ */
2 
3 /*-
4  * Copyright (c) 1992, 1993
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Christos Zoulas of Cornell University.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include "config.h"
36 #if !defined(lint) && !defined(SCCSID)
37 #if 0
38 static char sccsid[] = "@(#)search.c    8.1 (Berkeley) 6/4/93";
39 #else
40 __RCSID("$NetBSD: search.c,v 1.52 2024/06/30 16:26:30 christos Exp $");
41 #endif
42 #endif /* not lint && not SCCSID */
43 
44 /*
45  * search.c: History and character search functions
46  */
47 #include <stdlib.h>
48 #include <string.h>
49 #if defined(REGEX)
50 #include <regex.h>
51 #elif defined(REGEXP)
52 #include <regexp.h>
53 #endif
54 
55 #include "el.h"
56 #include "common.h"
57 #include "fcns.h"
58 
59 /*
60  * Adjust cursor in vi mode to include the character under it
61  */
62 #define   EL_CURSOR(el) \
63     ((el)->el_line.cursor + (((el)->el_map.type == MAP_VI) && \
64                                   ((el)->el_map.current == (el)->el_map.alt)))
65 
66 /* search_init():
67  *        Initialize the search stuff
68  */
69 libedit_private int
search_init(EditLine * el)70 search_init(EditLine *el)
71 {
72 
73           el->el_search.patbuf = el_calloc(EL_BUFSIZ,
74               sizeof(*el->el_search.patbuf));
75           if (el->el_search.patbuf == NULL)
76                     return -1;
77           el->el_search.patbuf[0] = L'\0';
78           el->el_search.patlen = 0;
79           el->el_search.patdir = -1;
80           el->el_search.chacha = L'\0';
81           el->el_search.chadir = CHAR_FWD;
82           el->el_search.chatflg = 0;
83           return 0;
84 }
85 
86 
87 /* search_end():
88  *        Initialize the search stuff
89  */
90 libedit_private void
search_end(EditLine * el)91 search_end(EditLine *el)
92 {
93 
94           el_free(el->el_search.patbuf);
95           el->el_search.patbuf = NULL;
96 }
97 
98 
99 #ifdef REGEXP
100 /* regerror():
101  *        Handle regular expression errors
102  */
103 void
104 /*ARGSUSED*/
regerror(const char * msg)105 regerror(const char *msg)
106 {
107 }
108 #endif
109 
110 
111 /* el_match():
112  *        Return if string matches pattern
113  */
114 libedit_private int
el_match(const wchar_t * str,const wchar_t * pat)115 el_match(const wchar_t *str, const wchar_t *pat)
116 {
117           static ct_buffer_t conv;
118 #if defined (REGEX)
119           regex_t re;
120           int rv;
121 #elif defined (REGEXP)
122           regexp *rp;
123           int rv;
124 #else
125           extern char         *re_comp(const char *);
126           extern int           re_exec(const char *);
127 #endif
128 
129           if (wcsstr(str, pat) != 0)
130                     return 1;
131 
132 #if defined(REGEX)
133           if (regcomp(&re, ct_encode_string(pat, &conv), 0) == 0) {
134                     rv = regexec(&re, ct_encode_string(str, &conv), (size_t)0, NULL,
135                         0) == 0;
136                     regfree(&re);
137           } else {
138                     rv = 0;
139           }
140           return rv;
141 #elif defined(REGEXP)
142           if ((re = regcomp(ct_encode_string(pat, &conv))) != NULL) {
143                     rv = regexec(re, ct_encode_string(str, &conv));
144                     el_free(re);
145           } else {
146                     rv = 0;
147           }
148           return rv;
149 #else
150           if (re_comp(ct_encode_string(pat, &conv)) != NULL)
151                     return 0;
152           else
153                     return re_exec(ct_encode_string(str, &conv)) == 1;
154 #endif
155 }
156 
157 
158 /* c_hmatch():
159  *         return True if the pattern matches the prefix
160  */
161 libedit_private int
c_hmatch(EditLine * el,const wchar_t * str)162 c_hmatch(EditLine *el, const wchar_t *str)
163 {
164 #ifdef SDEBUG
165           (void) fprintf(el->el_errfile, "match `%ls' with `%ls'\n",
166               el->el_search.patbuf, str);
167 #endif /* SDEBUG */
168 
169           return el_match(str, el->el_search.patbuf);
170 }
171 
172 
173 /* c_setpat():
174  *        Set the history seatch pattern
175  */
176 libedit_private void
c_setpat(EditLine * el)177 c_setpat(EditLine *el)
178 {
179           if (el->el_state.lastcmd != ED_SEARCH_PREV_HISTORY &&
180               el->el_state.lastcmd != ED_SEARCH_NEXT_HISTORY) {
181                     el->el_search.patlen =
182                         (size_t)(EL_CURSOR(el) - el->el_line.buffer);
183                     if (el->el_search.patlen >= EL_BUFSIZ)
184                               el->el_search.patlen = EL_BUFSIZ - 1;
185                     (void) wcsncpy(el->el_search.patbuf, el->el_line.buffer,
186                         el->el_search.patlen);
187                     el->el_search.patbuf[el->el_search.patlen] = '\0';
188           }
189 #ifdef SDEBUG
190           (void) fprintf(el->el_errfile, "\neventno = %d\n",
191               el->el_history.eventno);
192           (void) fprintf(el->el_errfile, "patlen = %ld\n", el->el_search.patlen);
193           (void) fprintf(el->el_errfile, "patbuf = \"%ls\"\n",
194               el->el_search.patbuf);
195           (void) fprintf(el->el_errfile, "cursor %ld lastchar %ld\n",
196               EL_CURSOR(el) - el->el_line.buffer,
197               el->el_line.lastchar - el->el_line.buffer);
198 #endif
199 }
200 
201 
202 /* ce_inc_search():
203  *        Emacs incremental search
204  */
205 libedit_private el_action_t
ce_inc_search(EditLine * el,int dir)206 ce_inc_search(EditLine *el, int dir)
207 {
208           static const wchar_t STRfwd[] = L"fwd", STRbck[] = L"bck";
209           static wchar_t pchar = L':';  /* ':' = normal, '?' = failed */
210           static wchar_t endcmd[2] = {'\0', '\0'};
211           wchar_t *ocursor = el->el_line.cursor, oldpchar = pchar, ch;
212           const wchar_t *cp;
213 
214           el_action_t ret = CC_NORM;
215 
216           int ohisteventno = el->el_history.eventno;
217           size_t oldpatlen = el->el_search.patlen;
218           int newdir = dir;
219           int done, redo;
220 
221           if (el->el_line.lastchar + sizeof(STRfwd) /
222               sizeof(*el->el_line.lastchar) + 2 +
223               el->el_search.patlen >= el->el_line.limit)
224                     return CC_ERROR;
225 
226           for (;;) {
227 
228                     if (el->el_search.patlen == 0) {        /* first round */
229                               pchar = ':';
230 #ifdef ANCHOR
231 #define   LEN       2
232                               el->el_search.patbuf[el->el_search.patlen++] = '.';
233                               el->el_search.patbuf[el->el_search.patlen++] = '*';
234 #else
235 #define   LEN       0
236 #endif
237                     }
238                     done = redo = 0;
239                     *el->el_line.lastchar++ = '\n';
240                     for (cp = (newdir == ED_SEARCH_PREV_HISTORY) ? STRbck : STRfwd;
241                         *cp; *el->el_line.lastchar++ = *cp++)
242                               continue;
243                     *el->el_line.lastchar++ = pchar;
244                     for (cp = &el->el_search.patbuf[LEN];
245                         cp < &el->el_search.patbuf[el->el_search.patlen];
246                         *el->el_line.lastchar++ = *cp++)
247                               continue;
248                     *el->el_line.lastchar = '\0';
249                     re_refresh(el);
250 
251                     if (el_wgetc(el, &ch) != 1)
252                               return ed_end_of_file(el, 0);
253 
254                     switch (el->el_map.current[(unsigned char) ch]) {
255                     case ED_INSERT:
256                     case ED_DIGIT:
257                               if (el->el_search.patlen >= EL_BUFSIZ - LEN)
258                                         terminal_beep(el);
259                               else {
260                                         el->el_search.patbuf[el->el_search.patlen++] =
261                                             ch;
262                                         *el->el_line.lastchar++ = ch;
263                                         *el->el_line.lastchar = '\0';
264                                         re_refresh(el);
265                               }
266                               break;
267 
268                     case EM_INC_SEARCH_NEXT:
269                               newdir = ED_SEARCH_NEXT_HISTORY;
270                               redo++;
271                               break;
272 
273                     case EM_INC_SEARCH_PREV:
274                               newdir = ED_SEARCH_PREV_HISTORY;
275                               redo++;
276                               break;
277 
278                     case EM_DELETE_PREV_CHAR:
279                     case ED_DELETE_PREV_CHAR:
280                               if (el->el_search.patlen > LEN)
281                                         done++;
282                               else
283                                         terminal_beep(el);
284                               break;
285 
286                     default:
287                               switch (ch) {
288                               case 0007:          /* ^G: Abort */
289                                         ret = CC_ERROR;
290                                         done++;
291                                         break;
292 
293                               case 0027:          /* ^W: Append word */
294                               /* No can do if globbing characters in pattern */
295                                         for (cp = &el->el_search.patbuf[LEN];; cp++)
296                                             if (cp >= &el->el_search.patbuf[
297                                                   el->el_search.patlen]) {
298                                                   if (el->el_line.cursor ==
299                                                       el->el_line.buffer)
300                                                             break;
301                                                   el->el_line.cursor +=
302                                                       el->el_search.patlen - LEN - 1;
303                                                   cp = c__next_word(el->el_line.cursor,
304                                                       el->el_line.lastchar, 1,
305                                                       ce__isword);
306                                                   while (el->el_line.cursor < cp &&
307                                                       *el->el_line.cursor != '\n') {
308                                                             if (el->el_search.patlen >=
309                                                                 EL_BUFSIZ - LEN) {
310                                                                       terminal_beep(el);
311                                                                       break;
312                                                             }
313                                                             el->el_search.patbuf[el->el_search.patlen++] =
314                                                                 *el->el_line.cursor;
315                                                             *el->el_line.lastchar++ =
316                                                                 *el->el_line.cursor++;
317                                                   }
318                                                   el->el_line.cursor = ocursor;
319                                                   *el->el_line.lastchar = '\0';
320                                                   re_refresh(el);
321                                                   break;
322                                             } else if (isglob(*cp)) {
323                                                       terminal_beep(el);
324                                                       break;
325                                             }
326                                         break;
327 
328                               default:  /* Terminate and execute cmd */
329                                         endcmd[0] = ch;
330                                         el_wpush(el, endcmd);
331                                         /* FALLTHROUGH */
332 
333                               case 0033:          /* ESC: Terminate */
334                                         ret = CC_REFRESH;
335                                         done++;
336                                         break;
337                               }
338                               break;
339                     }
340 
341                     while (el->el_line.lastchar > el->el_line.buffer &&
342                         *el->el_line.lastchar != '\n')
343                               *el->el_line.lastchar-- = '\0';
344                     *el->el_line.lastchar = '\0';
345 
346                     if (!done) {
347 
348                               /* Can't search if unmatched '[' */
349                               for (cp = &el->el_search.patbuf[el->el_search.patlen-1],
350                                   ch = L']';
351                                   cp >= &el->el_search.patbuf[LEN];
352                                   cp--)
353                                         if (*cp == '[' || *cp == ']') {
354                                                   ch = *cp;
355                                                   break;
356                                         }
357                               if (el->el_search.patlen > LEN && ch != L'[') {
358                                         if (redo && newdir == dir) {
359                                                   if (pchar == '?') { /* wrap around */
360                                                             el->el_history.eventno =
361                                                                 newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff;
362                                                             if (hist_get(el) == CC_ERROR)
363                                                                       /* el->el_history.event
364                                                                        * no was fixed by
365                                                                        * first call */
366                                                                       (void) hist_get(el);
367                                                             el->el_line.cursor = newdir ==
368                                                                 ED_SEARCH_PREV_HISTORY ?
369                                                                 el->el_line.lastchar :
370                                                                 el->el_line.buffer;
371                                                   } else
372                                                             el->el_line.cursor +=
373                                                                 newdir ==
374                                                                 ED_SEARCH_PREV_HISTORY ?
375                                                                 -1 : 1;
376                                         }
377 #ifdef ANCHOR
378                                         el->el_search.patbuf[el->el_search.patlen++] =
379                                             '.';
380                                         el->el_search.patbuf[el->el_search.patlen++] =
381                                             '*';
382 #endif
383                                         el->el_search.patbuf[el->el_search.patlen] =
384                                             '\0';
385                                         if (el->el_line.cursor < el->el_line.buffer ||
386                                             el->el_line.cursor > el->el_line.lastchar ||
387                                             (ret = ce_search_line(el, newdir))
388                                             == CC_ERROR) {
389                                                   /* avoid c_setpat */
390                                                   el->el_state.lastcmd =
391                                                       (el_action_t) newdir;
392                                                   ret = (el_action_t)
393                                                       (newdir == ED_SEARCH_PREV_HISTORY ?
394                                                       ed_search_prev_history(el, 0) :
395                                                       ed_search_next_history(el, 0));
396                                                   if (ret != CC_ERROR) {
397                                                             el->el_line.cursor = newdir ==
398                                                                 ED_SEARCH_PREV_HISTORY ?
399                                                                 el->el_line.lastchar :
400                                                                 el->el_line.buffer;
401                                                             (void) ce_search_line(el,
402                                                                 newdir);
403                                                   }
404                                         }
405                                         el->el_search.patlen -= LEN;
406                                         el->el_search.patbuf[el->el_search.patlen] =
407                                             '\0';
408                                         if (ret == CC_ERROR) {
409                                                   terminal_beep(el);
410                                                   if (el->el_history.eventno !=
411                                                       ohisteventno) {
412                                                             el->el_history.eventno =
413                                                                 ohisteventno;
414                                                             if (hist_get(el) == CC_ERROR)
415                                                                       return CC_ERROR;
416                                                   }
417                                                   el->el_line.cursor = ocursor;
418                                                   pchar = '?';
419                                         } else {
420                                                   pchar = ':';
421                                         }
422                               }
423                               ret = ce_inc_search(el, newdir);
424 
425                               if (ret == CC_ERROR && pchar == '?' && oldpchar == ':')
426                                         /*
427                                          * break abort of failed search at last
428                                          * non-failed
429                                          */
430                                         ret = CC_NORM;
431 
432                     }
433                     if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) {
434                               /* restore on normal return or error exit */
435                               pchar = oldpchar;
436                               el->el_search.patlen = oldpatlen;
437                               if (el->el_history.eventno != ohisteventno) {
438                                         el->el_history.eventno = ohisteventno;
439                                         if (hist_get(el) == CC_ERROR)
440                                                   return CC_ERROR;
441                               }
442                               el->el_line.cursor = ocursor;
443                               if (ret == CC_ERROR)
444                                         re_refresh(el);
445                     }
446                     if (done || ret != CC_NORM)
447                               return ret;
448           }
449 }
450 
451 
452 /* cv_search():
453  *        Vi search.
454  */
455 libedit_private el_action_t
cv_search(EditLine * el,int dir)456 cv_search(EditLine *el, int dir)
457 {
458           wchar_t ch;
459           wchar_t tmpbuf[EL_BUFSIZ];
460           ssize_t tmplen;
461 
462 #ifdef ANCHOR
463           tmpbuf[0] = '.';
464           tmpbuf[1] = '*';
465 #endif
466           tmplen = LEN;
467 
468           el->el_search.patdir = dir;
469 
470           tmplen = c_gets(el, &tmpbuf[LEN],
471                     dir == ED_SEARCH_PREV_HISTORY ? L"\n/" : L"\n?" );
472           if (tmplen == -1)
473                     return CC_REFRESH;
474 
475           tmplen += LEN;
476           ch = tmpbuf[tmplen];
477           tmpbuf[tmplen] = '\0';
478 
479           if (tmplen == LEN) {
480                     /*
481                      * Use the old pattern, but wild-card it.
482                      */
483                     if (el->el_search.patlen == 0) {
484                               re_refresh(el);
485                               return CC_ERROR;
486                     }
487 #ifdef ANCHOR
488                     if (el->el_search.patbuf[0] != '.' &&
489                         el->el_search.patbuf[0] != '*') {
490                               (void) wcsncpy(tmpbuf, el->el_search.patbuf,
491                                   sizeof(tmpbuf) / sizeof(*tmpbuf) - 1);
492                               el->el_search.patbuf[0] = '.';
493                               el->el_search.patbuf[1] = '*';
494                               (void) wcsncpy(&el->el_search.patbuf[2], tmpbuf,
495                                   EL_BUFSIZ - 3);
496                               el->el_search.patlen++;
497                               el->el_search.patbuf[el->el_search.patlen++] = '.';
498                               el->el_search.patbuf[el->el_search.patlen++] = '*';
499                               el->el_search.patbuf[el->el_search.patlen] = '\0';
500                     }
501 #endif
502           } else {
503 #ifdef ANCHOR
504                     tmpbuf[tmplen++] = '.';
505                     tmpbuf[tmplen++] = '*';
506 #endif
507                     tmpbuf[tmplen] = '\0';
508                     (void) wcsncpy(el->el_search.patbuf, tmpbuf, EL_BUFSIZ - 1);
509                     el->el_search.patlen = (size_t)tmplen;
510           }
511           el->el_state.lastcmd = (el_action_t) dir;         /* avoid c_setpat */
512           el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer;
513           if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) :
514               ed_search_next_history(el, 0)) == CC_ERROR) {
515                     re_refresh(el);
516                     return CC_ERROR;
517           }
518           if (ch == 0033) {
519                     re_refresh(el);
520                     return ed_newline(el, 0);
521           }
522           return CC_REFRESH;
523 }
524 
525 
526 /* ce_search_line():
527  *        Look for a pattern inside a line
528  */
529 libedit_private el_action_t
ce_search_line(EditLine * el,int dir)530 ce_search_line(EditLine *el, int dir)
531 {
532           wchar_t *cp = el->el_line.cursor;
533           wchar_t *pattern = el->el_search.patbuf;
534           wchar_t oc, *ocp;
535 #ifdef ANCHOR
536           ocp = &pattern[1];
537           oc = *ocp;
538           *ocp = '^';
539 #else
540           ocp = pattern;
541           oc = *ocp;
542 #endif
543 
544           if (dir == ED_SEARCH_PREV_HISTORY) {
545                     for (; cp >= el->el_line.buffer; cp--) {
546                               if (el_match(cp, ocp)) {
547                                         *ocp = oc;
548                                         el->el_line.cursor = cp;
549                                         return CC_NORM;
550                               }
551                     }
552                     *ocp = oc;
553                     return CC_ERROR;
554           } else {
555                     for (; *cp != '\0' && cp < el->el_line.limit; cp++) {
556                               if (el_match(cp, ocp)) {
557                                         *ocp = oc;
558                                         el->el_line.cursor = cp;
559                                         return CC_NORM;
560                               }
561                     }
562                     *ocp = oc;
563                     return CC_ERROR;
564           }
565 }
566 
567 
568 /* cv_repeat_srch():
569  *        Vi repeat search
570  */
571 libedit_private el_action_t
cv_repeat_srch(EditLine * el,wint_t c)572 cv_repeat_srch(EditLine *el, wint_t c)
573 {
574 
575 #ifdef SDEBUG
576           static ct_buffer_t conv;
577           (void) fprintf(el->el_errfile, "dir %d patlen %ld patbuf %s\n",
578               c, el->el_search.patlen, ct_encode_string(el->el_search.patbuf, &conv));
579 #endif
580 
581           el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */
582           el->el_line.lastchar = el->el_line.buffer;
583 
584           switch (c) {
585           case ED_SEARCH_NEXT_HISTORY:
586                     return ed_search_next_history(el, 0);
587           case ED_SEARCH_PREV_HISTORY:
588                     return ed_search_prev_history(el, 0);
589           default:
590                     return CC_ERROR;
591           }
592 }
593 
594 
595 /* cv_csearch():
596  *        Vi character search
597  */
598 libedit_private el_action_t
cv_csearch(EditLine * el,int direction,wint_t ch,int count,int tflag)599 cv_csearch(EditLine *el, int direction, wint_t ch, int count, int tflag)
600 {
601           wchar_t *cp;
602 
603           if (ch == 0)
604                     return CC_ERROR;
605 
606           if (ch == (wint_t)-1) {
607                     wchar_t c;
608                     if (el_wgetc(el, &c) != 1)
609                               return ed_end_of_file(el, 0);
610                     ch = c;
611           }
612 
613           /* Save for ';' and ',' commands */
614           el->el_search.chacha = ch;
615           el->el_search.chadir = direction;
616           el->el_search.chatflg = (char)tflag;
617 
618           cp = el->el_line.cursor;
619           while (count--) {
620                     if ((wint_t)*cp == ch)
621                               cp += direction;
622                     for (;;cp += direction) {
623                               if (cp >= el->el_line.lastchar)
624                                         return CC_ERROR;
625                               if (cp < el->el_line.buffer)
626                                         return CC_ERROR;
627                               if ((wint_t)*cp == ch)
628                                         break;
629                     }
630           }
631 
632           if (tflag)
633                     cp -= direction;
634 
635           el->el_line.cursor = cp;
636 
637           if (el->el_chared.c_vcmd.action != NOP) {
638                     if (direction > 0)
639                               el->el_line.cursor++;
640                     cv_delfini(el);
641                     return CC_REFRESH;
642           }
643           return CC_CURSOR;
644 }
645