histedit.c revision 97731
1270096Strasz/*-
2270096Strasz * Copyright (c) 1993
3270096Strasz *	The Regents of the University of California.  All rights reserved.
4270096Strasz *
5270096Strasz * This code is derived from software contributed to Berkeley by
6270096Strasz * Kenneth Almquist.
7270096Strasz *
8270096Strasz * Redistribution and use in source and binary forms, with or without
9270096Strasz * modification, are permitted provided that the following conditions
10270096Strasz * are met:
11270096Strasz * 1. Redistributions of source code must retain the above copyright
12270096Strasz *    notice, this list of conditions and the following disclaimer.
13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright
14270096Strasz *    notice, this list of conditions and the following disclaimer in the
15270096Strasz *    documentation and/or other materials provided with the distribution.
16270096Strasz * 3. All advertising materials mentioning features or use of this software
17270096Strasz *    must display the following acknowledgement:
18270096Strasz *	This product includes software developed by the University of
19270096Strasz *	California, Berkeley and its contributors.
20270096Strasz * 4. Neither the name of the University nor the names of its contributors
21270096Strasz *    may be used to endorse or promote products derived from this software
22270096Strasz *    without specific prior written permission.
23270096Strasz *
24270096Strasz * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27270096Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34270096Strasz * SUCH DAMAGE.
35270096Strasz */
36270096Strasz
37270096Strasz#ifndef lint
38270096Strasz#if 0
39270096Straszstatic char sccsid[] = "@(#)histedit.c	8.2 (Berkeley) 5/4/95";
40270096Strasz#endif
41270096Straszstatic const char rcsid[] =
42270096Strasz  "$FreeBSD: head/bin/sh/histedit.c 97731 2002-06-02 08:34:09Z tjr $";
43270096Strasz#endif /* not lint */
44270096Strasz
45272403Strasz#include <sys/param.h>
46270096Strasz#include <limits.h>
47270096Strasz#include <paths.h>
48270096Strasz#include <stdio.h>
49270096Strasz#include <stdlib.h>
50270281Strasz#include <unistd.h>
51270096Strasz/*
52270096Strasz * Editline and history functions (and glue).
53270096Strasz */
54270096Strasz#include "shell.h"
55270402Strasz#include "parser.h"
56270402Strasz#include "var.h"
57270096Strasz#include "options.h"
58270096Strasz#include "main.h"
59270096Strasz#include "output.h"
60270096Strasz#include "mystring.h"
61270096Strasz#ifndef NO_HISTORY
62270096Strasz#include "myhistedit.h"
63270096Strasz#include "error.h"
64270096Strasz#include "eval.h"
65270096Strasz#include "memalloc.h"
66270096Strasz
67270096Strasz#define MAXHISTLOOPS	4	/* max recursions through fc */
68270096Strasz#define DEFEDITOR	"ed"	/* default editor *should* be $EDITOR */
69270096Strasz
70270096StraszHistory *hist;	/* history cookie */
71270096StraszEditLine *el;	/* editline cookie */
72270096Straszint displayhist;
73270096Straszstatic FILE *el_in, *el_out, *el_err;
74270096Strasz
75270096StraszSTATIC char *fc_replace(const char *, char *, char *);
76270096Strasz
77270096Strasz/*
78270096Strasz * Set history and editing status.  Called whenever the status may
79270096Strasz * have changed (figures out what to do).
80270096Strasz */
81270096Straszvoid
82270096Straszhistedit(void)
83270096Strasz{
84270096Strasz
85270096Strasz#define editing (Eflag || Vflag)
86270096Strasz
87270096Strasz	if (iflag) {
88270096Strasz		if (!hist) {
89270096Strasz			/*
90270096Strasz			 * turn history on
91270096Strasz			 */
92270096Strasz			INTOFF;
93270096Strasz			hist = history_init();
94270096Strasz			INTON;
95270096Strasz
96270096Strasz			if (hist != NULL)
97270096Strasz				sethistsize(histsizeval());
98270096Strasz			else
99270096Strasz				out2str("sh: can't initialize history\n");
100270096Strasz		}
101270096Strasz		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
102270096Strasz			/*
103270096Strasz			 * turn editing on
104270096Strasz			 */
105270096Strasz			INTOFF;
106270096Strasz			if (el_in == NULL)
107270096Strasz				el_in = fdopen(0, "r");
108270096Strasz			if (el_err == NULL)
109270096Strasz				el_err = fdopen(1, "w");
110270096Strasz			if (el_out == NULL)
111270096Strasz				el_out = fdopen(2, "w");
112270096Strasz			if (el_in == NULL || el_err == NULL || el_out == NULL)
113270096Strasz				goto bad;
114270096Strasz			el = el_init(arg0, el_in, el_out, el_err);
115270096Strasz			if (el != NULL) {
116270096Strasz				if (hist)
117270096Strasz					el_set(el, EL_HIST, history, hist);
118270096Strasz				el_set(el, EL_PROMPT, getprompt);
119270096Strasz			} else {
120270096Straszbad:
121270096Strasz				out2str("sh: can't initialize editing\n");
122270096Strasz			}
123270096Strasz			INTON;
124270096Strasz		} else if (!editing && el) {
125270096Strasz			INTOFF;
126270096Strasz			el_end(el);
127270096Strasz			el = NULL;
128270096Strasz			INTON;
129270096Strasz		}
130270096Strasz		if (el) {
131270096Strasz			if (Vflag)
132270096Strasz				el_set(el, EL_EDITOR, "vi");
133270096Strasz			else if (Eflag)
134270096Strasz				el_set(el, EL_EDITOR, "emacs");
135270096Strasz		}
136270096Strasz	} else {
137270096Strasz		INTOFF;
138270096Strasz		if (el) {	/* no editing if not interactive */
139270096Strasz			el_end(el);
140270096Strasz			el = NULL;
141270096Strasz		}
142270096Strasz		if (hist) {
143270096Strasz			history_end(hist);
144270096Strasz			hist = NULL;
145270096Strasz		}
146270096Strasz		INTON;
147270096Strasz	}
148270096Strasz}
149270096Strasz
150270096Strasz
151270096Straszvoid
152270096Straszsethistsize(hs)
153270096Strasz	const char *hs;
154270096Strasz{
155270402Strasz	int histsize;
156270096Strasz	HistEvent he;
157270096Strasz
158270096Strasz	if (hist != NULL) {
159270096Strasz		if (hs == NULL || *hs == '\0' ||
160270096Strasz		   (histsize = atoi(hs)) < 0)
161270096Strasz			histsize = 100;
162270096Strasz		history(hist, &he, H_EVENT, histsize);
163270096Strasz	}
164270096Strasz}
165270096Strasz
166270096Strasz/*
167270402Strasz *  This command is provided since POSIX decided to standardize
168270096Strasz *  the Korn shell fc command.  Oh well...
169270096Strasz */
170270096Straszint
171270096Straszhistcmd(int argc, char **argv)
172270096Strasz{
173270096Strasz	int ch;
174270096Strasz	char *editor = NULL;
175270096Strasz	HistEvent he;
176270096Strasz	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
177270096Strasz	int i, retval;
178270096Strasz	char *firststr, *laststr;
179270096Strasz	int first, last, direction;
180270096Strasz	char *pat = NULL, *repl;	/* ksh "fc old=new" crap */
181270096Strasz	static int active = 0;
182270096Strasz	struct jmploc jmploc;
183270096Strasz	struct jmploc *volatile savehandler;
184270096Strasz	char editfile[PATH_MAX];
185270096Strasz	FILE *efp;
186270096Strasz	int oldhistnum;
187270096Strasz#ifdef __GNUC__
188270096Strasz	/* Avoid longjmp clobbering */
189270096Strasz	(void) &editor;
190270096Strasz	(void) &lflg;
191270096Strasz	(void) &nflg;
192270096Strasz	(void) &rflg;
193270096Strasz	(void) &sflg;
194270096Strasz	(void) &firststr;
195270096Strasz	(void) &laststr;
196270096Strasz	(void) &pat;
197270096Strasz	(void) &repl;
198270096Strasz	(void) &efp;
199270096Strasz	(void) &argc;
200270096Strasz	(void) &argv;
201270207Strasz#endif
202270207Strasz
203270207Strasz	if (hist == NULL)
204270207Strasz		error("history not active");
205270207Strasz
206270207Strasz	if (argc == 1)
207270207Strasz		error("missing history argument");
208270207Strasz
209270207Strasz	optreset = 1; optind = 1; /* initialize getopt */
210270096Strasz	while (not_fcnumber(argv[optind]) &&
211270096Strasz	      (ch = getopt(argc, argv, ":e:lnrs")) != -1)
212270096Strasz		switch ((char)ch) {
213270096Strasz		case 'e':
214270096Strasz			editor = optarg;
215270096Strasz			break;
216270096Strasz		case 'l':
217270096Strasz			lflg = 1;
218270096Strasz			break;
219270096Strasz		case 'n':
220270096Strasz			nflg = 1;
221270096Strasz			break;
222270096Strasz		case 'r':
223270096Strasz			rflg = 1;
224270096Strasz			break;
225270096Strasz		case 's':
226270096Strasz			sflg = 1;
227270096Strasz			break;
228270096Strasz		case ':':
229270207Strasz			error("option -%c expects argument", optopt);
230270207Strasz		case '?':
231270207Strasz		default:
232270207Strasz			error("unknown option: -%c", optopt);
233270207Strasz		}
234270096Strasz	argc -= optind, argv += optind;
235270207Strasz
236270207Strasz	/*
237270096Strasz	 * If executing...
238270207Strasz	 */
239270096Strasz	if (lflg == 0 || editor || sflg) {
240270207Strasz		lflg = 0;	/* ignore */
241270096Strasz		editfile[0] = '\0';
242270096Strasz		/*
243270096Strasz		 * Catch interrupts to reset active counter and
244270096Strasz		 * cleanup temp files.
245270096Strasz		 */
246270096Strasz		if (setjmp(jmploc.loc)) {
247270096Strasz			active = 0;
248270096Strasz			if (*editfile)
249270096Strasz				unlink(editfile);
250270096Strasz			handler = savehandler;
251270096Strasz			longjmp(handler->loc, 1);
252270096Strasz		}
253270096Strasz		savehandler = handler;
254270096Strasz		handler = &jmploc;
255270096Strasz		if (++active > MAXHISTLOOPS) {
256270096Strasz			active = 0;
257270096Strasz			displayhist = 0;
258270096Strasz			error("called recursively too many times");
259270096Strasz		}
260270096Strasz		/*
261270096Strasz		 * Set editor.
262270096Strasz		 */
263270096Strasz		if (sflg == 0) {
264270096Strasz			if (editor == NULL &&
265270096Strasz			    (editor = bltinlookup("FCEDIT", 1)) == NULL &&
266270096Strasz			    (editor = bltinlookup("EDITOR", 1)) == NULL)
267270096Strasz				editor = DEFEDITOR;
268270096Strasz			if (editor[0] == '-' && editor[1] == '\0') {
269270096Strasz				sflg = 1;	/* no edit */
270270096Strasz				editor = NULL;
271270096Strasz			}
272270096Strasz		}
273270096Strasz	}
274270096Strasz
275270096Strasz	/*
276270096Strasz	 * If executing, parse [old=new] now
277270096Strasz	 */
278270096Strasz	if (lflg == 0 && argc > 0 &&
279270096Strasz	     ((repl = strchr(argv[0], '=')) != NULL)) {
280270096Strasz		pat = argv[0];
281270096Strasz		*repl++ = '\0';
282270096Strasz		argc--, argv++;
283270096Strasz	}
284270096Strasz	/*
285270096Strasz	 * determine [first] and [last]
286270096Strasz	 */
287270096Strasz	switch (argc) {
288270096Strasz	case 0:
289270096Strasz		firststr = lflg ? "-16" : "-1";
290270096Strasz		laststr = "-1";
291270096Strasz		break;
292270096Strasz	case 1:
293270096Strasz		firststr = argv[0];
294270096Strasz		laststr = lflg ? "-1" : argv[0];
295270096Strasz		break;
296270096Strasz	case 2:
297270096Strasz		firststr = argv[0];
298270096Strasz		laststr = argv[1];
299270096Strasz		break;
300270096Strasz	default:
301270096Strasz		error("too many args");
302270096Strasz	}
303270096Strasz	/*
304270096Strasz	 * Turn into event numbers.
305270096Strasz	 */
306270096Strasz	first = str_to_event(firststr, 0);
307270096Strasz	last = str_to_event(laststr, 1);
308270096Strasz
309270096Strasz	if (rflg) {
310270096Strasz		i = last;
311270096Strasz		last = first;
312270096Strasz		first = i;
313270096Strasz	}
314270096Strasz	/*
315270096Strasz	 * XXX - this should not depend on the event numbers
316270096Strasz	 * always increasing.  Add sequence numbers or offset
317270096Strasz	 * to the history element in next (diskbased) release.
318270096Strasz	 */
319270096Strasz	direction = first < last ? H_PREV : H_NEXT;
320270096Strasz
321270096Strasz	/*
322270096Strasz	 * If editing, grab a temp file.
323270096Strasz	 */
324270096Strasz	if (editor) {
325270096Strasz		int fd;
326270096Strasz		INTOFF;		/* easier */
327270096Strasz		sprintf(editfile, "%s/_shXXXXXX", _PATH_TMP);
328270096Strasz		if ((fd = mkstemp(editfile)) < 0)
329270096Strasz			error("can't create temporary file %s", editfile);
330270096Strasz		if ((efp = fdopen(fd, "w")) == NULL) {
331270096Strasz			close(fd);
332270096Strasz			error("can't allocate stdio buffer for temp");
333270096Strasz		}
334270096Strasz	}
335270096Strasz
336270096Strasz	/*
337270096Strasz	 * Loop through selected history events.  If listing or executing,
338270096Strasz	 * do it now.  Otherwise, put into temp file and call the editor
339270096Strasz	 * after.
340270096Strasz	 *
341270096Strasz	 * The history interface needs rethinking, as the following
342270096Strasz	 * convolutions will demonstrate.
343270096Strasz	 */
344270096Strasz	history(hist, &he, H_FIRST);
345270096Strasz	retval = history(hist, &he, H_NEXT_EVENT, first);
346270096Strasz	for (;retval != -1; retval = history(hist, &he, direction)) {
347270096Strasz		if (lflg) {
348270096Strasz			if (!nflg)
349270096Strasz				out1fmt("%5d ", he.num);
350270096Strasz			out1str(he.str);
351270096Strasz		} else {
352270096Strasz			char *s = pat ?
353270096Strasz			   fc_replace(he.str, pat, repl) : (char *)he.str;
354270096Strasz
355270096Strasz			if (sflg) {
356270096Strasz				if (displayhist) {
357270096Strasz					out2str(s);
358270096Strasz				}
359270096Strasz				evalstring(s);
360270096Strasz				if (displayhist && hist) {
361270096Strasz					/*
362270096Strasz					 *  XXX what about recursive and
363270096Strasz					 *  relative histnums.
364270096Strasz					 */
365270096Strasz					oldhistnum = he.num;
366270096Strasz					history(hist, &he, H_ENTER, s);
367270096Strasz					/*
368270096Strasz					 * XXX H_ENTER moves the internal
369270096Strasz					 * cursor, set it back to the current
370270096Strasz					 * entry.
371270096Strasz					 */
372270096Strasz					retval = history(hist, &he,
373270096Strasz					    H_NEXT_EVENT, oldhistnum);
374270096Strasz				}
375270096Strasz			} else
376270096Strasz				fputs(s, efp);
377270096Strasz		}
378270096Strasz		/*
379270096Strasz		 * At end?  (if we were to loose last, we'd sure be
380270096Strasz		 * messed up).
381270096Strasz		 */
382270096Strasz		if (he.num == last)
383270096Strasz			break;
384270096Strasz	}
385270096Strasz	if (editor) {
386270096Strasz		char *editcmd;
387270096Strasz
388270096Strasz		fclose(efp);
389270096Strasz		editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
390270096Strasz		sprintf(editcmd, "%s %s", editor, editfile);
391270096Strasz		evalstring(editcmd);	/* XXX - should use no JC command */
392270096Strasz		INTON;
393270096Strasz		readcmdfile(editfile);	/* XXX - should read back - quick tst */
394270096Strasz		unlink(editfile);
395270096Strasz	}
396270096Strasz
397270096Strasz	if (lflg == 0 && active > 0)
398270096Strasz		--active;
399270096Strasz	if (displayhist)
400270096Strasz		displayhist = 0;
401270096Strasz	return 0;
402270096Strasz}
403270096Strasz
404270096StraszSTATIC char *
405270096Straszfc_replace(const char *s, char *p, char *r)
406270096Strasz{
407270096Strasz	char *dest;
408270096Strasz	int plen = strlen(p);
409270096Strasz
410270096Strasz	STARTSTACKSTR(dest);
411270096Strasz	while (*s) {
412270096Strasz		if (*s == *p && strncmp(s, p, plen) == 0) {
413270096Strasz			while (*r)
414270096Strasz				STPUTC(*r++, dest);
415270096Strasz			s += plen;
416270096Strasz			*p = '\0';	/* so no more matches */
417270096Strasz		} else
418270096Strasz			STPUTC(*s++, dest);
419270096Strasz	}
420270096Strasz	STACKSTRNUL(dest);
421270096Strasz	dest = grabstackstr(dest);
422270096Strasz
423270096Strasz	return (dest);
424270096Strasz}
425270096Strasz
426270096Straszint
427270096Strasznot_fcnumber(char *s)
428270096Strasz{
429270096Strasz	if (s == NULL)
430270096Strasz		return (0);
431270096Strasz	if (*s == '-')
432270096Strasz		s++;
433270096Strasz	return (!is_number(s));
434270096Strasz}
435270096Strasz
436270096Straszint
437270096Straszstr_to_event(char *str, int last)
438270096Strasz{
439270096Strasz	HistEvent he;
440270096Strasz	char *s = str;
441270096Strasz	int relative = 0;
442270096Strasz	int i, retval;
443270096Strasz
444270096Strasz	retval = history(hist, &he, H_FIRST);
445270096Strasz	switch (*s) {
446270096Strasz	case '-':
447270096Strasz		relative = 1;
448270096Strasz		/*FALLTHROUGH*/
449270096Strasz	case '+':
450270096Strasz		s++;
451270096Strasz	}
452270096Strasz	if (is_number(s)) {
453270096Strasz		i = atoi(s);
454270096Strasz		if (relative) {
455270096Strasz			while (retval != -1 && i--) {
456270096Strasz				retval = history(hist, &he, H_NEXT);
457270096Strasz			}
458270096Strasz			if (retval == -1)
459270096Strasz				retval = history(hist, &he, H_LAST);
460270096Strasz		} else {
461270096Strasz			retval = history(hist, &he, H_NEXT_EVENT, i);
462270096Strasz			if (retval == -1) {
463270096Strasz				/*
464270096Strasz				 * the notion of first and last is
465270096Strasz				 * backwards to that of the history package
466270096Strasz				 */
467270096Strasz				retval = history(hist, &he, last ? H_FIRST : H_LAST);
468270096Strasz			}
469270096Strasz		}
470270096Strasz		if (retval == -1)
471270096Strasz			error("history number %s not found (internal error)",
472270096Strasz			       str);
473270096Strasz	} else {
474270096Strasz		/*
475270096Strasz		 * pattern
476270096Strasz		 */
477270096Strasz		retval = history(hist, &he, H_PREV_STR, str);
478270096Strasz		if (retval == -1)
479270096Strasz			error("history pattern not found: %s", str);
480270096Strasz	}
481270096Strasz	return (he.num);
482270096Strasz}
483270096Strasz#else
484270096Strasz#include "error.h"
485270096Strasz
486270096Straszint
487270096Straszhistcmd(int argc, char **argv)
488270096Strasz{
489270096Strasz
490270096Strasz	error("not compiled with history support");
491270096Strasz	/*NOTREACHED*/
492270096Strasz	return (0);
493270096Strasz}
494270096Strasz#endif
495270096Strasz