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