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