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