xref: /original-bsd/bin/sh/histedit.c (revision b3c06cab)
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  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 static char sccsid[] = "@(#)histedit.c	8.2 (Berkeley) 05/04/95";
13 #endif /* not lint */
14 
15 #include <sys/param.h>
16 #include <paths.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 /*
21  * Editline and history functions (and glue).
22  */
23 #include "shell.h"
24 #include "parser.h"
25 #include "var.h"
26 #include "options.h"
27 #include "main.h"
28 #include "output.h"
29 #include "mystring.h"
30 #ifndef NO_HISTORY
31 #include "myhistedit.h"
32 #endif
33 #include "error.h"
34 #include "eval.h"
35 #include "memalloc.h"
36 
37 #define MAXHISTLOOPS	4	/* max recursions through fc */
38 #define DEFEDITOR	"ed"	/* default editor *should* be $EDITOR */
39 
40 History *hist;	/* history cookie */
41 EditLine *el;	/* editline cookie */
42 int displayhist;
43 static FILE *el_in, *el_out;
44 
45 STATIC char *fc_replace __P((const char *, char *, char *));
46 
47 /*
48  * Set history and editing status.  Called whenever the status may
49  * have changed (figures out what to do).
50  */
51 void
52 histedit()
53 {
54 
55 #define editing (Eflag || Vflag)
56 
57 	if (iflag) {
58 		if (!hist) {
59 			/*
60 			 * turn history on
61 			 */
62 			INTOFF;
63 			hist = history_init();
64 			INTON;
65 
66 			if (hist != NULL)
67 				sethistsize();
68 			else
69 				out2str("sh: can't initialize history\n");
70 		}
71 		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
72 			/*
73 			 * turn editing on
74 			 */
75 			INTOFF;
76 			if (el_in == NULL)
77 				el_in = fdopen(0, "r");
78 			if (el_out == NULL)
79 				el_out = fdopen(2, "w");
80 			if (el_in == NULL || el_out == NULL)
81 				goto bad;
82 			el = el_init(arg0, el_in, el_out);
83 			if (el != NULL) {
84 				if (hist)
85 					el_set(el, EL_HIST, history, hist);
86 				el_set(el, EL_PROMPT, getprompt);
87 			} else {
88 bad:
89 				out2str("sh: can't initialize editing\n");
90 			}
91 			INTON;
92 		} else if (!editing && el) {
93 			INTOFF;
94 			el_end(el);
95 			el = NULL;
96 			INTON;
97 		}
98 		if (el) {
99 			if (Vflag)
100 				el_set(el, EL_EDITOR, "vi");
101 			else if (Eflag)
102 				el_set(el, EL_EDITOR, "emacs");
103 		}
104 	} else {
105 		INTOFF;
106 		if (el) {	/* no editing if not interactive */
107 			el_end(el);
108 			el = NULL;
109 		}
110 		if (hist) {
111 			history_end(hist);
112 			hist = NULL;
113 		}
114 		INTON;
115 	}
116 }
117 
118 
119 void
120 sethistsize()
121 {
122 	char *cp;
123 	int histsize;
124 
125 	if (hist != NULL) {
126 		cp = lookupvar("HISTSIZE");
127 		if (cp == NULL || *cp == '\0' ||
128 		   (histsize = atoi(cp)) < 0)
129 			histsize = 100;
130 		history(hist, H_EVENT, histsize);
131 	}
132 }
133 
134 /*
135  *  This command is provided since POSIX decided to standardize
136  *  the Korn shell fc command.  Oh well...
137  */
138 int
139 histcmd(argc, argv)
140 	int argc;
141 	char **argv;
142 {
143 	extern char *optarg;
144 	extern int optind, optopt, optreset;
145 	int ch;
146 	char *editor = NULL;
147 	const HistEvent *he;
148 	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
149 	int i;
150 	char *firststr, *laststr;
151 	int first, last, direction;
152 	char *pat = NULL, *repl;	/* ksh "fc old=new" crap */
153 	static int active = 0;
154 	struct jmploc jmploc;
155 	struct jmploc *volatile savehandler;
156 	char editfile[MAXPATHLEN + 1];
157 	FILE *efp;
158 #ifdef __GNUC__
159 	/* Avoid longjmp clobbering */
160 	(void) &editor;
161 	(void) &lflg;
162 	(void) &nflg;
163 	(void) &rflg;
164 	(void) &sflg;
165 	(void) &firststr;
166 	(void) &laststr;
167 	(void) &pat;
168 	(void) &repl;
169 	(void) &efp;
170 	(void) &argc;
171 	(void) &argv;
172 #endif
173 
174 	if (hist == NULL)
175 		error("history not active");
176 
177 	if (argc == 1)
178 		error("missing history argument");
179 
180 	optreset = 1; optind = 1; /* initialize getopt */
181 	while (not_fcnumber(argv[optind]) &&
182 	      (ch = getopt(argc, argv, ":e:lnrs")) != EOF)
183 		switch ((char)ch) {
184 		case 'e':
185 			editor = optarg;
186 			break;
187 		case 'l':
188 			lflg = 1;
189 			break;
190 		case 'n':
191 			nflg = 1;
192 			break;
193 		case 'r':
194 			rflg = 1;
195 			break;
196 		case 's':
197 			sflg = 1;
198 			break;
199 		case ':':
200 			error("option -%c expects argument", optopt);
201 		case '?':
202 		default:
203 			error("unknown option: -%c", optopt);
204 		}
205 	argc -= optind, argv += optind;
206 
207 	/*
208 	 * If executing...
209 	 */
210 	if (lflg == 0 || editor || sflg) {
211 		lflg = 0;	/* ignore */
212 		editfile[0] = '\0';
213 		/*
214 		 * Catch interrupts to reset active counter and
215 		 * cleanup temp files.
216 		 */
217 		if (setjmp(jmploc.loc)) {
218 			active = 0;
219 			if (*editfile)
220 				unlink(editfile);
221 			handler = savehandler;
222 			longjmp(handler->loc, 1);
223 		}
224 		savehandler = handler;
225 		handler = &jmploc;
226 		if (++active > MAXHISTLOOPS) {
227 			active = 0;
228 			displayhist = 0;
229 			error("called recursively too many times");
230 		}
231 		/*
232 		 * Set editor.
233 		 */
234 		if (sflg == 0) {
235 			if (editor == NULL &&
236 			    (editor = bltinlookup("FCEDIT", 1)) == NULL &&
237 			    (editor = bltinlookup("EDITOR", 1)) == NULL)
238 				editor = DEFEDITOR;
239 			if (editor[0] == '-' && editor[1] == '\0') {
240 				sflg = 1;	/* no edit */
241 				editor = NULL;
242 			}
243 		}
244 	}
245 
246 	/*
247 	 * If executing, parse [old=new] now
248 	 */
249 	if (lflg == 0 && argc > 0 &&
250 	     ((repl = strchr(argv[0], '=')) != NULL)) {
251 		pat = argv[0];
252 		*repl++ = '\0';
253 		argc--, argv++;
254 	}
255 	/*
256 	 * determine [first] and [last]
257 	 */
258 	switch (argc) {
259 	case 0:
260 		firststr = lflg ? "-16" : "-1";
261 		laststr = "-1";
262 		break;
263 	case 1:
264 		firststr = argv[0];
265 		laststr = lflg ? "-1" : argv[0];
266 		break;
267 	case 2:
268 		firststr = argv[0];
269 		laststr = argv[1];
270 		break;
271 	default:
272 		error("too many args");
273 	}
274 	/*
275 	 * Turn into event numbers.
276 	 */
277 	first = str_to_event(firststr, 0);
278 	last = str_to_event(laststr, 1);
279 
280 	if (rflg) {
281 		i = last;
282 		last = first;
283 		first = i;
284 	}
285 	/*
286 	 * XXX - this should not depend on the event numbers
287 	 * always increasing.  Add sequence numbers or offset
288 	 * to the history element in next (diskbased) release.
289 	 */
290 	direction = first < last ? H_PREV : H_NEXT;
291 
292 	/*
293 	 * If editing, grab a temp file.
294 	 */
295 	if (editor) {
296 		int fd;
297 		INTOFF;		/* easier */
298 		sprintf(editfile, "%s/_shXXXXXX", _PATH_TMP);
299 		if ((fd = mkstemp(editfile)) < 0)
300 			error("can't create temporary file %s", editfile);
301 		if ((efp = fdopen(fd, "w")) == NULL) {
302 			close(fd);
303 			error("can't allocate stdio buffer for temp\n");
304 		}
305 	}
306 
307 	/*
308 	 * Loop through selected history events.  If listing or executing,
309 	 * do it now.  Otherwise, put into temp file and call the editor
310 	 * after.
311 	 *
312 	 * The history interface needs rethinking, as the following
313 	 * convolutions will demonstrate.
314 	 */
315 	history(hist, H_FIRST);
316 	he = history(hist, H_NEXT_EVENT, first);
317 	for (;he != NULL; he = history(hist, direction)) {
318 		if (lflg) {
319 			if (!nflg)
320 				out1fmt("%5d ", he->num);
321 			out1str(he->str);
322 		} else {
323 			char *s = pat ?
324 			   fc_replace(he->str, pat, repl) : (char *)he->str;
325 
326 			if (sflg) {
327 				if (displayhist) {
328 					out2str(s);
329 				}
330 				evalstring(s);
331 				if (displayhist && hist) {
332 					/*
333 					 *  XXX what about recursive and
334 					 *  relative histnums.
335 					 */
336 					history(hist, H_ENTER, s);
337 				}
338 			} else
339 				fputs(s, efp);
340 		}
341 		/*
342 		 * At end?  (if we were to loose last, we'd sure be
343 		 * messed up).
344 		 */
345 		if (he->num == last)
346 			break;
347 	}
348 	if (editor) {
349 		char *editcmd;
350 
351 		fclose(efp);
352 		editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
353 		sprintf(editcmd, "%s %s", editor, editfile);
354 		evalstring(editcmd);	/* XXX - should use no JC command */
355 		INTON;
356 		readcmdfile(editfile);	/* XXX - should read back - quick tst */
357 		unlink(editfile);
358 	}
359 
360 	if (lflg == 0 && active > 0)
361 		--active;
362 	if (displayhist)
363 		displayhist = 0;
364 	return 0;
365 }
366 
367 STATIC char *
368 fc_replace(s, p, r)
369 	const char *s;
370 	char *p, *r;
371 {
372 	char *dest;
373 	int plen = strlen(p);
374 
375 	STARTSTACKSTR(dest);
376 	while (*s) {
377 		if (*s == *p && strncmp(s, p, plen) == 0) {
378 			while (*r)
379 				STPUTC(*r++, dest);
380 			s += plen;
381 			*p = '\0';	/* so no more matches */
382 		} else
383 			STPUTC(*s++, dest);
384 	}
385 	STACKSTRNUL(dest);
386 	dest = grabstackstr(dest);
387 
388 	return (dest);
389 }
390 
391 int
392 not_fcnumber(s)
393         char *s;
394 {
395 	if (s == NULL)
396 		return 0;
397         if (*s == '-')
398                 s++;
399 	return (!is_number(s));
400 }
401 
402 int
403 str_to_event(str, last)
404 	char *str;
405 	int last;
406 {
407 	const HistEvent *he;
408 	char *s = str;
409 	int relative = 0;
410 	int i;
411 
412 	he = history(hist, H_FIRST);
413 	switch (*s) {
414 	case '-':
415 		relative = 1;
416 		/*FALLTHROUGH*/
417 	case '+':
418 		s++;
419 	}
420 	if (is_number(s)) {
421 		i = atoi(s);
422 		if (relative) {
423 			while (he != NULL && i--) {
424 				he = history(hist, H_NEXT);
425 			}
426 			if (he == NULL)
427 				he = history(hist, H_LAST);
428 		} else {
429 			he = history(hist, H_NEXT_EVENT, i);
430 			if (he == NULL) {
431 				/*
432 				 * the notion of first and last is
433 				 * backwards to that of the history package
434 				 */
435 				he = history(hist, last ? H_FIRST : H_LAST);
436 			}
437 		}
438 		if (he == NULL)
439 			error("history number %s not found (internal error)",
440 			       str);
441 	} else {
442 		/*
443 		 * pattern
444 		 */
445 		he = history(hist, H_PREV_STR, str);
446 		if (he == NULL)
447 			error("history pattern not found: %s", str);
448 	}
449 	return (he->num);
450 }
451