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