Commit | Line | Data |
---|---|---|
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 | |
19 | History *hist; /* history cookie */ | |
20 | EditLine *el; /* editline cookie */ | |
f7280c66 | 21 | int displayhist; |
dc06984c MT |
22 | static FILE *el_in, *el_out; |
23 | ||
f7280c66 | 24 | STATIC 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 | */ | |
30 | histedit() { | |
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 { | |
65 | bad: | |
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 | ||
95 | sethistsize() { | |
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 | */ | |
112 | histcmd(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 | ||
323 | STATIC char * | |
324 | fc_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 | ||
347 | not_fcnumber(s) | |
348 | char *s; | |
349 | { | |
350 | if (*s == '-') | |
351 | s++; | |
352 | return (!is_number(s)); | |
353 | } | |
354 | ||
355 | str_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 | } |