histedit.c revision 97731
1270096Strasz/*- 2270096Strasz * Copyright (c) 1993 3270096Strasz * The Regents of the University of California. All rights reserved. 4270096Strasz * 5270096Strasz * This code is derived from software contributed to Berkeley by 6270096Strasz * Kenneth Almquist. 7270096Strasz * 8270096Strasz * Redistribution and use in source and binary forms, with or without 9270096Strasz * modification, are permitted provided that the following conditions 10270096Strasz * are met: 11270096Strasz * 1. Redistributions of source code must retain the above copyright 12270096Strasz * notice, this list of conditions and the following disclaimer. 13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright 14270096Strasz * notice, this list of conditions and the following disclaimer in the 15270096Strasz * documentation and/or other materials provided with the distribution. 16270096Strasz * 3. All advertising materials mentioning features or use of this software 17270096Strasz * must display the following acknowledgement: 18270096Strasz * This product includes software developed by the University of 19270096Strasz * California, Berkeley and its contributors. 20270096Strasz * 4. Neither the name of the University nor the names of its contributors 21270096Strasz * may be used to endorse or promote products derived from this software 22270096Strasz * without specific prior written permission. 23270096Strasz * 24270096Strasz * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27270096Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34270096Strasz * SUCH DAMAGE. 35270096Strasz */ 36270096Strasz 37270096Strasz#ifndef lint 38270096Strasz#if 0 39270096Straszstatic char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; 40270096Strasz#endif 41270096Straszstatic const char rcsid[] = 42270096Strasz "$FreeBSD: head/bin/sh/histedit.c 97731 2002-06-02 08:34:09Z tjr $"; 43270096Strasz#endif /* not lint */ 44270096Strasz 45272403Strasz#include <sys/param.h> 46270096Strasz#include <limits.h> 47270096Strasz#include <paths.h> 48270096Strasz#include <stdio.h> 49270096Strasz#include <stdlib.h> 50270281Strasz#include <unistd.h> 51270096Strasz/* 52270096Strasz * Editline and history functions (and glue). 53270096Strasz */ 54270096Strasz#include "shell.h" 55270402Strasz#include "parser.h" 56270402Strasz#include "var.h" 57270096Strasz#include "options.h" 58270096Strasz#include "main.h" 59270096Strasz#include "output.h" 60270096Strasz#include "mystring.h" 61270096Strasz#ifndef NO_HISTORY 62270096Strasz#include "myhistedit.h" 63270096Strasz#include "error.h" 64270096Strasz#include "eval.h" 65270096Strasz#include "memalloc.h" 66270096Strasz 67270096Strasz#define MAXHISTLOOPS 4 /* max recursions through fc */ 68270096Strasz#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ 69270096Strasz 70270096StraszHistory *hist; /* history cookie */ 71270096StraszEditLine *el; /* editline cookie */ 72270096Straszint displayhist; 73270096Straszstatic FILE *el_in, *el_out, *el_err; 74270096Strasz 75270096StraszSTATIC char *fc_replace(const char *, char *, char *); 76270096Strasz 77270096Strasz/* 78270096Strasz * Set history and editing status. Called whenever the status may 79270096Strasz * have changed (figures out what to do). 80270096Strasz */ 81270096Straszvoid 82270096Straszhistedit(void) 83270096Strasz{ 84270096Strasz 85270096Strasz#define editing (Eflag || Vflag) 86270096Strasz 87270096Strasz if (iflag) { 88270096Strasz if (!hist) { 89270096Strasz /* 90270096Strasz * turn history on 91270096Strasz */ 92270096Strasz INTOFF; 93270096Strasz hist = history_init(); 94270096Strasz INTON; 95270096Strasz 96270096Strasz if (hist != NULL) 97270096Strasz sethistsize(histsizeval()); 98270096Strasz else 99270096Strasz out2str("sh: can't initialize history\n"); 100270096Strasz } 101270096Strasz if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ 102270096Strasz /* 103270096Strasz * turn editing on 104270096Strasz */ 105270096Strasz INTOFF; 106270096Strasz if (el_in == NULL) 107270096Strasz el_in = fdopen(0, "r"); 108270096Strasz if (el_err == NULL) 109270096Strasz el_err = fdopen(1, "w"); 110270096Strasz if (el_out == NULL) 111270096Strasz el_out = fdopen(2, "w"); 112270096Strasz if (el_in == NULL || el_err == NULL || el_out == NULL) 113270096Strasz goto bad; 114270096Strasz el = el_init(arg0, el_in, el_out, el_err); 115270096Strasz if (el != NULL) { 116270096Strasz if (hist) 117270096Strasz el_set(el, EL_HIST, history, hist); 118270096Strasz el_set(el, EL_PROMPT, getprompt); 119270096Strasz } else { 120270096Straszbad: 121270096Strasz out2str("sh: can't initialize editing\n"); 122270096Strasz } 123270096Strasz INTON; 124270096Strasz } else if (!editing && el) { 125270096Strasz INTOFF; 126270096Strasz el_end(el); 127270096Strasz el = NULL; 128270096Strasz INTON; 129270096Strasz } 130270096Strasz if (el) { 131270096Strasz if (Vflag) 132270096Strasz el_set(el, EL_EDITOR, "vi"); 133270096Strasz else if (Eflag) 134270096Strasz el_set(el, EL_EDITOR, "emacs"); 135270096Strasz } 136270096Strasz } else { 137270096Strasz INTOFF; 138270096Strasz if (el) { /* no editing if not interactive */ 139270096Strasz el_end(el); 140270096Strasz el = NULL; 141270096Strasz } 142270096Strasz if (hist) { 143270096Strasz history_end(hist); 144270096Strasz hist = NULL; 145270096Strasz } 146270096Strasz INTON; 147270096Strasz } 148270096Strasz} 149270096Strasz 150270096Strasz 151270096Straszvoid 152270096Straszsethistsize(hs) 153270096Strasz const char *hs; 154270096Strasz{ 155270402Strasz int histsize; 156270096Strasz HistEvent he; 157270096Strasz 158270096Strasz if (hist != NULL) { 159270096Strasz if (hs == NULL || *hs == '\0' || 160270096Strasz (histsize = atoi(hs)) < 0) 161270096Strasz histsize = 100; 162270096Strasz history(hist, &he, H_EVENT, histsize); 163270096Strasz } 164270096Strasz} 165270096Strasz 166270096Strasz/* 167270402Strasz * This command is provided since POSIX decided to standardize 168270096Strasz * the Korn shell fc command. Oh well... 169270096Strasz */ 170270096Straszint 171270096Straszhistcmd(int argc, char **argv) 172270096Strasz{ 173270096Strasz int ch; 174270096Strasz char *editor = NULL; 175270096Strasz HistEvent he; 176270096Strasz int lflg = 0, nflg = 0, rflg = 0, sflg = 0; 177270096Strasz int i, retval; 178270096Strasz char *firststr, *laststr; 179270096Strasz int first, last, direction; 180270096Strasz char *pat = NULL, *repl; /* ksh "fc old=new" crap */ 181270096Strasz static int active = 0; 182270096Strasz struct jmploc jmploc; 183270096Strasz struct jmploc *volatile savehandler; 184270096Strasz char editfile[PATH_MAX]; 185270096Strasz FILE *efp; 186270096Strasz int oldhistnum; 187270096Strasz#ifdef __GNUC__ 188270096Strasz /* Avoid longjmp clobbering */ 189270096Strasz (void) &editor; 190270096Strasz (void) &lflg; 191270096Strasz (void) &nflg; 192270096Strasz (void) &rflg; 193270096Strasz (void) &sflg; 194270096Strasz (void) &firststr; 195270096Strasz (void) &laststr; 196270096Strasz (void) &pat; 197270096Strasz (void) &repl; 198270096Strasz (void) &efp; 199270096Strasz (void) &argc; 200270096Strasz (void) &argv; 201270207Strasz#endif 202270207Strasz 203270207Strasz if (hist == NULL) 204270207Strasz error("history not active"); 205270207Strasz 206270207Strasz if (argc == 1) 207270207Strasz error("missing history argument"); 208270207Strasz 209270207Strasz optreset = 1; optind = 1; /* initialize getopt */ 210270096Strasz while (not_fcnumber(argv[optind]) && 211270096Strasz (ch = getopt(argc, argv, ":e:lnrs")) != -1) 212270096Strasz switch ((char)ch) { 213270096Strasz case 'e': 214270096Strasz editor = optarg; 215270096Strasz break; 216270096Strasz case 'l': 217270096Strasz lflg = 1; 218270096Strasz break; 219270096Strasz case 'n': 220270096Strasz nflg = 1; 221270096Strasz break; 222270096Strasz case 'r': 223270096Strasz rflg = 1; 224270096Strasz break; 225270096Strasz case 's': 226270096Strasz sflg = 1; 227270096Strasz break; 228270096Strasz case ':': 229270207Strasz error("option -%c expects argument", optopt); 230270207Strasz case '?': 231270207Strasz default: 232270207Strasz error("unknown option: -%c", optopt); 233270207Strasz } 234270096Strasz argc -= optind, argv += optind; 235270207Strasz 236270207Strasz /* 237270096Strasz * If executing... 238270207Strasz */ 239270096Strasz if (lflg == 0 || editor || sflg) { 240270207Strasz lflg = 0; /* ignore */ 241270096Strasz editfile[0] = '\0'; 242270096Strasz /* 243270096Strasz * Catch interrupts to reset active counter and 244270096Strasz * cleanup temp files. 245270096Strasz */ 246270096Strasz if (setjmp(jmploc.loc)) { 247270096Strasz active = 0; 248270096Strasz if (*editfile) 249270096Strasz unlink(editfile); 250270096Strasz handler = savehandler; 251270096Strasz longjmp(handler->loc, 1); 252270096Strasz } 253270096Strasz savehandler = handler; 254270096Strasz handler = &jmploc; 255270096Strasz if (++active > MAXHISTLOOPS) { 256270096Strasz active = 0; 257270096Strasz displayhist = 0; 258270096Strasz error("called recursively too many times"); 259270096Strasz } 260270096Strasz /* 261270096Strasz * Set editor. 262270096Strasz */ 263270096Strasz if (sflg == 0) { 264270096Strasz if (editor == NULL && 265270096Strasz (editor = bltinlookup("FCEDIT", 1)) == NULL && 266270096Strasz (editor = bltinlookup("EDITOR", 1)) == NULL) 267270096Strasz editor = DEFEDITOR; 268270096Strasz if (editor[0] == '-' && editor[1] == '\0') { 269270096Strasz sflg = 1; /* no edit */ 270270096Strasz editor = NULL; 271270096Strasz } 272270096Strasz } 273270096Strasz } 274270096Strasz 275270096Strasz /* 276270096Strasz * If executing, parse [old=new] now 277270096Strasz */ 278270096Strasz if (lflg == 0 && argc > 0 && 279270096Strasz ((repl = strchr(argv[0], '=')) != NULL)) { 280270096Strasz pat = argv[0]; 281270096Strasz *repl++ = '\0'; 282270096Strasz argc--, argv++; 283270096Strasz } 284270096Strasz /* 285270096Strasz * determine [first] and [last] 286270096Strasz */ 287270096Strasz switch (argc) { 288270096Strasz case 0: 289270096Strasz firststr = lflg ? "-16" : "-1"; 290270096Strasz laststr = "-1"; 291270096Strasz break; 292270096Strasz case 1: 293270096Strasz firststr = argv[0]; 294270096Strasz laststr = lflg ? "-1" : argv[0]; 295270096Strasz break; 296270096Strasz case 2: 297270096Strasz firststr = argv[0]; 298270096Strasz laststr = argv[1]; 299270096Strasz break; 300270096Strasz default: 301270096Strasz error("too many args"); 302270096Strasz } 303270096Strasz /* 304270096Strasz * Turn into event numbers. 305270096Strasz */ 306270096Strasz first = str_to_event(firststr, 0); 307270096Strasz last = str_to_event(laststr, 1); 308270096Strasz 309270096Strasz if (rflg) { 310270096Strasz i = last; 311270096Strasz last = first; 312270096Strasz first = i; 313270096Strasz } 314270096Strasz /* 315270096Strasz * XXX - this should not depend on the event numbers 316270096Strasz * always increasing. Add sequence numbers or offset 317270096Strasz * to the history element in next (diskbased) release. 318270096Strasz */ 319270096Strasz direction = first < last ? H_PREV : H_NEXT; 320270096Strasz 321270096Strasz /* 322270096Strasz * If editing, grab a temp file. 323270096Strasz */ 324270096Strasz if (editor) { 325270096Strasz int fd; 326270096Strasz INTOFF; /* easier */ 327270096Strasz sprintf(editfile, "%s/_shXXXXXX", _PATH_TMP); 328270096Strasz if ((fd = mkstemp(editfile)) < 0) 329270096Strasz error("can't create temporary file %s", editfile); 330270096Strasz if ((efp = fdopen(fd, "w")) == NULL) { 331270096Strasz close(fd); 332270096Strasz error("can't allocate stdio buffer for temp"); 333270096Strasz } 334270096Strasz } 335270096Strasz 336270096Strasz /* 337270096Strasz * Loop through selected history events. If listing or executing, 338270096Strasz * do it now. Otherwise, put into temp file and call the editor 339270096Strasz * after. 340270096Strasz * 341270096Strasz * The history interface needs rethinking, as the following 342270096Strasz * convolutions will demonstrate. 343270096Strasz */ 344270096Strasz history(hist, &he, H_FIRST); 345270096Strasz retval = history(hist, &he, H_NEXT_EVENT, first); 346270096Strasz for (;retval != -1; retval = history(hist, &he, direction)) { 347270096Strasz if (lflg) { 348270096Strasz if (!nflg) 349270096Strasz out1fmt("%5d ", he.num); 350270096Strasz out1str(he.str); 351270096Strasz } else { 352270096Strasz char *s = pat ? 353270096Strasz fc_replace(he.str, pat, repl) : (char *)he.str; 354270096Strasz 355270096Strasz if (sflg) { 356270096Strasz if (displayhist) { 357270096Strasz out2str(s); 358270096Strasz } 359270096Strasz evalstring(s); 360270096Strasz if (displayhist && hist) { 361270096Strasz /* 362270096Strasz * XXX what about recursive and 363270096Strasz * relative histnums. 364270096Strasz */ 365270096Strasz oldhistnum = he.num; 366270096Strasz history(hist, &he, H_ENTER, s); 367270096Strasz /* 368270096Strasz * XXX H_ENTER moves the internal 369270096Strasz * cursor, set it back to the current 370270096Strasz * entry. 371270096Strasz */ 372270096Strasz retval = history(hist, &he, 373270096Strasz H_NEXT_EVENT, oldhistnum); 374270096Strasz } 375270096Strasz } else 376270096Strasz fputs(s, efp); 377270096Strasz } 378270096Strasz /* 379270096Strasz * At end? (if we were to loose last, we'd sure be 380270096Strasz * messed up). 381270096Strasz */ 382270096Strasz if (he.num == last) 383270096Strasz break; 384270096Strasz } 385270096Strasz if (editor) { 386270096Strasz char *editcmd; 387270096Strasz 388270096Strasz fclose(efp); 389270096Strasz editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); 390270096Strasz sprintf(editcmd, "%s %s", editor, editfile); 391270096Strasz evalstring(editcmd); /* XXX - should use no JC command */ 392270096Strasz INTON; 393270096Strasz readcmdfile(editfile); /* XXX - should read back - quick tst */ 394270096Strasz unlink(editfile); 395270096Strasz } 396270096Strasz 397270096Strasz if (lflg == 0 && active > 0) 398270096Strasz --active; 399270096Strasz if (displayhist) 400270096Strasz displayhist = 0; 401270096Strasz return 0; 402270096Strasz} 403270096Strasz 404270096StraszSTATIC char * 405270096Straszfc_replace(const char *s, char *p, char *r) 406270096Strasz{ 407270096Strasz char *dest; 408270096Strasz int plen = strlen(p); 409270096Strasz 410270096Strasz STARTSTACKSTR(dest); 411270096Strasz while (*s) { 412270096Strasz if (*s == *p && strncmp(s, p, plen) == 0) { 413270096Strasz while (*r) 414270096Strasz STPUTC(*r++, dest); 415270096Strasz s += plen; 416270096Strasz *p = '\0'; /* so no more matches */ 417270096Strasz } else 418270096Strasz STPUTC(*s++, dest); 419270096Strasz } 420270096Strasz STACKSTRNUL(dest); 421270096Strasz dest = grabstackstr(dest); 422270096Strasz 423270096Strasz return (dest); 424270096Strasz} 425270096Strasz 426270096Straszint 427270096Strasznot_fcnumber(char *s) 428270096Strasz{ 429270096Strasz if (s == NULL) 430270096Strasz return (0); 431270096Strasz if (*s == '-') 432270096Strasz s++; 433270096Strasz return (!is_number(s)); 434270096Strasz} 435270096Strasz 436270096Straszint 437270096Straszstr_to_event(char *str, int last) 438270096Strasz{ 439270096Strasz HistEvent he; 440270096Strasz char *s = str; 441270096Strasz int relative = 0; 442270096Strasz int i, retval; 443270096Strasz 444270096Strasz retval = history(hist, &he, H_FIRST); 445270096Strasz switch (*s) { 446270096Strasz case '-': 447270096Strasz relative = 1; 448270096Strasz /*FALLTHROUGH*/ 449270096Strasz case '+': 450270096Strasz s++; 451270096Strasz } 452270096Strasz if (is_number(s)) { 453270096Strasz i = atoi(s); 454270096Strasz if (relative) { 455270096Strasz while (retval != -1 && i--) { 456270096Strasz retval = history(hist, &he, H_NEXT); 457270096Strasz } 458270096Strasz if (retval == -1) 459270096Strasz retval = history(hist, &he, H_LAST); 460270096Strasz } else { 461270096Strasz retval = history(hist, &he, H_NEXT_EVENT, i); 462270096Strasz if (retval == -1) { 463270096Strasz /* 464270096Strasz * the notion of first and last is 465270096Strasz * backwards to that of the history package 466270096Strasz */ 467270096Strasz retval = history(hist, &he, last ? H_FIRST : H_LAST); 468270096Strasz } 469270096Strasz } 470270096Strasz if (retval == -1) 471270096Strasz error("history number %s not found (internal error)", 472270096Strasz str); 473270096Strasz } else { 474270096Strasz /* 475270096Strasz * pattern 476270096Strasz */ 477270096Strasz retval = history(hist, &he, H_PREV_STR, str); 478270096Strasz if (retval == -1) 479270096Strasz error("history pattern not found: %s", str); 480270096Strasz } 481270096Strasz return (he.num); 482270096Strasz} 483270096Strasz#else 484270096Strasz#include "error.h" 485270096Strasz 486270096Straszint 487270096Straszhistcmd(int argc, char **argv) 488270096Strasz{ 489270096Strasz 490270096Strasz error("not compiled with history support"); 491270096Strasz /*NOTREACHED*/ 492270096Strasz return (0); 493270096Strasz} 494270096Strasz#endif 495270096Strasz