/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ #define NO_PROCDECL #include "tune.h" /* to allow definition of debug flags there. */ #ifdef DEBUG /* General debug flag, turn on file-specific flag. */ # ifndef EXTEND_DEBUG # define EXTEND_DEBUG 1 # endif #else # if EXTEND_DEBUG /* File-specific debug flag, turn on general flag. */ # define DEBUG # endif #endif #include "jove.h" RCS("$Id: extend.c,v 14.32.0.12 1994/06/24 17:55:02 tom Exp tom $") #include "ctype.h" #include "io.h" #include "maps.h" #include "process.h" #ifndef NO_PROCDECL #include "re.h" #include "screen.h" #endif #ifndef TINY private const char *const TypeName[] = { NullStr, "Command", /* FUNCTION */ "Variable", /* VARIABLE */ "Macro", /* MACRO */ "Keymap", /* KEYMAP */ }; #endif private const char Help[] = "*Help*"; private void do_help __(( const data_obj *_(dp) )); #ifdef COLOR private int ask_color __(( int _(def), const char *_(prompt) )); #endif private const char * vpr_aux __(( const Variable *_(vp) )); int InJoverc ZERO; DEF_INT( "quiet", Quiet, V_BOOL ) ZERO; /*===== Command/Macro auto-execute. ==========================================*/ private struct autoexec { struct autoexec *a_next; data_obj *a_cmd; char *a_pattern; } *AutoExecs ZERO; DEF_CMD( "auto-execute-command", DefAutoExec, ARG(FUNCTION) ); DEF_CMD( "auto-execute-macro", DefAutoExec, ARG(MACRO) ) { register struct autoexec *ap, **hp; register data_obj *dp; register char *pattern; #ifndef SMALL if (exp_p) { if (ap = AutoExecs) { register const char *fmt = "%-30s %s"; TOstart(Help, TRUE); Typeout(fmt, "Command", "Pattern"); Typeout(fmt, "-------", "-------"); do { Typeout(fmt, ap->a_cmd->Name, ap->a_pattern); } while (ap = ap->a_next); TOstop(); } } #endif /* !SMALL */ dp = FindObj(ObjArg(LastCmd), ProcFmt); pattern = re_ask((char *) 0, YES, ProcArgFmt, dp->Name); for (hp = &AutoExecs; ap = (*hp); hp = &ap->a_next) { if ((ap->a_cmd == dp) && (strcmp(pattern, ap->a_pattern) == 0)) return; /* Eliminate duplicates. */ } ap = (struct autoexec *) emalloc(sizeof(*ap)); ap->a_pattern = copystr(pattern); ap->a_cmd = dp; ap->a_next = NULL; (*hp) = ap; } /* DoAutoExec: NEW and OLD are file names, and if NEW and OLD aren't the same kind of file (i.e., match the same pattern) or OLD is 0 and it matches, we execute the command associated with that kind of file. */ public void DoAutoExec(newfile, oldfile) const char *newfile, *oldfile; { register struct autoexec *ap; register const char *new, *old = oldfile; if ((new = newfile) == NULL) return; if (ap = AutoExecs) do { if ((LookingAt(ap->a_pattern, new, 0)) && (old == NULL || !LookingAt(ap->a_pattern, old, 0))) DoTimes(ExecNow(ap->a_cmd), 1); /* So minor modes don't toggle. We always want them on. */ } while (ap = ap->a_next); } /*===== Get an additional character. =========================================*/ addgetc() { register int c; if (!InJoverc) { Asking = strlen(mesgbuf); c = getch(); Asking = 0; add_mess("%p ", c); } else switch (c = getch()) { /* interpret input from .joverc */ case '\n': return EOF; /* this isn't part of the sequence */ case '\\': /* literal character */ if ((c = getch()) != '\n') break; complain("[Premature end of line]"); case '^': /* control character */ if (isalpha(c = getch())) return (c & 037); /* ^A - ^Z, ^a - ^z */ #ifdef FUNCKEYS if (c == ':') { /* function key id */ union { char name[2]; short id; } u; register short *kmp = &FKeyMap[NFKEYS]; if ((u.name[0] = getch()) != '\n') { u.name[1] = getch(); c = u.id; do { if (c == *--kmp) return (kmp - FKeyMap + MAPSIZE - NFKEYS); } while (kmp != FKeyMap); complain("[Unknown function key name]"); } } #endif /* FUNCKEYS */ if (index("@[\\]^_?", c)) /* others */ return (c ^ '@'); complain("[Unknown control sequence]"); default: break; } return c; } /*===== Describe key. ========================================================*/ DEF_CMD( "describe-key", KeyDesc, NO ) { register const data_obj *dp; s_mess(ProcFmt); if (dp = *MapKey(active_map(), MAP_FOLLOW_ACTIVE_LIST|MAP_ALLOW_PREFIX)) { #ifdef TINY add_mess("is bound to %s.", dp->Name); #else add_mess("is bound to the %s %s.", TypeName[dp->Type & REALTYPEMASK], dp->Name); #endif if (exp_p) do_help(dp); } else { add_mess("is unbound."); } } /*===== Describe a data-object. ==============================================*/ DEF_CMD( "describe-macro", Desc, ARG(MACRO) ); DEF_CMD( "describe-variable", Desc, ARG(VARIABLE) ); DEF_CMD( "describe-command", Desc, ARG(FUNCTION) ) { register const data_obj *dp = FindObj(ObjArg(LastCmd), ProcFmt); s_mess(ProcArgFmt, dp->Name); do_help(dp); } private void do_help(dp) register const data_obj * dp; { static char entry_pat[] = "^:entry \"%s\" \"%s\"$"; register File *fp; register char *sstr; register int Type = (dp->Type & REALTYPEMASK); #ifndef TINY /* first try user's help file, then system-wide command database. */ extern char JoveHlp[]; char orgmesg[MESG_SIZE]; char fnamebuf[FILESIZE]; register const char *helpfile = PathParse(JoveHlp, fnamebuf); strcpy(orgmesg, mesgbuf); #else # define helpfile CmdDb #endif DrawMesg(NO); curstoLL(); flusho(); for (;;) { if (fp = open_file(helpfile, iobuff, F_READ|F_TEXT|F_QUIET)) { sstr = sprint(entry_pat, dp->Name, ((Type)==VARIABLE ? "Variable" : "Command")); f_bsearch(fp, &sstr[1], 8, (int) strlen(sstr) - 2); /* ^ ^^^^^^^^^^^^^^^^^ */ /* |:entry "| |:entry "" ""| */ if (re_fsearch(fp, sstr)) break; s_mess("There is no documentation for \"%s\".", dp->Name); #ifndef TINY f_close(fp); } if (helpfile != CmdDb) { /* retry with system-wide command database */ message(orgmesg); helpfile = CmdDb; continue; #endif #undef helpfile } complain((char *)0); } /* we only get here when an entry was found. */ TOstart(Help, TRUE); if (find_binds(dp, genbuf) || Type != VARIABLE) { /* command or macro, or variable bound to a key. */ if (genbuf[0] == '\0') /* i.e. !find_binds() */ sprintf(genbuf, "ESC X %s%s", (Type == MACRO) ? "execute-macro " : NullStr, dp->Name); Typeout("Type \"%s\" to invoke %s.", genbuf, dp->Name); } else { Typeout("%s => %s", dp->Name, vpr_aux((Variable *) dp)); } Typeout(NULL); while (f_gets(fp, genbuf, LBSIZE) == 0 && numcomp(genbuf, &sstr[1]) < 6) Typeout("%s", genbuf); TOstop(); f_close(fp); } /*===== Describe bindings. ===================================================*/ private int mpr_aux __(( char *_(buf), const Keymap *_(map), int _(c1), const data_obj *_(d1) )); private int mpr_aux(buf, map, c1, d1) char *buf; register const Keymap *map; register int c1; const data_obj *d1; /* "d1 == map[c1]" */ { register int c2 = c1, last_c = -1; register char *fmt = "%p"; /* only print lowercase bindings when different from uppercase */ if (isfunckey(c1) || !islower(c1) || get_bind(map, toupper(c1)) != d1) { while (++c2 < MAPSIZE && get_bind(map, c2) == d1) ; last_c = --c2; switch (c2 - c1) { case 0: /* use default format if upper- and lowercase bindings differ */ if (isfunckey(c1) || !isupper(c1) || get_bind(map, c2 = tolower(c1)) != d1) break; case 1: fmt = "{%p,%p}"; break; default: fmt = "[%p-%p]"; break; } sprintf(buf, fmt, c1, c2); } return last_c; } private void DescMap __(( Keymap *_(initial_map), char *_(buf) )); private void DescMap(initial_map, buf) Keymap *initial_map; char *buf; { register Keymap *map; register int c = 0; register char *bp; register data_obj *dp; register int newc; bp = buf; while (*bp++) ; --bp; do { map = initial_map; do { /* * This inner loop is an optimization of: * if (dp = get_bind(map, c)) { ... } */ if ((dp = get_shallow_bind(map, c)) == NULL) continue; if ((newc = mpr_aux(bp, initial_map, c, dp)) < 0) break; c = newc; /* also print "prefix-n" bindings */ Typeout("%-14s%s", buf, dp->Name); if (TOabort) return; if (map = IsPrefix(dp)) { /* * describe prefixed maps only once * also inhibits infinite recursion. */ if (!(map->Type & VISITED)) { map->Type |= VISITED; strcat(bp, " "); DescMap(map, buf); } } break; } while (K_ACTIVE_NEXT(map)); } while (++c < MAPSIZE); } DEF_CMD( "describe-bindings", DescBindings, NO ) { map_init_visit(); genbuf[0] = '\0'; TOstart("*Key Bindings*", TRUE); DescMap(active_map(), genbuf); TOstop(); } /* [TRH changed March 1990] * fb_aux describes bindings from a prefixed map. * If a single binding is found, it is appended to buf; * multiple bindings are collected in a comma-separated list * surrounded by { } braces. * fb_aux returns the number of bindings found. */ private int fb_aux __(( char *_(buf), Keymap *_(thismap), const data_obj *_(dp) )); private int fb_aux(buf, thismap, dp) char *buf; Keymap *thismap; const data_obj *dp; { register char *bp = buf; register Keymap *map = thismap; int n = 0; if (!(map->Type & VISITED)) { map->Type |= VISITED; *bp++ = '{'; #ifndef FIXED_MAPS if (map->Type & SPARSE) { register sparsemap *sp = (sparsemap *) map->k_bind; do { if (sp->s_cmd != dp) continue; bp += sprintf(bp, "%p,", sp->s_chr); ++n; } while ((++sp)->s_chr >= 0); } else #endif { register data_obj *const *cp = map->k_bind; register int c = 0; do { if (*cp++ != dp) continue; bp += sprintf(bp, "%p,", c); ++n; } while (++c < MAPSIZE); } *--bp = '\0'; /* remove trailing ',' */ if (n) if (n == 1) { /* chuck leading '{' */ register const char *sp; for (bp = buf, sp = bp + 1; *bp++ = *sp++; ) continue; } else *bp++ = '}', *bp = '\0'; } return n; } /* * BUG: find_binds should handle nested Keymaps recursively. */ int find_binds(dp, buf) const data_obj *dp; char *buf; { register int c = 0; register Keymap *map; register char *bp = buf; register data_obj *cp; register int newc; map_init_visit(); do { map = activemap; do { /* * This inner loop is an optimization of: * if (dp = get_bind(map, c)) { ... } */ if ((cp = get_shallow_bind(map, c)) == NULL) continue; if (cp == dp) { if ((newc = mpr_aux(bp, activemap, c, dp)) < 0) break; c = newc; } else if (!(map = IsPrefix(cp)) || !fb_aux(bp + sprintf(bp, "%p ", c), map, dp)) { break; } /* only get here on a match */ while (*bp++) ; --bp; *bp++ = ',', *bp++ = ' '; /* append ", " */ break; } while (K_ACTIVE_NEXT(map)); } while (++c < MAPSIZE); if (bp >= buf + 2) bp -= 2; /* remove superfluous ", " */ *bp = '\0'; return (int) buf[0]; } DEF_CMD( "apropos", Apropos, NO ) { extern char compbuf[]; /* just to be sure... */ register int any = 0; register char *bp = genbuf; re_ask(NullStr, UseRE, "%N: %f (keyword) "); TOstart(Help, TRUE); { register const Command *cp = commands; do { if (re_sindex(cp->Name, 0, compbuf)) { if (!any) { any |= Bit(FUNCTION); Typeout("Commands"); Typeout("--------"); } Typeout((find_binds((data_obj *) cp, bp)) ? ": %-35s(%s)" : ": %s", cp->Name, bp); } } while ((++cp)->Name && !TOabort); } { register const Variable *v = variables; do { if (re_sindex(v->Name, 0, compbuf)) { if (!(any & Bit(VARIABLE))) { if (any) Typeout(NullStr); any |= Bit(VARIABLE); Typeout("Variables"); Typeout("---------"); } Typeout(": set %-30s %s", v->Name, vpr_aux(v)); } } while ((++v)->Name && !TOabort); } { register Macro *m = macros; /* keyboard-macro always present */ do { if (re_sindex(m->Name, 0, compbuf)) { if (!(any & Bit(MACRO))) { if (any) Typeout(NullStr); any |= Bit(MACRO); Typeout("Macros"); Typeout("------"); } Typeout((find_binds((data_obj *) m, bp)) ? ": %-35s(%s)" : ": %s", m->Name, bp); } } while ((m = m->m_nextm) && !TOabort); } TOstop(); } /*===== Prompt for a command and execute it. =================================*/ DEF_CMD( "execute-named-command", Extend, ARG(FUNCTION) ) { ExecCmd(findcom("%N: ")); } /*===== Invoke macros, set variables. ========================================*/ DEF_CMD( "execute-macro", Invoke, ARG(MACRO) ); DEF_CMD( "set", Invoke, ARG(VARIABLE) ) { ExecCmd(FindObj(ObjArg(LastCmd), ProcFmt)); } /*===== Handle Variables. ====================================================*/ private /* deliberately non-const */ char bool_val[2][4] = { "off", "on" }; private const char * vpr_aux(vp) register const Variable *vp; { register int *v_value = vp->v_value; static char buffer[20]; register char *buf = buffer; switch (V_TYPE(vp)) { case V_BASE10: sprintf(buf, "%d", *v_value); break; case V_BASE8: sprintf(buf, "%o", *v_value); break; case V_BOOL: if (True(*v_value)) buf = bool_val[YES]; else buf = bool_val[NO]; break; case V_STRING: case V_FILENAME: case V_REGEXP: buf = *(char **) v_value; break; case V_CHAR: sprintf(buf, "%p", *v_value); break; #ifdef COLOR case V_COLOR: { extern const char * const color_names[]; register int color; if ((color = *v_value) == 0) color = DefColor; sprintf(buf, "%s on %s", color_names[FG_COLOR(color)], color_names[BG_COLOR(color)]); } break; #endif } return buf; } DEF_CMD( "print", PrVar, NO ) { register const Variable *vp = (Variable *) findvar(ProcFmt); register const char *p = vpr_aux(vp); if (exp < 0) ins_str(p, NO); else s_mess("%N: %f %s => %s", vp->Name, p); } void DoSetVar(vp) register const Variable *vp; { static const char SetPrompt[] = "%N: set %f "; register int *v_value = vp->v_value; register int v_type = V_TYPE(vp); char fbuf[FILESIZE], promptbuf[MESG_SIZE]; register const char *prompt = promptbuf; register const char *on_off; LastCmd = (data_obj *) vp; /* so we can use "%f" */ if (V_FLAGS(vp) == V_CONST) complain("[Can't modify constant \"%f\"]"); if (v_type == V_BOOL) on_off = True(*v_value) ? bool_val[OFF] : bool_val[ON]; if (!InJoverc) sprintf((char *) prompt, "%N: set %f (default %s) ", (v_type != V_BOOL) ? vpr_aux(vp) : on_off); /* cannot use `sprint' here due to race condition. */ switch (v_type) { case V_BASE10: case V_BASE8: { register int v_max; # define value v_type /* alias! */ if (exp_p == NO) value = ask_int(prompt, (v_type == V_BASE10) ? 10 : 8, *v_value); else if ((value = exp) < 0) if ((value += *v_value) < 0) value = 0; if ((v_max = V_LENGTH(vp)) && value > v_max) value = v_max; /* silently adjust to bound */ *v_value = value; break; # undef value /* unalias */ } case V_BOOL: { # define value v_type /* alias! */ if (exp_p) { if (exp_p == YES) *v_value = (exp > 0); else *v_value += exp; /* allows you to save/restore modes in macros */ break; } on_off = strlwr(ask(on_off, prompt)); value = 2; do { if (--value < 0) complain("Boolean variables must be ON or OFF."); } while (strcmp(on_off, bool_val[value]) != 0); *v_value = value; s_mess("%s%s", prompt, on_off); break; # undef value /* unalias */ } case V_FILENAME: { set_str((char **) v_value, ask_file(prompt, *(char **) v_value, fbuf)); break; } case V_STRING: { register const char *str; /* Do_ask() so you can set string to "" if you so desire. */ if ((str = do_ask("\r\n", (int(*)()) 0, *(char **) v_value, SetPrompt)) == NULL) str = NullStr; set_str((char **) v_value, str); break; } case V_REGEXP: { set_str((char **) v_value, re_ask(*(char **) v_value, YES, SetPrompt)); break; } case V_CHAR: { # define value v_type /* alias! */ if (exp_p) { *v_value = exp & 0377; break; } message(prompt); value = addgetc(); if (value != AbortChar) *v_value = value; break; # undef value /* unalias */ } #ifdef COLOR case V_COLOR: *v_value = ask_color(*v_value, prompt); break; #endif } switch (V_FLAGS(vp)) { case V_MODELINE: updmode(); break; case V_CLRSCREEN: ClAndRedraw(); break; case V_TTY_RESET: tty_reset(); break; } #ifdef MENUS Modechange++; #endif } /*===== Command completion. ==================================================*/ /* Command completion - possible is an array of strings, prompt is the prompt to use, and flags are ... well read jove.h. [TRH] the UNIQUE code seems obsolete. If flags are RET_STATE, and the user hits what they typed so far is in the Minibuf string. */ DEF_INT( "completion-auto-help", ComplAutoHelp, V_BOOL ) ZERO; private const char * const *Possible; private int comp_value, comp_flags; private int minmatch, /* some useful values for command completion */ maxmatch, lenmatch, lastmatch; match(possible, what) const char * const *possible; const char *what; { register const char *pp; register int i, len, found; lenmatch = strlen(what); minmatch = LBSIZE; /* big */ maxmatch = 0; found = ORIGINAL; for (i = 0; pp = possible[i]; i++) { if (*pp != *what) /* optimization */ len = 0; else if ((len = numcomp(pp, what)) > maxmatch) maxmatch = len; if (len != lenmatch) /* no match */ continue; if (*(pp += len) == '\0') { /* exact match */ minmatch = len; return (lastmatch = i); } if (found == ORIGINAL) { /* first partial match */ minmatch = len + strlen(pp); found = i; } else { /* ambiguous match */ len += numcomp(&possible[lastmatch][len], pp); if (len < minmatch) minmatch = len; found = AMBIGUOUS; } lastmatch = i; } return found; } private int aux_complete __(( int _(c) )); private int aux_complete(c) register int c; { register const char * const *possible = Possible; register int command; if (comp_flags & CASEIND) strlwr(linebuf); /* * c is one of: * ? show possible completions * LF,CR complete command, return if unique match, * else return literal string if RET_STATE flag set. * space complete command, return if unique match. * tab just complete the command. */ switch (c) { case EOF: /* only if bad .joverc */ complain("[Premature end of line]"); case '\r': case '\n': c = 0; default: if ((command = match(possible, linebuf)) >= 0) { if (c != '\t' || InJoverc) /* non-ambiguous match */ break; } else if ((comp_flags & RET_STATE) && (c == 0 || InJoverc)) { if (linebuf[0] == '\0') command = NULLSTRING; break; } else if (command != AMBIGUOUS) { if (InJoverc) complain("[\"%s\" unknown]", linebuf); /* If we're not in the .joverc then let's do something helpful for the user. */ linebuf[maxmatch] = '\0'; rbell(); Eol(); return 1; } else if (c == 0) { /* ambiguous match */ if (InJoverc) complain("[\"%s\" ambiguous]", linebuf); } /* complete the command if we get here */ null_ncpy(linebuf, possible[lastmatch], minmatch); Eol(); if (minmatch != lenmatch) return 1; /* No difference; ring the bell, and eventually list completions iff requested and we were ambiguous. */ rbell(); if (command >= 0 || False(ComplAutoHelp)) return 1; /* fall through... */ case '?': if (InJoverc) complain((char *)0); # define length command /* alias! */ /* kludge: copy because if we're using UseBuffers, linebuf gets written all over */ length = appcpy(Minibuf, linebuf) - Minibuf; /* Optimization */ TOstart("*Completion*", TRUE); /* for now ... */ for (; *possible; *possible++) if (numcomp(*possible, Minibuf) >= length) { Typeout("%s", *possible); if (TOabort) break; } # undef length TOstop(); return 1; } comp_value = command; return 0; } /* * Apr-89 [TRH] modified calling sequence */ /* VARARGS3 */ int DEFVARG(complete, (const char * const possible[], int flags, const char *fmt, ...), (possible, flags, fmt, va_alist) const char * const possible[]; const char *fmt;) { va_register va_list ap; register const char * const *o_possible = Possible; /* to make it re-entrant */ register int o_flags = comp_flags; VA_INIT_PROPAGATE Possible = possible; comp_flags = flags; va_begin(ap, fmt); do_ask("\r\n \t?", aux_complete, NullStr, VA_PROPAGATE(fmt, ap)); va_end(ap); Possible = o_possible; comp_flags = o_flags; return comp_value; } /*===== Execute a ".joverc" command file. ====================================*/ DEF_CMD( "source", Source, NO ) { char buf[FILESIZE]; register const char *com = ask_file((char *) 0, JoveRc, buf); if (joverc(com) == NIL) complain(IOerr("read", com)); } /* * Put caught error message (in mesgbuf) in a buffer. * Format a la "grep" so we can use error parsing on it. */ void PutErrInBuf(bufname, filename, lno, lbuf) const char *bufname, *filename, *lbuf; { static Buffer *save ZERO; register Buffer *errbuf; register char *file_line = sprint((lno) ? "%s:%d:" : "%s:", pr_name(filename), lno); if (save != NULL) /* we got an error in here */ f_mess("%s%s", file_line, lbuf); else { save = curbuf; SetBuf(errbuf = do_select((Window *) 0, bufname)); SETBUFTYPE(errbuf, B_SCRATCH); ToLast(); /* * Don't use a single sprintf here since that has a limited * maximum string length. */ ins_str(file_line, NO); ins_str(lbuf, YES); if (!bolp(errbuf)) LineInsert(1); ins_str(sprint("\t%s\n", mesgbuf), NO); } SetBuf(save); save = NULL; } private do_if __(( char *_(cmd) )); private do_if(cmd) char *cmd; { register int status; #if unix register int pid; #ifdef IPROCS # ifdef PIPEPROCS int state = kbd(OFF); # else sighold(SIGCHLD); # endif #endif if ((pid = vfork()) <= 0) { char *args[12]; register char *cp = cmd, **ap = args; #ifdef IPROCS # ifndef PIPEPROCS sigrelse(SIGCHLD); # endif #endif if (pid < 0) #ifdef IPROCS # ifdef PIPEPROCS kbd(state), # endif #endif complain("[Fork failed]"); /* build argument vector */ *ap++ = cp; while (*cp) { if (*cp++ != ' ') continue; cp[-1] = '\0'; *ap++ = cp; } *ap = 0; ap = args; close(0); /* we want reads to fail */ /* close(1); but not writes or ioctl's close(2); */ execvp(ap[0], ap); exec_fail((char *)0); /* signals exec error (see below) */ } status = dowait(pid); #ifdef IPROCS # ifdef PIPEPROCS kbd(state); # else sigrelse(SIGCHLD); # endif #endif if (status == -ENOEXEC) complain("[Exec failed]"); #else /* unix */ status = system(cmd); #endif /* unix */ if (status < 0) complain("[Exit %d]", status); return (status == EXIT_SUCCESS); } DEF_REF( "digit", DigitCmd ); private void do_command __(( char *_(lp) )); private void do_command(lp) register char *lp; { register int c = *lp; /* look ahead */ Inputp = lp; exp = 1; exp_p = NO; if (isdigit(c) || c == '-') { getch(); /* i.e., LastKeyStruck = c, Inputp++; */ ExecCmd(DigitCmd); getch(); /* skip Ungetc'd char */ } Extend(); } joverc(file) const char *file; { char buf[BLKSIZ]; register char *lp; /* The following weird construct tries to outsmart optimizing compilers (like ST Turbo C) that puts as many variables in registers as possible. That would be okay weren't it for the longjmp() restoring the context to the situation at the time of the initializing setjmp(). So we want these variables on the stack! (BTW the volatile modifier has no effect (for ST Turbo C at least) but maybe I just don't understand what this modifier *should* do). */ volatile struct { int i; } /* so that they are forced on the stack */ _lnum, _IfNesting, _IfStatus; # define lnum _lnum.i # define IfNesting _IfNesting.i # define IfStatus _IfStatus.i /* If States (other than FALSE and TRUE) */ # define DONE 2 # define SAWELSE 4 # define IfTrue(s) ((s)&TRUE) # define IfDone(s) ((s)&DONE) # define IfElseSeen(s) ((s)&SAWELSE) # define MaxIfNesting 10 /* size of `if' nesting stack */ volatile int IfStack[MaxIfNesting]; register File *fp; int s_quiet = Quiet; Savejmp savejmp; lp = buf; if ((fp = open_file(PathParse(file, lp), lp, F_READ|F_TEXT|F_QUIET)) == NULL) return NIL; /* [TRH] increment InJoverc instead of absolute assignment, to allow nested "source" directives */ InJoverc++; lnum = 0; IfStatus = TRUE; IfNesting = 0; Quiet = NO; /* Catch any errors, here, and do the right thing with them, and then restore the error handler to whoever did a setjmp last. */ switch (push_env(savejmp)) { case QUIT: pop_env(savejmp); Leave(); default: if (False(Quiet)) PutErrInBuf("*RC errors*", file, lnum, genbuf); Asking = 0; case 0: break; } /* [TRH] As far as I can see, it is safe to use genbuf as the line buffer, since at most a single command is executed from it, and each command parses its arguments BEFORE it does anything else with genbuf. (this saves some stack space; which only matters on DOS systems...) */ while (f_gets(fp, lp = genbuf, LBSIZE - 1) == 0 /*!EOF*/) { { static const struct builtin { char bi_name[6]; short bi_len; } Builtins[] = { "if", 2, "elif", 4, "else", 4, "endif", 5 }; # define builtin_cases case 'i': case 'e' # define IF 0 # define ELIF 1 # define ELSE 2 # define ENDIF 3 # define CMD 4 register const struct builtin *cmd; lnum++; while (isspace(*lp++)) ; /* skip leading blanks */ switch (tolower(*--lp)) { case '\0': /* empty line */ case '#': /* comment */ continue; builtin_cases: /* to speed things up, only lookup directives if there is a possibility for a match. */ cmd = Builtins; do { if (casencmp(lp, cmd->bi_name, cmd->bi_len) == 0) goto found_builtin; /* break out of switch */ } while (++cmd != &Builtins[CMD]); default: if (IfTrue(IfStatus)) do_command(lp); continue; } found_builtin: if (cmd == &Builtins[IF]) { if (IfNesting == MaxIfNesting) complain("[`if's nested too deeply]"); if (!IfTrue(IfStack[IfNesting++] = IfStatus)) { IfStatus = DONE; /* ...to prevent nested `elif's in a false branch from being evaluated. */ continue; } } else { if (!IfNesting) complain("[Unexpected `%s']", cmd->bi_name); if (cmd == &Builtins[ENDIF]) { IfStatus = IfStack[--IfNesting]; continue; } # ifndef TINY if (IfElseSeen(IfStatus)) complain("[Unexpected `%s']", cmd->bi_name); # endif if (IfDone(IfStatus)) continue; /* transition: FALSE => TRUE and TRUE => DONE */ if (IfDone(++IfStatus)) continue; if (cmd == &Builtins[ELSE]) { # ifndef TINY IfStatus += SAWELSE; # endif continue; } } /* only get here on (IF || ELIF) && IsTrue(IfStatus) */ /* so we won't be screwed by complain */ IfStatus = DONE; /* * This rather intimidating Regular Expression defines the * conditions we are prepared to handle. * 1. ( in ) * - true if value of matches * 2. ( ) * - true if STRING comparison satisfies * 3. () * - test presence of ENVIRONMENT variables, * OR non-zeroness of numeric JOVE variables, * OR truth value of boolean JOVE variables, * 4. term in * - special case for backward compatibility * 5. command [parameters] * - test exit status of command. * 6. ! * - logical NOT operator. * If starts with a dollar sign `$' it is * considered an environment variable; anything else is * thought an internal JOVE variable or -constant. */ if (!LookingAt("\ *\\(!?\\) *(\\([^ )=<>!]+\\)\ *\\{\\(\\{in,==,!=,<=,<,>=,>\\}\\) *\\(.+\\)),)\\}[\t ]*\\{#,$\\}\ \\|\ *\\(!?\\) *\\{\\(term\\) +\\(in\\) +,\\}\\([^(].*\\)$", lp, cmd->bi_len)) complain("[`If' syntax error]"); } { char not[2], op[3]; register const char *var; putmatch(1, not, sizeof not); putmatch(2, (char *)(var = Minibuf), sizeof Minibuf); putmatch(3, op, sizeof op); putmatch(4, lp, LBSIZE); if (var[0]) { /* "(var op value)" or "(var)" */ if (var[0] == '$') { /* environment variable */ if ((var = getenv(&var[1])) == NULL) { var = "0"; if (!isdigit(lp[0])) var = NullStr; } } else { /* internal JOVE variable */ Inputp = (char *) var; var = vpr_aux((Variable *) findvar(NullStr)); if (!op[0]) { /* (var) */ lp = "0"; if (!isdigit(var[0])) { lp = bool_val[YES]; not[0] ^= '!'; } } } /* (how ad-hoc can you get...) */ if (op[0] == 'i') { /* in */ IfStatus = LookingAt(lp, var, 0); } else { IfStatus = strcmp(var, lp); switch (op[0]) { case '=': /* == */ not[0] ^= '!'; break; # if EXTEND_DEBUG case '!': /* != */ case '\0': /* (var), implicit != */ break; default: complain("BUG: unknown `if' operator \"%s\"", op); # endif case '>': /* >, >= */ IfStatus = -IfStatus; case '<': /* <, <= */ if (op[1]) IfStatus--; if (IfStatus > 0) IfStatus = 0; /* * We can get away with this since: * 1a. (a > 0) <=> (-a < 0) * 1b. (a >= 0) <=> (-a <= 0) * and, with integer arithmetic: * 2. (a <= 0) <=> (a-1 < 0) */ } } if (IfStatus) IfStatus = 1; } else { IfStatus = do_if(lp); } if (not[0]) IfStatus ^= 1; /* negate */ } } if (IfNesting) { IfNesting = 0; complain("[Missing `endif']"); } f_close(fp); pop_env(savejmp); Inputp = 0; Asking = 0; InJoverc--; Quiet = s_quiet; return !NIL; #undef lnum #undef IfNesting #undef IfStatus #undef DONE #undef SAWELSE #undef IfTrue #undef IfDone #undef IfElseSeen #undef MaxIfNesting #undef builtin #undef IF #undef ELIF #undef ELSE #undef ENDIF #undef CMD } /*===== Command-line option handling. ========================================*/ #ifdef CLIOPTIONS typedef struct option Option; struct option { char *o_option; /* option name--must start with '-' or '+' */ char *o_cmd; /* command to be executed. */ short o_force; /* force read of last buffer */ Option *o_next; /* link */ }; private Option *CliOpts; /* * Define a command-line option. This is only of any use from within * your ".joverc" file since command-line options are (obviously) never * used after the command line is processed. * Key escape sequences in the command string are recognized; * occurrences of $1 .. $9 are replaced with the Nth next argument from * the command line; $$ is a single literal dollar; in all other cases * the $ is left undisturbed. */ DEF_CMD( "command-line-option", CliDefine, NO ) _IF(def CLIOPTIONS) { register char *s; register Option *op; register int c; if (!InJoverc) { /* not useful when not in .joverc */ # ifdef SMALL rbell(); # else if (op = CliOpts) { TOstart(Help, YES); Typeout(" Option Command (* means force read current buffer)"); Typeout(" ------ -------"); do { Typeout("%8s %c %s", op->o_option, (op->o_force) ? '*' : ' ', op->o_cmd); } while (op = op->o_next); TOstop(); } else message("[No %fs defined]"); # endif return; } s = do_ask(" ", (int(*)())0, (char *)0, ProcFmt); # ifndef TINY if (!(*s == '-' || *s == '+')) complain("[Option must start with `-' or `+']"); # endif op = (Option *) emalloc(sizeof(Option)); op->o_option = copystr(s); /* addgetc does the key sequence interpretation */ for (s = genbuf; (c = addgetc()) > 0; *s++ = c) ; *s = '\0'; op->o_cmd = copystr(genbuf); op->o_force = exp_p; op->o_next = CliOpts; CliOpts = op; } /* * Delete CLI option definitions. * This is called by the command-argument interpreter after all * arguments have been processed. */ void CliDelete() { # ifdef SMALL register Option *op; while (op = CliOpts) { CliOpts = op->o_next; free(op->o_cmd); free(op->o_option); free(op); } # else /* Keep them around to support help. */ # endif } /* * Scan for command line option. If one is found, execute the * command line associated with it. * Returns the number of arguments consumed, or -1 if option does not * match any user-defined option. */ int CliLookup(opt, argc, argv) char *opt; /* option to match */ int argc; /* number of args left (including current arg!)*/ char **argv; /* points to next arg */ { char lbuf[LBSIZE]; register Option *op; Savejmp savejmp; Buffer *savebuf; int nargs; /* number of arguments to skip */ if (op = CliOpts) do { if (strcmp(op->o_option, opt) != 0) continue; /* Force read of most-recently created buffer */ if (op->o_force) { register Buffer *b = curbuf; savebuf = b; while (b->b_next) b = b->b_next; SetBuf(b); } /* build command; do dollar argument substitutions */ { register char *s = op->o_cmd, *d = lbuf; register int i; nargs = 0; /* do dollar substitutions */ while(*d = *s++) { if (*d++ != '$') continue; if ((i = *s++) == '\0') break; if (i == '$') /* $$ => literal dollar */ continue; if ((unsigned)(i -= '0') > 9) /* not $1 .. $9 */ continue; --d; /* backup over '$' */ if (i > nargs) nargs = i; if (i >= argc) /* out of args; complain later */ break; d = appcpy(d, argv[i - 1]); } *d++ = '\n'; /* make sure cmd ends with newline */ *d = '\0'; } switch(push_env(savejmp)) { case QUIT: pop_env(savejmp); Leave(); default: PutErrInBuf("*CLI errors*", op->o_option, nargs, lbuf); break; case 0: if (nargs >= argc) complain("[missing argument]"); { register char *lp = lbuf; do do_command(lp); while ((lp = Inputp) && *lp); } } pop_env(savejmp); Inputp = NULL; Asking = 0; if (op->o_force) SetBuf(savebuf); return nargs; } while (op = op->o_next); return -1; } #endif /* CLIOPTIONS */ /*===== Color user interface. ================================================*/ #ifdef COLOR const char * const color_names[] = { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", NullStr, /* for default */ NULL }; #if 0 /* don't expand these because they don't obey C-syntax */ DEF_INT( "color-of-file-buffers", Gcolor[B_FILE], V_COLOR ) _IF(def COLOR) DEF_INT( "color-of-message-line", Gcolor[0], V_COLOR ) _IF(def COLOR) DEF_INT( "color-of-process-buffers", Gcolor[B_PROCESS], V_COLOR ) _IF(def COLOR) DEF_INT( "color-of-scratch-buffers", Gcolor[B_SCRATCH], V_COLOR ) _IF(def COLOR) DEF_INT( "color-of-i-process-buffers", Gcolor[B_IPROCESS], V_COLOR ) _IF(def COLOR)_IF(def IPROCS) #endif DEF_INT( "exit-color", ExitColor, V_COLOR ) _IF(def COLOR) ZERO; DEF_INT( "default-color", DefColor, V_COLOR ) _IF(def COLOR) = DEF_COLOR; public int Gcolor[1 + B_NTYPES] ZERO; private int ask_color(def, prompt) const char *prompt; { if (def == 0) def = DefColor; { register int bg_color = BG_COLOR(def), fg_color = FG_COLOR(def), n = exp; register unsigned i; if (exp_p == YES) { if (n < 0) bg_color = -n - 1; else fg_color = n - 1; } else { if (exp_p == NO) n = 0; if (n >= 0 && (i = complete(color_names, CASEIND, "%sText: ", prompt)) < NCOLORS) fg_color = i; if (n <= 0 && (i = complete(color_names, CASEIND, "%sBackground: ", prompt)) < NCOLORS) bg_color = i; } return MK_COLOR(fg_color, bg_color); } } DEF_CMD( "color", NonExisting, NO ) _IF(ndef COLOR); DEF_CMD( "color", BufColor, NO ) _IF(def COLOR) { register int def; char prompt[132]; if ((def = curbuf->b_color) == 0) def = DefColor; sprintf(prompt, "%N: %f (default %s on %s) ", color_names[FG_COLOR(def)], color_names[BG_COLOR(def)]); curbuf->b_color = ask_color(def, prompt); } #endif /* COLOR */ /*====================================================================== * $Log: extend.c,v $ * Revision 14.32.0.12 1994/06/24 17:55:02 tom * (Help[],DescBindings,aux_complete,joverc,CliLookup): * fix name of scratch buffer. * * Revision 14.32.0.10 1994/04/22 18:24:22 tom * (DefAutoExec): store new definitions at end of list; * (complete): use `va_register va_list'. * * Revision 14.32.0.8 1993/11/01 18:05:42 tom * (Apropos): use UseRE in call to re_ask. * * Revision 14.32.0.1 1993/07/07 12:20:55 tom * (F_TEXT): new option for f_open et al. * * Revision 14.32 1993/06/22 03:55:53 tom * (addgetc, do_help, joverc): some cleanup. * * Revision 14.31 1993/02/17 01:07:03 tom * standardize DEBUG flags; add help typeout to DefAutoExec(); add sanity * check for command-line options; remove (void) casts; lotsa random * optimizations. * * Revision 14.30 1993/02/05 00:07:27 tom * cleanup whitespace; some random optimizations; improve integration of * regular keymaps and sparsemaps. * * Revision 14.29 1992/12/29 14:09:46 tom * avoid NULL-pointer bug. * * Revision 14.28 1992/10/02 17:18:05 tom * "describe-key" message format changed; integrate sparsemaps with full-size * keymaps; support user-variable "default-color". * * Revision 14.27 1992/09/21 14:11:23 tom * use DEF_REF(). * * Revision 14.26 1992/08/26 23:56:52 tom * display current variable's value in do_help(); make vpr_aux() private; * add RCS directives. * */