xref: /dragonfly/bin/sh/histedit.c (revision 3e3895bf4584c1562faf4533cbd97026ee6a8dcf)
1 /*-
2  * Copyright (c) 1993
3  *        The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Kenneth Almquist.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)histedit.c  8.2 (Berkeley) 5/4/95";
36 #endif
37 #endif /* not lint */
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD: head/bin/sh/histedit.c 360139 2020-04-21 00:37:55Z bdrewery $");
40 
41 #include <sys/param.h>
42 #include <limits.h>
43 #include <paths.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 /*
48  * Editline and history functions (and glue).
49  */
50 #include "shell.h"
51 #include "parser.h"
52 #include "var.h"
53 #include "options.h"
54 #include "main.h"
55 #include "output.h"
56 #include "mystring.h"
57 #include "builtins.h"
58 #ifndef NO_HISTORY
59 #include "myhistedit.h"
60 #include "error.h"
61 #include "eval.h"
62 #include "memalloc.h"
63 
64 #define MAXHISTLOOPS          4         /* max recursions through fc */
65 #define DEFEDITOR   "ed"      /* default editor *should* be $EDITOR */
66 
67 History *hist;      /* history cookie */
68 EditLine *el;       /* editline cookie */
69 int displayhist;
70 static FILE *el_in, *el_out;
71 
72 static char *fc_replace(const char *, char *, char *);
73 static int not_fcnumber(const char *);
74 static int str_to_event(const char *, int);
75 
76 /*
77  * Set history and editing status.  Called whenever the status may
78  * have changed (figures out what to do).
79  */
80 void
histedit(void)81 histedit(void)
82 {
83 
84 #define editing (Eflag || Vflag)
85 
86           if (iflag) {
87                     if (!hist) {
88                               /*
89                                * turn history on
90                                */
91                               INTOFF;
92                               hist = history_init();
93                               INTON;
94 
95                               if (hist != NULL)
96                                         sethistsize(histsizeval());
97                               else
98                                         out2fmt_flush("sh: can't initialize history\n");
99                     }
100                     if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
101                               /*
102                                * turn editing on
103                                */
104                               char *term;
105 
106                               INTOFF;
107                               if (el_in == NULL)
108                                         el_in = fdopen(0, "r");
109                               if (el_out == NULL)
110                                         el_out = fdopen(2, "w");
111                               if (el_in == NULL || el_out == NULL)
112                                         goto bad;
113                               term = lookupvar("TERM");
114                               if (term)
115                                         setenv("TERM", term, 1);
116                               else
117                                         unsetenv("TERM");
118                               el = el_init(arg0, el_in, el_out, el_out);
119                               if (el != NULL) {
120                                         if (hist)
121                                                   el_set(el, EL_HIST, history, hist);
122                                         el_set(el, EL_PROMPT, getprompt);
123                                         el_set(el, EL_ADDFN, "sh-complete",
124                                             "Filename completion",
125                                             _el_fn_complete);
126                               } else {
127 bad:
128                                         out2fmt_flush("sh: can't initialize editing\n");
129                               }
130                               INTON;
131                     } else if (!editing && el) {
132                               INTOFF;
133                               el_end(el);
134                               el = NULL;
135                               INTON;
136                     }
137                     if (el) {
138                               if (Vflag)
139                                         el_set(el, EL_EDITOR, "vi");
140                               else if (Eflag)
141                                         el_set(el, EL_EDITOR, "emacs");
142                               el_set(el, EL_BIND, "^I", "sh-complete", NULL);
143                               el_source(el, NULL);
144                     }
145           } else {
146                     INTOFF;
147                     if (el) { /* no editing if not interactive */
148                               el_end(el);
149                               el = NULL;
150                     }
151                     if (hist) {
152                               history_end(hist);
153                               hist = NULL;
154                     }
155                     INTON;
156           }
157 }
158 
159 
160 void
sethistsize(const char * hs)161 sethistsize(const char *hs)
162 {
163           int histsize;
164           HistEvent he;
165 
166           if (hist != NULL) {
167                     if (hs == NULL || !is_number(hs))
168                               histsize = 100;
169                     else
170                               histsize = atoi(hs);
171                     history(hist, &he, H_SETSIZE, histsize);
172                     history(hist, &he, H_SETUNIQUE, 1);
173           }
174 }
175 
176 void
setterm(const char * term)177 setterm(const char *term)
178 {
179           if (rootshell && el != NULL && term != NULL)
180                     el_set(el, EL_TERMINAL, term);
181 }
182 
183 int
histcmd(int argc,char ** argv __unused)184 histcmd(int argc, char **argv __unused)
185 {
186           int ch;
187           const char *editor = NULL;
188           HistEvent he;
189           int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
190           int i, retval;
191           const char *firststr, *laststr;
192           int first, last, direction;
193           char *pat = NULL, *repl = NULL;
194           static int active = 0;
195           struct jmploc jmploc;
196           struct jmploc *savehandler;
197           char editfilestr[PATH_MAX];
198           char *volatile editfile;
199           FILE *efp = NULL;
200           int oldhistnum;
201 
202           if (hist == NULL)
203                     error("history not active");
204 
205           if (argc == 1)
206                     error("missing history argument");
207 
208           while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0')
209                     switch ((char)ch) {
210                     case 'e':
211                               editor = shoptarg;
212                               break;
213                     case 'l':
214                               lflg = 1;
215                               break;
216                     case 'n':
217                               nflg = 1;
218                               break;
219                     case 'r':
220                               rflg = 1;
221                               break;
222                     case 's':
223                               sflg = 1;
224                               break;
225                     }
226 
227           savehandler = handler;
228           /*
229            * If executing...
230            */
231           if (lflg == 0 || editor || sflg) {
232                     lflg = 0; /* ignore */
233                     editfile = NULL;
234                     /*
235                      * Catch interrupts to reset active counter and
236                      * cleanup temp files.
237                      */
238                     if (setjmp(jmploc.loc)) {
239                               active = 0;
240                               if (editfile)
241                                         unlink(editfile);
242                               handler = savehandler;
243                               longjmp(handler->loc, 1);
244                     }
245                     handler = &jmploc;
246                     if (++active > MAXHISTLOOPS) {
247                               active = 0;
248                               displayhist = 0;
249                               error("called recursively too many times");
250                     }
251                     /*
252                      * Set editor.
253                      */
254                     if (sflg == 0) {
255                               if (editor == NULL &&
256                                   (editor = bltinlookup("FCEDIT", 1)) == NULL &&
257                                   (editor = bltinlookup("EDITOR", 1)) == NULL)
258                                         editor = DEFEDITOR;
259                               if (editor[0] == '-' && editor[1] == '\0') {
260                                         sflg = 1; /* no edit */
261                                         editor = NULL;
262                               }
263                     }
264           }
265 
266           /*
267            * If executing, parse [old=new] now
268            */
269           if (lflg == 0 && *argptr != NULL &&
270                ((repl = strchr(*argptr, '=')) != NULL)) {
271                     pat = *argptr;
272                     *repl++ = '\0';
273                     argptr++;
274           }
275           /*
276            * determine [first] and [last]
277            */
278           if (*argptr == NULL) {
279                     firststr = lflg ? "-16" : "-1";
280                     laststr = "-1";
281           } else if (argptr[1] == NULL) {
282                     firststr = argptr[0];
283                     laststr = lflg ? "-1" : argptr[0];
284           } else if (argptr[2] == NULL) {
285                     firststr = argptr[0];
286                     laststr = argptr[1];
287           } else
288                     error("too many arguments");
289           /*
290            * Turn into event numbers.
291            */
292           first = str_to_event(firststr, 0);
293           last = str_to_event(laststr, 1);
294 
295           if (rflg) {
296                     i = last;
297                     last = first;
298                     first = i;
299           }
300           /*
301            * XXX - this should not depend on the event numbers
302            * always increasing.  Add sequence numbers or offset
303            * to the history element in next (diskbased) release.
304            */
305           direction = first < last ? H_PREV : H_NEXT;
306 
307           /*
308            * If editing, grab a temp file.
309            */
310           if (editor) {
311                     int fd;
312                     INTOFF;             /* easier */
313                     sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
314                     if ((fd = mkstemp(editfilestr)) < 0)
315                               error("can't create temporary file %s", editfile);
316                     editfile = editfilestr;
317                     if ((efp = fdopen(fd, "w")) == NULL) {
318                               close(fd);
319                               error("Out of space");
320                     }
321           }
322 
323           /*
324            * Loop through selected history events.  If listing or executing,
325            * do it now.  Otherwise, put into temp file and call the editor
326            * after.
327            *
328            * The history interface needs rethinking, as the following
329            * convolutions will demonstrate.
330            */
331           history(hist, &he, H_FIRST);
332           retval = history(hist, &he, H_NEXT_EVENT, first);
333           for (;retval != -1; retval = history(hist, &he, direction)) {
334                     if (lflg) {
335                               if (!nflg)
336                                         out1fmt("%5d ", he.num);
337                               out1str(he.str);
338                     } else {
339                               const char *s = pat ?
340                                  fc_replace(he.str, pat, repl) : he.str;
341 
342                               if (sflg) {
343                                         if (displayhist) {
344                                                   out2str(s);
345                                                   flushout(out2);
346                                         }
347                                         evalstring(s, 0);
348                                         if (displayhist && hist) {
349                                                   /*
350                                                    *  XXX what about recursive and
351                                                    *  relative histnums.
352                                                    */
353                                                   oldhistnum = he.num;
354                                                   history(hist, &he, H_ENTER, s);
355                                                   /*
356                                                    * XXX H_ENTER moves the internal
357                                                    * cursor, set it back to the current
358                                                    * entry.
359                                                    */
360                                                   history(hist, &he,
361                                                       H_NEXT_EVENT, oldhistnum);
362                                         }
363                               } else
364                                         fputs(s, efp);
365                     }
366                     /*
367                      * At end?  (if we were to lose last, we'd sure be
368                      * messed up).
369                      */
370                     if (he.num == last)
371                               break;
372           }
373           if (editor) {
374                     char *editcmd;
375 
376                     fclose(efp);
377                     INTON;
378                     editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
379                     sprintf(editcmd, "%s %s", editor, editfile);
380                     evalstring(editcmd, 0);       /* XXX - should use no JC command */
381                     readcmdfile(editfile);        /* XXX - should read back - quick tst */
382                     unlink(editfile);
383           }
384 
385           if (lflg == 0 && active > 0)
386                     --active;
387           if (displayhist)
388                     displayhist = 0;
389           handler = savehandler;
390           return 0;
391 }
392 
393 static char *
fc_replace(const char * s,char * p,char * r)394 fc_replace(const char *s, char *p, char *r)
395 {
396           char *dest;
397           int plen = strlen(p);
398 
399           STARTSTACKSTR(dest);
400           while (*s) {
401                     if (*s == *p && strncmp(s, p, plen) == 0) {
402                               STPUTS(r, dest);
403                               s += plen;
404                               *p = '\0';          /* so no more matches */
405                     } else
406                               STPUTC(*s++, dest);
407           }
408           STPUTC('\0', dest);
409           dest = grabstackstr(dest);
410 
411           return (dest);
412 }
413 
414 static int
not_fcnumber(const char * s)415 not_fcnumber(const char *s)
416 {
417           if (s == NULL)
418                     return (0);
419           if (*s == '-')
420                     s++;
421           return (!is_number(s));
422 }
423 
424 static int
str_to_event(const char * str,int last)425 str_to_event(const char *str, int last)
426 {
427           HistEvent he;
428           const char *s = str;
429           int relative = 0;
430           int i, retval;
431 
432           retval = history(hist, &he, H_FIRST);
433           switch (*s) {
434           case '-':
435                     relative = 1;
436                     /*FALLTHROUGH*/
437           case '+':
438                     s++;
439           }
440           if (is_number(s)) {
441                     i = atoi(s);
442                     if (relative) {
443                               while (retval != -1 && i--) {
444                                         retval = history(hist, &he, H_NEXT);
445                               }
446                               if (retval == -1)
447                                         retval = history(hist, &he, H_LAST);
448                     } else {
449                               retval = history(hist, &he, H_NEXT_EVENT, i);
450                               if (retval == -1) {
451                                         /*
452                                          * the notion of first and last is
453                                          * backwards to that of the history package
454                                          */
455                                         retval = history(hist, &he, last ? H_FIRST : H_LAST);
456                               }
457                     }
458                     if (retval == -1)
459                               error("history number %s not found (internal error)",
460                                      str);
461           } else {
462                     /*
463                      * pattern
464                      */
465                     retval = history(hist, &he, H_PREV_STR, str);
466                     if (retval == -1)
467                               error("history pattern not found: %s", str);
468           }
469           return (he.num);
470 }
471 
472 int
bindcmd(int argc,char ** argv)473 bindcmd(int argc, char **argv)
474 {
475           int ret;
476           FILE *old;
477           FILE *out;
478 
479           if (el == NULL)
480                     error("line editing is disabled");
481 
482           INTOFF;
483 
484           out = out1fp();
485           if (out == NULL)
486                     error("Out of space");
487 
488           el_get(el, EL_GETFP, 1, &old);
489           el_set(el, EL_SETFP, 1, out);
490 
491           ret = el_parse(el, argc, __DECONST(const char **, argv));
492 
493           el_set(el, EL_SETFP, 1, old);
494 
495           fclose(out);
496 
497           INTON;
498 
499           return ret;
500 }
501 
502 #else
503 #include "error.h"
504 
505 int
histcmd(int argc __unused,char ** argv __unused)506 histcmd(int argc __unused, char **argv __unused)
507 {
508 
509           error("not compiled with history support");
510           /*NOTREACHED*/
511           return (0);
512 }
513 
514 int
bindcmd(int argc __unused,char ** argv __unused)515 bindcmd(int argc __unused, char **argv __unused)
516 {
517 
518           error("not compiled with line editing support");
519           return (0);
520 }
521 #endif
522