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