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