]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libedit/search.c
libedit: vendor import libedit 2021-03-28
[FreeBSD/FreeBSD.git] / contrib / libedit / search.c
1 /*      $NetBSD: search.c,v 1.51 2020/03/30 06:56:38 ryo 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.51 2020/03/30 06:56:38 ryo 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
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
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*/
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
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
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
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
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                                         el->el_line.cursor +=
299                                             el->el_search.patlen - LEN - 1;
300                                         cp = c__next_word(el->el_line.cursor,
301                                             el->el_line.lastchar, 1,
302                                             ce__isword);
303                                         while (el->el_line.cursor < cp &&
304                                             *el->el_line.cursor != '\n') {
305                                                 if (el->el_search.patlen >=
306                                                     EL_BUFSIZ - LEN) {
307                                                         terminal_beep(el);
308                                                         break;
309                                                 }
310                                                 el->el_search.patbuf[el->el_search.patlen++] =
311                                                     *el->el_line.cursor;
312                                                 *el->el_line.lastchar++ =
313                                                     *el->el_line.cursor++;
314                                         }
315                                         el->el_line.cursor = ocursor;
316                                         *el->el_line.lastchar = '\0';
317                                         re_refresh(el);
318                                         break;
319                                     } else if (isglob(*cp)) {
320                                             terminal_beep(el);
321                                             break;
322                                     }
323                                 break;
324
325                         default:        /* Terminate and execute cmd */
326                                 endcmd[0] = ch;
327                                 el_wpush(el, endcmd);
328                                 /* FALLTHROUGH */
329
330                         case 0033:      /* ESC: Terminate */
331                                 ret = CC_REFRESH;
332                                 done++;
333                                 break;
334                         }
335                         break;
336                 }
337
338                 while (el->el_line.lastchar > el->el_line.buffer &&
339                     *el->el_line.lastchar != '\n')
340                         *el->el_line.lastchar-- = '\0';
341                 *el->el_line.lastchar = '\0';
342
343                 if (!done) {
344
345                         /* Can't search if unmatched '[' */
346                         for (cp = &el->el_search.patbuf[el->el_search.patlen-1],
347                             ch = L']';
348                             cp >= &el->el_search.patbuf[LEN];
349                             cp--)
350                                 if (*cp == '[' || *cp == ']') {
351                                         ch = *cp;
352                                         break;
353                                 }
354                         if (el->el_search.patlen > LEN && ch != L'[') {
355                                 if (redo && newdir == dir) {
356                                         if (pchar == '?') { /* wrap around */
357                                                 el->el_history.eventno =
358                                                     newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff;
359                                                 if (hist_get(el) == CC_ERROR)
360                                                         /* el->el_history.event
361                                                          * no was fixed by
362                                                          * first call */
363                                                         (void) hist_get(el);
364                                                 el->el_line.cursor = newdir ==
365                                                     ED_SEARCH_PREV_HISTORY ?
366                                                     el->el_line.lastchar :
367                                                     el->el_line.buffer;
368                                         } else
369                                                 el->el_line.cursor +=
370                                                     newdir ==
371                                                     ED_SEARCH_PREV_HISTORY ?
372                                                     -1 : 1;
373                                 }
374 #ifdef ANCHOR
375                                 el->el_search.patbuf[el->el_search.patlen++] =
376                                     '.';
377                                 el->el_search.patbuf[el->el_search.patlen++] =
378                                     '*';
379 #endif
380                                 el->el_search.patbuf[el->el_search.patlen] =
381                                     '\0';
382                                 if (el->el_line.cursor < el->el_line.buffer ||
383                                     el->el_line.cursor > el->el_line.lastchar ||
384                                     (ret = ce_search_line(el, newdir))
385                                     == CC_ERROR) {
386                                         /* avoid c_setpat */
387                                         el->el_state.lastcmd =
388                                             (el_action_t) newdir;
389                                         ret = (el_action_t)
390                                             (newdir == ED_SEARCH_PREV_HISTORY ?
391                                             ed_search_prev_history(el, 0) :
392                                             ed_search_next_history(el, 0));
393                                         if (ret != CC_ERROR) {
394                                                 el->el_line.cursor = newdir ==
395                                                     ED_SEARCH_PREV_HISTORY ?
396                                                     el->el_line.lastchar :
397                                                     el->el_line.buffer;
398                                                 (void) ce_search_line(el,
399                                                     newdir);
400                                         }
401                                 }
402                                 el->el_search.patlen -= LEN;
403                                 el->el_search.patbuf[el->el_search.patlen] =
404                                     '\0';
405                                 if (ret == CC_ERROR) {
406                                         terminal_beep(el);
407                                         if (el->el_history.eventno !=
408                                             ohisteventno) {
409                                                 el->el_history.eventno =
410                                                     ohisteventno;
411                                                 if (hist_get(el) == CC_ERROR)
412                                                         return CC_ERROR;
413                                         }
414                                         el->el_line.cursor = ocursor;
415                                         pchar = '?';
416                                 } else {
417                                         pchar = ':';
418                                 }
419                         }
420                         ret = ce_inc_search(el, newdir);
421
422                         if (ret == CC_ERROR && pchar == '?' && oldpchar == ':')
423                                 /*
424                                  * break abort of failed search at last
425                                  * non-failed
426                                  */
427                                 ret = CC_NORM;
428
429                 }
430                 if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) {
431                         /* restore on normal return or error exit */
432                         pchar = oldpchar;
433                         el->el_search.patlen = oldpatlen;
434                         if (el->el_history.eventno != ohisteventno) {
435                                 el->el_history.eventno = ohisteventno;
436                                 if (hist_get(el) == CC_ERROR)
437                                         return CC_ERROR;
438                         }
439                         el->el_line.cursor = ocursor;
440                         if (ret == CC_ERROR)
441                                 re_refresh(el);
442                 }
443                 if (done || ret != CC_NORM)
444                         return ret;
445         }
446 }
447
448
449 /* cv_search():
450  *      Vi search.
451  */
452 libedit_private el_action_t
453 cv_search(EditLine *el, int dir)
454 {
455         wchar_t ch;
456         wchar_t tmpbuf[EL_BUFSIZ];
457         ssize_t tmplen;
458
459 #ifdef ANCHOR
460         tmpbuf[0] = '.';
461         tmpbuf[1] = '*';
462 #endif
463         tmplen = LEN;
464
465         el->el_search.patdir = dir;
466
467         tmplen = c_gets(el, &tmpbuf[LEN],
468                 dir == ED_SEARCH_PREV_HISTORY ? L"\n/" : L"\n?" );
469         if (tmplen == -1)
470                 return CC_REFRESH;
471
472         tmplen += LEN;
473         ch = tmpbuf[tmplen];
474         tmpbuf[tmplen] = '\0';
475
476         if (tmplen == LEN) {
477                 /*
478                  * Use the old pattern, but wild-card it.
479                  */
480                 if (el->el_search.patlen == 0) {
481                         re_refresh(el);
482                         return CC_ERROR;
483                 }
484 #ifdef ANCHOR
485                 if (el->el_search.patbuf[0] != '.' &&
486                     el->el_search.patbuf[0] != '*') {
487                         (void) wcsncpy(tmpbuf, el->el_search.patbuf,
488                             sizeof(tmpbuf) / sizeof(*tmpbuf) - 1);
489                         el->el_search.patbuf[0] = '.';
490                         el->el_search.patbuf[1] = '*';
491                         (void) wcsncpy(&el->el_search.patbuf[2], tmpbuf,
492                             EL_BUFSIZ - 3);
493                         el->el_search.patlen++;
494                         el->el_search.patbuf[el->el_search.patlen++] = '.';
495                         el->el_search.patbuf[el->el_search.patlen++] = '*';
496                         el->el_search.patbuf[el->el_search.patlen] = '\0';
497                 }
498 #endif
499         } else {
500 #ifdef ANCHOR
501                 tmpbuf[tmplen++] = '.';
502                 tmpbuf[tmplen++] = '*';
503 #endif
504                 tmpbuf[tmplen] = '\0';
505                 (void) wcsncpy(el->el_search.patbuf, tmpbuf, EL_BUFSIZ - 1);
506                 el->el_search.patlen = (size_t)tmplen;
507         }
508         el->el_state.lastcmd = (el_action_t) dir;       /* avoid c_setpat */
509         el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer;
510         if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) :
511             ed_search_next_history(el, 0)) == CC_ERROR) {
512                 re_refresh(el);
513                 return CC_ERROR;
514         }
515         if (ch == 0033) {
516                 re_refresh(el);
517                 return ed_newline(el, 0);
518         }
519         return CC_REFRESH;
520 }
521
522
523 /* ce_search_line():
524  *      Look for a pattern inside a line
525  */
526 libedit_private el_action_t
527 ce_search_line(EditLine *el, int dir)
528 {
529         wchar_t *cp = el->el_line.cursor;
530         wchar_t *pattern = el->el_search.patbuf;
531         wchar_t oc, *ocp;
532 #ifdef ANCHOR
533         ocp = &pattern[1];
534         oc = *ocp;
535         *ocp = '^';
536 #else
537         ocp = pattern;
538         oc = *ocp;
539 #endif
540
541         if (dir == ED_SEARCH_PREV_HISTORY) {
542                 for (; cp >= el->el_line.buffer; cp--) {
543                         if (el_match(cp, ocp)) {
544                                 *ocp = oc;
545                                 el->el_line.cursor = cp;
546                                 return CC_NORM;
547                         }
548                 }
549                 *ocp = oc;
550                 return CC_ERROR;
551         } else {
552                 for (; *cp != '\0' && cp < el->el_line.limit; cp++) {
553                         if (el_match(cp, ocp)) {
554                                 *ocp = oc;
555                                 el->el_line.cursor = cp;
556                                 return CC_NORM;
557                         }
558                 }
559                 *ocp = oc;
560                 return CC_ERROR;
561         }
562 }
563
564
565 /* cv_repeat_srch():
566  *      Vi repeat search
567  */
568 libedit_private el_action_t
569 cv_repeat_srch(EditLine *el, wint_t c)
570 {
571
572 #ifdef SDEBUG
573         static ct_buffer_t conv;
574         (void) fprintf(el->el_errfile, "dir %d patlen %ld patbuf %s\n",
575             c, el->el_search.patlen, ct_encode_string(el->el_search.patbuf, &conv));
576 #endif
577
578         el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */
579         el->el_line.lastchar = el->el_line.buffer;
580
581         switch (c) {
582         case ED_SEARCH_NEXT_HISTORY:
583                 return ed_search_next_history(el, 0);
584         case ED_SEARCH_PREV_HISTORY:
585                 return ed_search_prev_history(el, 0);
586         default:
587                 return CC_ERROR;
588         }
589 }
590
591
592 /* cv_csearch():
593  *      Vi character search
594  */
595 libedit_private el_action_t
596 cv_csearch(EditLine *el, int direction, wint_t ch, int count, int tflag)
597 {
598         wchar_t *cp;
599
600         if (ch == 0)
601                 return CC_ERROR;
602
603         if (ch == (wint_t)-1) {
604                 wchar_t c;
605                 if (el_wgetc(el, &c) != 1)
606                         return ed_end_of_file(el, 0);
607                 ch = c;
608         }
609
610         /* Save for ';' and ',' commands */
611         el->el_search.chacha = ch;
612         el->el_search.chadir = direction;
613         el->el_search.chatflg = (char)tflag;
614
615         cp = el->el_line.cursor;
616         while (count--) {
617                 if ((wint_t)*cp == ch)
618                         cp += direction;
619                 for (;;cp += direction) {
620                         if (cp >= el->el_line.lastchar)
621                                 return CC_ERROR;
622                         if (cp < el->el_line.buffer)
623                                 return CC_ERROR;
624                         if ((wint_t)*cp == ch)
625                                 break;
626                 }
627         }
628
629         if (tflag)
630                 cp -= direction;
631
632         el->el_line.cursor = cp;
633
634         if (el->el_chared.c_vcmd.action != NOP) {
635                 if (direction > 0)
636                         el->el_line.cursor++;
637                 cv_delfini(el);
638                 return CC_REFRESH;
639         }
640         return CC_CURSOR;
641 }