Don't crash when board size is changed.
[AROS-Contrib.git] / gnu / abc-shell / history.c
blob7b7b093603577191c455d3937775d13ab7cc8b10
1 /*
2 * command history
4 * only implements in-memory history.
5 */
7 /*
8 * This file contains
9 * a) the original in-memory history mechanism
10 * b) a simple file saving history mechanism done by sjg@zen
13 #include <sys/stat.h>
14 #include "sh.h"
16 #ifdef HISTORY
18 # ifndef HISTFILE
19 # define HISTFILE "/SDK/Data/abc-shell/history"
20 # endif
22 void histappend(const char *, int);
24 static int hist_execute(char *);
25 static int hist_replace(char **, const char *, const char *, int);
26 static char **hist_get(const char *, int, int);
27 static char **hist_get_oldest(void);
28 static void histbackup(void);
30 static char **current; /* current postition in history[] */
31 static char *hname; /* current name of history file */
32 static int hstarted; /* set after hist_init() called */
33 static Source *hist_source;
36 int
37 c_fc(char **wp)
39 struct shf *shf;
40 struct temp *tf = NULL;
41 char *p, *editor = (char *) 0;
42 int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
43 int optc;
44 char *first = (char *) 0, *last = (char *) 0;
45 char **hfirst, **hlast, **hp;
47 if (!Flag(FTALKING_I)) {
48 bi_errorf("history functions not available");
49 return 1;
52 while ((optc = ksh_getopt(wp, &builtin_opt,
53 "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
54 switch (optc) {
55 case 'e':
56 p = builtin_opt.optarg;
57 if (strcmp(p, "-") == 0)
58 sflag++;
59 else {
60 size_t len = strlen(p) + 4;
61 editor = str_nsave(p, len, ATEMP);
62 strlcat(editor, " $_", len);
64 break;
65 case 'g': /* non-at&t ksh */
66 gflag++;
67 break;
68 case 'l':
69 lflag++;
70 break;
71 case 'n':
72 nflag++;
73 break;
74 case 'r':
75 rflag++;
76 break;
77 case 's': /* posix version of -e - */
78 sflag++;
79 break;
80 /* kludge city - accept -num as -- -num (kind of) */
81 case '0': case '1': case '2': case '3': case '4':
82 case '5': case '6': case '7': case '8': case '9':
83 p = shf_smprintf("-%c%s",
84 optc, builtin_opt.optarg);
85 if (!first)
86 first = p;
87 else if (!last)
88 last = p;
89 else {
90 bi_errorf("too many arguments");
91 return 1;
93 break;
94 case '?':
95 return 1;
97 wp += builtin_opt.optind;
99 /* Substitute and execute command */
100 if (sflag) {
101 char *pat = (char *) 0, *rep = (char *) 0;
103 if (editor || lflag || nflag || rflag) {
104 bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
105 return 1;
108 /* Check for pattern replacement argument */
109 if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
110 pat = str_save(*wp, ATEMP);
111 p = pat + (p - *wp);
112 *p++ = '\0';
113 rep = p;
114 wp++;
116 /* Check for search prefix */
117 if (!first && (first = *wp))
118 wp++;
119 if (last || *wp) {
120 bi_errorf("too many arguments");
121 return 1;
124 hp = first ? hist_get(first, false, false) :
125 hist_get_newest(false);
126 if (!hp)
127 return 1;
128 return hist_replace(hp, pat, rep, gflag);
131 if (editor && (lflag || nflag)) {
132 bi_errorf("can't use -l, -n with -e");
133 return 1;
136 if (!first && (first = *wp))
137 wp++;
138 if (!last && (last = *wp))
139 wp++;
140 if (*wp) {
141 bi_errorf("too many arguments");
142 return 1;
144 if (!first) {
145 hfirst = lflag ? hist_get("-16", true, true) :
146 hist_get_newest(false);
147 if (!hfirst)
148 return 1;
149 /* can't fail if hfirst didn't fail */
150 hlast = hist_get_newest(false);
151 } else {
152 /* POSIX says not an error if first/last out of bounds
153 * when range is specified; at&t ksh and pdksh allow out of
154 * bounds for -l as well.
156 hfirst = hist_get(first, (lflag || last) ? true : false,
157 lflag ? true : false);
158 if (!hfirst)
159 return 1;
160 hlast = last ? hist_get(last, true, lflag ? true : false) :
161 (lflag ? hist_get_newest(false) : hfirst);
162 if (!hlast)
163 return 1;
165 if (hfirst > hlast) {
166 char **temp;
168 temp = hfirst; hfirst = hlast; hlast = temp;
169 rflag = !rflag; /* POSIX */
172 /* List history */
173 if (lflag) {
174 char *s, *t;
175 const char *nfmt = nflag ? "\t" : "%d\t";
177 for (hp = rflag ? hlast : hfirst;
178 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
179 shf_fprintf(shl_stdout, nfmt,
180 hist_source->line - (int) (histptr - hp));
181 /* print multi-line commands correctly */
182 for (s = *hp; (t = strchr(s, '\n')); s = t)
183 shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
184 shf_fprintf(shl_stdout, "%s\n", s);
186 shf_flush(shl_stdout);
187 return 0;
190 /* Run editor on selected lines, then run resulting commands */
192 tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
193 if (!(shf = tf->shf)) {
194 bi_errorf("cannot create temp file %s - %s",
195 tf->name, strerror(errno));
196 return 1;
198 for (hp = rflag ? hlast : hfirst;
199 hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
200 shf_fprintf(shf, "%s\n", *hp);
201 if (shf_close(shf) == EOF) {
202 bi_errorf("error writing temporary file - %s", strerror(errno));
203 return 1;
206 /* Ignore setstr errors here (arbitrary) */
207 setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
209 /* XXX: source should not get trashed by this.. */
211 Source *sold = source;
212 int ret;
214 ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_");
215 source = sold;
216 if (ret)
217 return ret;
221 struct stat statb;
222 XString xs;
223 char *xp;
224 int n;
226 if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
227 bi_errorf("cannot open temp file %s", tf->name);
228 return 1;
231 n = fstat(shf_fileno(shf), &statb) < 0 ? 128 :
232 statb.st_size + 1;
233 Xinit(xs, xp, n, hist_source->areap);
234 while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
235 xp += n;
236 if (Xnleft(xs, xp) <= 0)
237 XcheckN(xs, xp, Xlength(xs, xp));
239 if (n < 0) {
240 bi_errorf("error reading temp file %s - %s",
241 tf->name, strerror(shf_errno(shf)));
242 shf_close(shf);
243 return 1;
245 shf_close(shf);
246 *xp = '\0';
247 strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
248 return hist_execute(Xstring(xs, xp));
252 /* Save cmd in history, execute cmd (cmd gets trashed) */
253 static int
254 hist_execute(char *cmd)
256 Source *sold;
257 int ret;
258 char *p, *q;
260 histbackup();
262 for (p = cmd; p; p = q) {
263 if ((q = strchr(p, '\n'))) {
264 *q++ = '\0'; /* kill the newline */
265 if (!*q) /* ignore trailing newline */
266 q = (char *) 0;
268 if (p != cmd)
269 histappend(p, true);
270 else
271 histsave(++(hist_source->line), p, 1);
273 shellf("%s\n", p); /* POSIX doesn't say this is done... */
274 if ((p = q)) /* restore \n (trailing \n not restored) */
275 q[-1] = '\n';
278 /* Commands are executed here instead of pushing them onto the
279 * input 'cause posix says the redirection and variable assignments
280 * in
281 * X=y fc -e - 42 2> /dev/null
282 * are to effect the repeated commands environment.
284 /* XXX: source should not get trashed by this.. */
285 sold = source;
286 ret = command(cmd);
287 source = sold;
288 return ret;
291 static int
292 hist_replace(char **hp, const char *pat, const char *rep, int global)
294 char *line;
296 if (!pat)
297 line = str_save(*hp, ATEMP);
298 else {
299 char *s, *s1;
300 int pat_len = strlen(pat);
301 int rep_len = strlen(rep);
302 int len;
303 XString xs;
304 char *xp;
305 int any_subst = 0;
307 Xinit(xs, xp, 128, ATEMP);
308 for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global);
309 s = s1 + pat_len) {
310 any_subst = 1;
311 len = s1 - s;
312 XcheckN(xs, xp, len + rep_len);
313 memcpy(xp, s, len); /* first part */
314 xp += len;
315 memcpy(xp, rep, rep_len); /* replacement */
316 xp += rep_len;
318 if (!any_subst) {
319 bi_errorf("substitution failed");
320 return 1;
322 len = strlen(s) + 1;
323 XcheckN(xs, xp, len);
324 memcpy(xp, s, len);
325 xp += len;
326 line = Xclose(xs, xp);
328 return hist_execute(line);
332 * get pointer to history given pattern
333 * pattern is a number or string
335 static char **
336 hist_get(const char *str, int approx, int allow_cur)
338 char **hp = (char **) 0;
339 int n;
341 if (getn(str, &n)) {
342 hp = histptr + (n < 0 ? n : (n - hist_source->line));
343 if (hp < history) {
344 if (approx)
345 hp = hist_get_oldest();
346 else {
347 bi_errorf("%s: not in history", str);
348 hp = (char **) 0;
350 } else if (hp > histptr) {
351 if (approx)
352 hp = hist_get_newest(allow_cur);
353 else {
354 bi_errorf("%s: not in history", str);
355 hp = (char **) 0;
357 } else if (!allow_cur && hp == histptr) {
358 bi_errorf("%s: invalid range", str);
359 hp = (char **) 0;
361 } else {
362 int anchored = *str == '?' ? (++str, 0) : 1;
364 /* the -1 is to avoid the current fc command */
365 n = findhist(histptr - history - 1, 0, str, anchored);
366 if (n < 0) {
367 bi_errorf("%s: not in history", str);
368 hp = (char **) 0;
369 } else
370 hp = &history[n];
372 return hp;
375 /* Return a pointer to the newest command in the history */
376 char **
377 hist_get_newest(int allow_cur)
379 if (histptr < history || (!allow_cur && histptr == history)) {
380 bi_errorf("no history (yet)");
381 return (char **) 0;
383 if (allow_cur)
384 return histptr;
385 return histptr - 1;
388 /* Return a pointer to the newest command in the history */
389 static char **
390 hist_get_oldest(void)
392 if (histptr <= history) {
393 bi_errorf("no history (yet)");
394 return (char **) 0;
396 return history;
399 /******************************/
400 /* Back up over last histsave */
401 /******************************/
402 static void
403 histbackup(void)
405 static int last_line = -1;
407 if (histptr >= history && last_line != hist_source->line) {
408 hist_source->line--;
409 afree((void*)*histptr, APERM);
410 histptr--;
411 last_line = hist_source->line;
416 * Return the current position.
418 char **
419 histpos(void)
421 return current;
425 histnum(int n)
427 int last = histptr - history;
429 if (n < 0 || n >= last) {
430 current = histptr;
431 return last;
432 } else {
433 current = &history[n];
434 return n;
439 * This will become unnecessary if hist_get is modified to allow
440 * searching from positions other than the end, and in either
441 * direction.
444 findhist(int start, int fwd, const char *str, int anchored)
446 char **hp;
447 int maxhist = histptr - history;
448 int incr = fwd ? 1 : -1;
449 int len = strlen(str);
451 if (start < 0 || start >= maxhist)
452 start = maxhist;
454 hp = &history[start];
455 for (; hp >= history && hp <= histptr; hp += incr)
456 if ((anchored && strncmp(*hp, str, len) == 0) ||
457 (!anchored && strstr(*hp, str)))
458 return hp - history;
460 return -1;
464 findhistrel(const char *str)
466 int maxhist = histptr - history;
467 int start = maxhist - 1;
468 int rec = atoi(str);
470 if (rec == 0)
471 return -1;
472 if (rec > 0) {
473 if (rec > maxhist)
474 return -1;
475 return rec - 1;
477 if (rec > maxhist)
478 return -1;
479 return start + rec + 1;
483 * set history
484 * this means reallocating the dataspace
486 void
487 sethistsize(int n)
489 if (n > 0 && n != histsize) {
490 int cursize = histptr - history;
492 /* save most recent history */
493 if (n < cursize) {
494 memmove(history, histptr - n, n * sizeof(char *));
495 cursize = n;
498 history = (char **)aresize(history, n*sizeof(char *), APERM);
500 histsize = n;
501 histptr = history + cursize;
506 * set history file
507 * This can mean reloading/resetting/starting history file
508 * maintenance
510 void
511 sethistfile(const char *name)
513 /* if not started then nothing to do */
514 if (hstarted == 0)
515 return;
517 /* if the name is the same as the name we have */
518 if (hname && strcmp(hname, name) == 0)
519 return;
522 * its a new name - possibly
524 if (hname) {
525 afree(hname, APERM);
526 hname = NULL;
529 hist_init(hist_source);
533 * initialise the history vector
535 void
536 init_histvec(void)
538 if (history == (char **)NULL) {
539 histsize = HISTORYSIZE;
540 history = (char **)alloc(histsize*sizeof (char *), APERM);
541 histptr = history - 1;
546 * save command in history
548 void
549 histsave(int lno, const char *cmd, int dowrite)
551 char **hp = histptr;
552 char *cp;
553 int l;
555 /* don't save if command is the same as last one */
556 if (hp >= history && *hp != NULL) {
557 l = strlen(cmd);
558 if (strcmp(*hp, cmd) == 0)
559 return;
560 else if ((cmd[l-1] == '\n')
561 && (strlen(*hp) == l-1)
562 && strncmp(*hp, cmd, l - 1) == 0)
563 return;
566 if (++hp >= history + histsize) { /* remove oldest command */
567 afree((void*)history[0], APERM);
568 memmove(history, history + 1,
569 sizeof(history[0]) * (histsize - 1));
570 hp = &history[histsize - 1];
572 *hp = str_save(cmd, APERM);
573 /* trash trailing newline but allow imbedded newlines */
574 cp = *hp + strlen(*hp);
575 if (cp > *hp && cp[-1] == '\n')
576 cp[-1] = '\0';
577 histptr = hp;
581 * Append an entry to the last saved command. Used for multiline
582 * commands
584 void
585 histappend(const char *cmd, int nl_separate)
587 int hlen, clen;
588 char *p;
590 hlen = strlen(*histptr);
591 clen = strlen(cmd);
592 if (clen > 0 && cmd[clen-1] == '\n')
593 clen--;
594 p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM);
595 p += hlen;
596 if (nl_separate)
597 *p++ = '\n';
598 memcpy(p, cmd, clen);
599 p[clen] = '\0';
603 * 92-04-25 <sjg@zen>
604 * A simple history file implementation.
605 * At present we only save the history when we exit.
606 * This can cause problems when there are multiple shells are
607 * running under the same user-id. The last shell to exit gets
608 * to save its history.
610 void
611 hist_init(Source *s)
613 char *f;
614 FILE *fh;
616 if (Flag(FTALKING) == 0)
617 return;
619 hstarted = 1;
621 hist_source = s;
623 if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') {
624 # if 1 /* Don't use history file unless the user asks for it */
625 hname = NULL;
626 return;
627 # else
628 char *home = str_val(global("HOME"));
629 int len;
631 if (home == NULL)
632 home = null;
633 f = HISTFILE;
634 hname = alloc(len = strlen(home) + strlen(f) + 2, APERM);
635 shf_snprintf(hname, len, "%s/%s", home, f);
636 # endif
637 } else
638 hname = str_save(f, APERM);
640 if ((fh = fopen(hname, "r"))) {
641 int pos = 0, nread = 0;
642 int contin = 0; /* continuation of previous command */
643 char *end;
644 char hline[LINE + 1];
646 while (1) {
647 if (pos >= nread) {
648 pos = 0;
649 nread = fread(hline, 1, LINE, fh);
650 if (nread <= 0)
651 break;
652 hline[nread] = '\n';
654 end = strchr(hline + pos, '\n'); /* will always succeed - 0, but not \n!!! */
655 if(end != NULL) /* in case sth put \0 in .history...) */
656 *end = '\0';
657 else
658 end = strchr(hline + pos, '\0'); /* THIS will always succeed */
659 if (contin)
660 histappend(hline + pos, 0);
661 else {
662 hist_source->line++;
663 histsave(0, hline + pos, 0);
665 pos = end - hline + 1;
666 contin = end == &hline[nread];
668 fclose(fh);
673 * save our history.
674 * We check that we do not have more than we are allowed.
675 * If the history file is read-only we do nothing.
676 * Handy for having all shells start with a useful history set.
679 void
680 hist_finish(void)
682 static int once;
683 FILE *fh;
684 int i;
685 char **hp;
687 if (once++)
688 return;
689 /* check how many we have */
690 i = histptr - history;
691 if (i >= histsize)
692 hp = &histptr[-histsize];
693 else
694 hp = history;
695 if (hname && (fh = fopen(hname, "w")))
697 for (i = 0; hp + i <= histptr && hp[i]; i++)
698 fprintf(fh, "%s\n", hp[i]);
699 fclose(fh);
703 #else /* HISTORY */
705 /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
706 void
707 init_histvec(void)
711 void
712 hist_init(Source *s)
716 void
717 hist_finish(void)
721 void
722 histsave(int lno, const char *cmd, int dowrite)
724 errorf("history not enabled");
726 #endif /* HISTORY */