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