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