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