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"); | |
133 | ||
134 | optreset = 1; optind = 1; /* initialize getopt */ | |
135 | while (not_fcnumber(argv[optind]) && | |
136 | (ch = getopt(argc, argv, ":e:lnrs")) != EOF) | |
137 | switch ((char)ch) { | |
138 | case 'e': | |
139 | editor = optarg; | |
140 | break; | |
141 | case 'l': | |
142 | lflg = 1; | |
143 | break; | |
144 | case 'n': | |
145 | nflg = 1; | |
146 | break; | |
147 | case 'r': | |
148 | rflg = 1; | |
149 | break; | |
150 | case 's': | |
151 | sflg = 1; | |
152 | break; | |
153 | case ':': | |
154 | error("option -%c expects argument", optopt); | |
155 | case '?': | |
156 | default: | |
157 | error("unknown option: -%c", optopt); | |
158 | } | |
159 | argc -= optind, argv += optind; | |
160 | ||
161 | /* | |
162 | * If executing... | |
163 | */ | |
164 | if (lflg == 0 || editor || sflg) { | |
165 | lflg = 0; /* ignore */ | |
166 | editfile[0] = '\0'; | |
167 | /* | |
168 | * Catch interrupts to reset active counter and | |
169 | * cleanup temp files. | |
170 | */ | |
171 | if (setjmp(jmploc.loc)) { | |
172 | active = 0; | |
173 | if (*editfile) | |
174 | unlink(editfile); | |
175 | handler = savehandler; | |
176 | longjmp(handler->loc, 1); | |
177 | } | |
178 | savehandler = handler; | |
179 | handler = &jmploc; | |
180 | if (++active > MAXHISTLOOPS) { | |
181 | active = 0; | |
182 | displayhist = 0; | |
183 | error("called recursively too many times"); | |
184 | } | |
185 | /* | |
186 | * Set editor. | |
187 | */ | |
188 | if (sflg == 0) { | |
189 | if (editor == NULL && | |
190 | (editor = lookupvar("FCEDIT")) == NULL && | |
191 | (editor = lookupvar("EDITOR")) == NULL) | |
192 | editor = DEFEDITOR; | |
193 | if (editor[0] == '-' && editor[1] == '\0') { | |
194 | sflg = 1; /* no edit */ | |
195 | editor = NULL; | |
196 | } | |
197 | } | |
198 | } | |
199 | ||
200 | /* | |
201 | * If executing, parse [old=new] now | |
202 | */ | |
203 | if (lflg == 0 && argc > 0 && | |
204 | ((repl = strchr(argv[0], '=')) != NULL)) { | |
205 | pat = argv[0]; | |
206 | *repl++ = '\0'; | |
207 | argc--, argv++; | |
208 | } | |
209 | /* | |
210 | * determine [first] and [last] | |
211 | */ | |
212 | switch (argc) { | |
213 | case 0: | |
214 | firststr = lflg ? "-16" : "-1"; | |
215 | laststr = "-1"; | |
216 | break; | |
217 | case 1: | |
218 | firststr = argv[0]; | |
219 | laststr = lflg ? "-1" : argv[0]; | |
220 | break; | |
221 | case 2: | |
222 | firststr = argv[0]; | |
223 | laststr = argv[1]; | |
224 | break; | |
225 | default: | |
226 | error("too many args"); | |
227 | } | |
228 | /* | |
229 | * Turn into event numbers. | |
230 | */ | |
231 | first = str_to_event(firststr, 0); | |
232 | last = str_to_event(laststr, 1); | |
233 | ||
234 | if (rflg) { | |
235 | i = last; | |
236 | last = first; | |
237 | first = i; | |
238 | } | |
239 | /* | |
240 | * XXX - this should not depend on the event numbers | |
241 | * always increasing. Add sequence numbers or offset | |
242 | * to the history element in next (diskbased) release. | |
243 | */ | |
244 | direction = first < last ? H_PREV : H_NEXT; | |
245 | ||
246 | /* | |
247 | * If editing, grab a temp file. | |
248 | */ | |
249 | if (editor) { | |
250 | int fd; | |
251 | INTOFF; /* easier */ | |
252 | sprintf(editfile, "%s/_shXXXXXX", _PATH_TMP); | |
253 | if ((fd = mkstemp(editfile)) < 0) | |
254 | error("can't create temporary file %s", editfile); | |
255 | if ((efp = fdopen(fd, "w")) == NULL) { | |
256 | close(fd); | |
257 | error("can't allocate stdio buffer for temp\n"); | |
258 | } | |
259 | } | |
260 | ||
261 | /* | |
262 | * Loop through selected history events. If listing or executing, | |
263 | * do it now. Otherwise, put into temp file and call the editor | |
264 | * after. | |
265 | * | |
266 | * The history interface needs rethinking, as the following | |
267 | * convolutions will demonstrate. | |
268 | */ | |
269 | history(hist, H_FIRST); | |
270 | he = history(hist, H_NEXT_EVENT, first); | |
271 | for (;he != NULL; he = history(hist, direction)) { | |
272 | if (lflg) { | |
273 | if (!nflg) | |
274 | out1fmt("%5d ", he->num); | |
275 | out1str(he->str); | |
276 | } else { | |
277 | char *s = pat ? | |
278 | fc_replace(he->str, pat, repl) : (char *)he->str; | |
279 | ||
280 | if (sflg) { | |
281 | if (displayhist) { | |
282 | out2str(s); | |
283 | } | |
284 | evalstring(s); | |
285 | if (displayhist && hist) { | |
286 | /* | |
287 | * XXX what about recursive and | |
288 | * relative histnums. | |
289 | */ | |
290 | history(hist, H_ENTER, s); | |
291 | } | |
292 | } else | |
293 | fputs(s, efp); | |
294 | } | |
295 | /* | |
296 | * At end? (if we were to loose last, we'd sure be | |
297 | * messed up). | |
298 | */ | |
299 | if (he->num == last) | |
300 | break; | |
301 | } | |
302 | if (editor) { | |
303 | char *editcmd; | |
304 | ||
305 | fclose(efp); | |
306 | editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); | |
307 | sprintf(editcmd, "%s %s", editor, editfile); | |
308 | evalstring(editcmd); /* XXX - should use no JC command */ | |
309 | INTON; | |
310 | readcmdfile(editfile); /* XXX - should read back - quick tst */ | |
311 | unlink(editfile); | |
312 | } | |
313 | ||
314 | if (lflg == 0 && active > 0) | |
315 | --active; | |
316 | if (displayhist) | |
317 | displayhist = 0; | |
318 | } | |
319 | ||
320 | STATIC char * | |
321 | fc_replace(s, p, r) | |
322 | const char *s; | |
323 | char *p, *r; | |
324 | { | |
325 | char *dest; | |
326 | int plen = strlen(p); | |
327 | ||
328 | STARTSTACKSTR(dest); | |
329 | while (*s) { | |
330 | if (*s == *p && strncmp(s, p, plen) == 0) { | |
331 | while (*r) | |
332 | STPUTC(*r++, dest); | |
333 | s += plen; | |
334 | *p = '\0'; /* so no more matches */ | |
335 | } else | |
336 | STPUTC(*s++, dest); | |
337 | } | |
338 | STACKSTRNUL(dest); | |
339 | dest = grabstackstr(dest); | |
340 | ||
341 | return (dest); | |
342 | } | |
343 | ||
344 | not_fcnumber(s) | |
345 | char *s; | |
346 | { | |
347 | if (*s == '-') | |
348 | s++; | |
349 | return (!is_number(s)); | |
350 | } | |
351 | ||
352 | str_to_event(str, last) | |
353 | char *str; | |
354 | int last; | |
355 | { | |
356 | const HistEvent *he; | |
357 | char *s = str; | |
358 | int relative = 0; | |
359 | int i, j; | |
360 | ||
361 | he = history(hist, H_FIRST); | |
362 | switch (*s) { | |
363 | case '-': | |
364 | relative = 1; | |
365 | /*FALLTHROUGH*/ | |
366 | case '+': | |
367 | s++; | |
368 | } | |
369 | if (is_number(s)) { | |
370 | i = atoi(s); | |
371 | if (relative) { | |
372 | while (he != NULL && i--) { | |
373 | he = history(hist, H_NEXT); | |
374 | } | |
375 | if (he == NULL) | |
376 | he = history(hist, H_LAST); | |
377 | } else { | |
378 | he = history(hist, H_NEXT_EVENT, i); | |
379 | if (he == NULL) { | |
380 | /* | |
381 | * the notion of first and last is | |
382 | * backwards to that of the history package | |
383 | */ | |
384 | he = history(hist, last ? H_FIRST : H_LAST); | |
385 | } | |
386 | } | |
387 | if (he == NULL) | |
388 | error("history number %s not found (internal error)", | |
389 | str); | |
390 | } else { | |
391 | /* | |
392 | * pattern | |
393 | */ | |
394 | he = history(hist, H_NEXT_STR, str); | |
395 | if (he == NULL) | |
396 | error("history pattern not found: %s", str); | |
dc06984c | 397 | } |
f7280c66 | 398 | return (he->num); |
dc06984c | 399 | } |