/*************************************************************************** * This program is Copyright (C) 1986, 1987, 1988 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. * ***************************************************************************/ #include "jove.h" #include "io.h" #include "termcap.h" #include "ctype.h" #ifdef JOB_CONTROL # include <signal.h> #endif #ifdef MAC # include "mac.h" #else # include <varargs.h> #endif #ifdef MSDOS #include <process.h> #endif #ifdef MAC # undef private # define private #endif #ifdef LINT_ARGS private void fb_aux(data_obj *, data_obj **, char *, char *), find_binds(data_obj *, char *), vpr_aux(struct variable *, char *); #else private void fb_aux(), find_binds(), vpr_aux(); #endif /* LINT_ARGS */ #ifdef MAC # undef private # define private static #endif int InJoverc = 0; extern int getch(), getchar(); /* Auto execute code */ #define NEXECS 20 private struct { char *a_pattern; data_obj *a_cmd; } AutoExecs[NEXECS] = {0}; private int ExecIndex = 0; /* Command auto-execute. */ void CAutoExec() { DefAutoExec(findcom); } /* Macro auto-execute. */ void MAutoExec() { DefAutoExec(findmac); } /* VARARGS0 */ void DefAutoExec(proc) #ifdef LINT_ARGS data_obj *(*proc)(char *); #else data_obj *(*proc)(); #endif { data_obj *d; char *pattern; int i; if (ExecIndex >= NEXECS) complain("Too many auto-executes, max %d.", NEXECS); if ((d = (*proc)(ProcFmt)) == 0) return; pattern = do_ask("\r\n", (int (*)()) 0, (char *) 0, ": %f %s ", d->Name); if (pattern != 0) for (i = 0; i < ExecIndex; i++) if ((AutoExecs[i].a_cmd == d) && (strcmp(pattern, AutoExecs[i].a_pattern) == 0)) return; /* eliminate duplicates */ AutoExecs[ExecIndex].a_pattern = copystr(pattern); AutoExecs[ExecIndex].a_cmd = d; ExecIndex += 1; } /* 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, OR if the pattern is 0 (none was specified) then, we execute the command associated with that kind of file. */ void DoAutoExec(new, old) register char *new, *old; { register int i; set_arg_value(1); for (i = 0; i < ExecIndex; i++) if ((AutoExecs[i].a_pattern == 0) || ((new != 0 && LookingAt(AutoExecs[i].a_pattern, new, 0)) && (old == 0 || !LookingAt(AutoExecs[i].a_pattern, old, 0)))) ExecCmd(AutoExecs[i].a_cmd); } void BindAKey() { BindSomething(findcom); } void BindMac() { BindSomething(findmac); } extern void EscPrefix(), CtlxPrefix(), MiscPrefix(); data_obj ** IsPrefix(cp) data_obj *cp; { #ifdef MAC void (*proc)(); #else int (*proc)(); #endif if (cp == 0 || (cp->Type & TYPEMASK) != FUNCTION) return 0; proc = ((struct cmd *) cp)->c_proc; if (proc == EscPrefix) return pref1map; if (proc == CtlxPrefix) return pref2map; if (proc == MiscPrefix) return miscmap; return 0; } void UnbindC() { char *keys; data_obj **map = mainmap; keys = ask((char *) 0, ProcFmt); for (;;) { if (keys[1] == '\0') break; if ((map = IsPrefix(map[*keys])) == 0) break; keys += 1; } if (keys[1] != 0) complain("That's not a legitimate key sequence."); map[keys[0]] = 0; } int addgetc() { int c; if (!InJoverc) { Asking = strlen(mesgbuf); c = getch(); Asking = 0; add_mess("%p ", c); } else { c = getch(); if (c == '\n') return EOF; /* this isn't part of the sequence */ else if (c == '\\') { if ((c = getch()) == LF) complain("[Premature end of line]"); } else if (c == '^') { if ((c = getch()) == '?') c = RUBOUT; else if (isalpha(c) || index("@[\\]^_", c)) c = CTL(c); else complain("[Unknown control character]"); } } return c; } void BindWMap(map, lastkey, cmd) data_obj **map, *cmd; { data_obj **nextmap; int c; c = addgetc(); if (c == EOF) { if (lastkey == EOF) complain("[Empty key sequence]"); complain("[Premature end of key sequence]"); } else { if (nextmap = IsPrefix(map[c])) BindWMap(nextmap, c, cmd); else { map[c] = cmd; #ifdef MAC ((struct cmd *) cmd)->c_key = c; /* see about_j() in mac.c */ if(map == mainmap) ((struct cmd *) cmd)->c_map = F_MAINMAP; else if(map == pref1map) ((struct cmd *) cmd)->c_map = F_PREF1MAP; else if(map == pref2map) ((struct cmd *) cmd)->c_map = F_PREF2MAP; #endif } } } /* VARARGS0 */ void BindSomething(proc) #ifdef LINT_ARGS data_obj *(*proc)(char *); #else data_obj *(*proc)(); #endif { data_obj *d; if ((d = (*proc)(ProcFmt)) == 0) return; s_mess(": %f %s ", d->Name); BindWMap(mainmap, EOF, d); } /* Describe key */ void DescWMap(map, key) data_obj **map; { data_obj *cp = map[key], **prefp; if (cp == 0) add_mess("is unbound."); else if (prefp = IsPrefix(cp)) DescWMap(prefp, addgetc()); else add_mess("is bound to %s.", cp->Name); } void KeyDesc() { s_mess(ProcFmt); DescWMap(mainmap, addgetc()); } void DescCom() { data_obj *dp; char pattern[100], doc_type[40], *the_type, *file = CmdDb; File *fp; int is_var; if (!strcmp(LastCmd->Name, "describe-variable")) { dp = (data_obj *) findvar(ProcFmt); the_type = "Variable"; is_var = YES; } else { dp = (data_obj *) findcom(ProcFmt); the_type = "Command"; is_var = NO; } if (dp == 0) return; fp = open_file(file, iobuff, F_READ, COMPLAIN, QUIET); Placur(ILI, 0); flusho(); sprintf(pattern, "^:entry \"%s\" \"\\([^\"]*\\)\"", dp->Name); TOstart("Help", TRUE); for (;;) { if (f_gets(fp, genbuf, LBSIZE) == EOF) { Typeout("There is no documentation for \"%s\".", dp->Name); goto outahere; } if ((strncmp(genbuf, ":entry", 6) == 0) && (LookingAt(pattern, genbuf, 0))) { char type[64]; putmatch(1, type, sizeof type); if (strcmp(type, the_type) == 0) break; } } /* found it ... let's print it */ putmatch(1, doc_type, sizeof doc_type); if (is_var == YES) Typeout(dp->Name); else { char binding[128]; find_binds(dp, binding); if (blnkp(binding)) Typeout("To invoke %s, type \"ESC X %s<cr>\".", dp->Name, dp->Name); else Typeout("Type \"%s\" to invoke %s.", binding, dp->Name); } Typeout(""); while (f_gets(fp, genbuf, LBSIZE) != EOF) if (strncmp(genbuf, ":entry", 6) == 0) goto outahere; else Typeout("%s", genbuf); outahere: f_close(fp); TOstop(); } void DescBindings() { extern void Typeout(); TOstart("Key Bindings", TRUE); DescMap(mainmap, NullStr); TOstop(); } extern int specialmap; void DescMap(map, pref) data_obj **map; char *pref; { int c1, c2 = 0, numbetween; char keydescbuf[40]; data_obj **prefp; #ifdef IBMPC specialmap = (map == miscmap); #endif for (c1 = 0; c1 < NCHARS && c2 < NCHARS; c1 = c2 + 1) { c2 = c1; if (map[c1] == 0) continue; while (++c2 < NCHARS && map[c1] == map[c2]) ; c2 -= 1; numbetween = c2 - c1; if (numbetween == 1) sprintf(keydescbuf, "%s {%p,%p}", pref, c1, c2); else if (numbetween == 0) sprintf(keydescbuf, "%s %p", pref, c1); else sprintf(keydescbuf, "%s [%p-%p]", pref, c1, c2); if ((prefp = IsPrefix(map[c1])) && (prefp != map)) DescMap(prefp, keydescbuf); else Typeout("%-18s%s", keydescbuf, map[c1]->Name); } } private void find_binds(dp, buf) data_obj *dp; char *buf; { char *endp; buf[0] = '\0'; fb_aux(dp, mainmap, (char *) 0, buf); endp = buf + strlen(buf) - 2; if ((endp > buf) && (strcmp(endp, ", ") == 0)) *endp = '\0'; } private void fb_aux(cp, map, prefix, buf) register data_obj *cp, **map; char *buf, *prefix; { int c1, c2; char *bufp = buf + strlen(buf), prefbuf[20]; data_obj **prefp; #ifdef IBMPC specialmap = (map == miscmap); #endif for (c1 = c2 = 0; c1 < NCHARS && c2 < NCHARS; c1 = c2 + 1) { c2 = c1; if (map[c1] == cp) { while (++c2 < NCHARS && map[c1] == map[c2]) ; c2 -= 1; if (prefix) sprintf(bufp, "%s ", prefix); bufp += strlen(bufp); switch (c2 - c1) { case 0: sprintf(bufp, "%p, ", c1); break; case 1: sprintf(bufp, "{%p,%p}, ", c1, c2); break; default: sprintf(bufp, "[%p-%p], ", c1, c2); break; } } if ((prefp = IsPrefix(map[c1])) && (prefp != map)) { sprintf(prefbuf, "%p", c1); fb_aux(cp, prefp, prefbuf, bufp); } bufp += strlen(bufp); } } void Apropos() { register struct cmd *cp; register struct macro *m; register struct variable *v; char *ans; int anyfs = NO, anyvs = NO, anyms = NO; char buf[256]; ans = ask((char *) 0, ": %f (keyword) "); TOstart("Help", TRUE); for (cp = commands; cp->Name != 0; cp++) if (sindex(ans, cp->Name)) { if (anyfs == 0) { Typeout("Commands"); Typeout("--------"); } find_binds((data_obj *) cp, buf); if (buf[0]) Typeout(": %-35s(%s)", cp->Name, buf); else Typeout(": %s", cp->Name); anyfs = YES; } if (anyfs) Typeout(NullStr); for (v = variables; v->Name != 0; v++) if (sindex(ans, v->Name)) { if (anyvs == 0) { Typeout("Variables"); Typeout("---------"); } anyvs = YES; vpr_aux(v, buf); Typeout(": set %-26s%s", v->Name, buf); } if (anyvs) Typeout(NullStr); for (m = macros; m != 0; m = m->m_nextm) if (sindex(ans, m->Name)) { if (anyms == 0) { Typeout("Macros"); Typeout("------"); } anyms = YES; find_binds((data_obj *) m, buf); if (buf[0]) Typeout(": %-35s(%s)", m->Name, buf); else Typeout(": %-35s%s", "execute-macro", m->Name); } TOstop(); } void Extend() { data_obj *d; if (d = findcom(": ")) ExecCmd(d); } /* Read a positive integer from CP. It must be in base BASE, and complains if it isn't. If allints is nonzero, all the characters in the string must be integers or we return -1; otherwise we stop reading at the first nondigit. */ int chr_to_int(cp, base, allints, result) register char *cp; register int *result; { register int c; int value = 0, sign; if ((c = *cp) == '-') { sign = -1; cp += 1; } else sign = 1; while (c = *cp++) { if (!isdigit(c)) { if (allints == YES) return INT_BAD; break; } c = c - '0'; if (c >= base) complain("You must specify in base %d.", base); value = value * base + c; } *result = value * sign; return INT_OKAY; } int ask_int(prompt, base) char *prompt; int base; { char *val = ask((char *) 0, prompt); int value; if (chr_to_int(val, base, YES, &value) == INT_BAD) complain("That's not a number!"); return value; } private void vpr_aux(vp, buf) register struct variable *vp; char *buf; { switch (vp->v_flags & V_TYPEMASK) { case V_BASE10: sprintf(buf, "%d", *(vp->v_value)); break; case V_BASE8: sprintf(buf, "%o", *(vp->v_value)); break; case V_BOOL: sprintf(buf, (*(vp->v_value)) ? "on" : "off"); break; case V_STRING: case V_FILENAME: sprintf(buf, "%s", (char *) vp->v_value); break; case V_CHAR: sprintf(buf, "%p", *(vp->v_value)); break; } } void PrVar() { struct variable *vp; char prbuf[256]; if ((vp = (struct variable *) findvar(ProcFmt)) == 0) return; vpr_aux(vp, prbuf); s_mess(": %f %s => %s", vp->Name, prbuf); } void SetVar() { struct variable *vp; char *prompt; if ((vp = (struct variable *) findvar(ProcFmt)) == 0) return; prompt = sprint(": %f %s ", vp->Name); switch (vp->v_flags & V_TYPEMASK) { case V_BASE10: case V_BASE8: { int value; value = ask_int(prompt, ((vp->v_flags & V_TYPEMASK) == V_BASE10) ? 10 : 8); *(vp->v_value) = value; break; } case V_BOOL: { char *def = *(vp->v_value) ? "off" : "on", *on_off; int value; on_off = ask(def, prompt); if (casecmp(on_off, "on") == 0) value = ON; else if (casecmp(on_off, "off") == 0) value = OFF; else complain("Boolean variables must be ON or OFF."); *(vp->v_value) = value; #ifdef MAC MarkVar(vp,-1,0); /* mark the menu item */ #endif s_mess("%s%s", prompt, value ? "on" : "off"); break; } case V_FILENAME: { char fbuf[FILESIZE]; sprintf(&prompt[strlen(prompt)], "(default %s) ", vp->v_value); (void) ask_file(prompt, (char *) vp->v_value, fbuf); strcpy((char *) vp->v_value, fbuf); break; } case V_STRING: { char *str; /* Do_ask() so you can set string to "" if you so desire. */ str = do_ask("\r\n", (int (*)()) 0, (char *) vp->v_value, prompt); if (str == 0) str = NullStr; strcpy((char *) vp->v_value, str); /* ... and hope there is enough room. */ break; } case V_CHAR: f_mess(prompt); *(vp->v_value) = addgetc(); break; } if (vp->v_flags & V_MODELINE) UpdModLine = YES; if (vp->v_flags & V_CLRSCREEN) { #ifdef IBMPC setcolor(Fgcolor, Bgcolor); #endif /* IBMPC */ ClAndRedraw(); } if (vp->v_flags & V_TTY_RESET) tty_reset(); } /* Command completion - possible is an array of strings, prompt is the prompt to use, and flags are ... well read jove.h. If flags are RET_STATE, and the user hits <return> what they typed so far is in the Minibuf string. */ private char **Possible; private int comp_value, comp_flags; int aux_complete(c) { int command, length, i; if (comp_flags & CASEIND) { char *lp; for (lp = linebuf; *lp != '\0'; lp++) #if (defined(IBMPC) || defined(MAC)) lower(lp); #else if (isupper(*lp)) *lp = tolower(*lp); #endif } switch (c) { case EOF: comp_value = -1; return 0; case '\r': case '\n': command = match(Possible, linebuf); if (command >= 0) { comp_value = command; return 0; /* tells ask to stop */ } if (eolp() && bolp()) { comp_value = NULLSTRING; return 0; } if (comp_flags & RET_STATE) { comp_value = command; return 0; } if (InJoverc) complain("[\"%s\" unknown]", linebuf); rbell(); break; case '\t': case ' ': { int minmatch = 1000, maxmatch = 0, numfound = 0, lastmatch = -1, length = strlen(linebuf); for (i = 0; Possible[i] != 0; i++) { int this_len; this_len = numcomp(Possible[i], linebuf); maxmatch = max(maxmatch, this_len); if (this_len >= length) { if (numfound) minmatch = min(minmatch, numcomp(Possible[lastmatch], Possible[i])); else minmatch = strlen(Possible[i]); numfound += 1; lastmatch = i; if (strcmp(linebuf, Possible[i]) == 0) break; } } if (numfound == 0) { rbell(); if (InJoverc) complain("[\"%s\" unknown]", linebuf); /* If we're not in the .joverc then let's do something helpful for the user. */ if (maxmatch < length) { char *cp; cp = linebuf + maxmatch; *cp = 0; Eol(); } break; } if (c != '\t' && numfound == 1) { comp_value = lastmatch; return 0; } null_ncpy(linebuf, Possible[lastmatch], minmatch); Eol(); if (minmatch == length) /* No difference */ rbell(); break; } case '?': if (InJoverc) complain((char *) 0); /* kludge: in case we're using UseBuffers, in which case linebuf gets written all over */ strcpy(Minibuf, linebuf); length = strlen(Minibuf); TOstart("Completion", TRUE); /* for now ... */ for (i = 0; Possible[i]; i++) if (numcomp(Possible[i], Minibuf) >= length) { Typeout(Possible[i]); if (TOabort != 0) break; } TOstop(); break; } return !FALSE; } int complete(possible, prompt, flags) register char *possible[]; char *prompt; { Possible = possible; comp_flags = flags; (void) do_ask("\r\n \t?", aux_complete, NullStr, prompt); return comp_value; } int match(choices, what) register char **choices, *what; { register int len; int i, found = 0, save, exactmatch = -1; len = strlen(what); if (len == 0) return NULLSTRING; for (i = 0; choices[i]; i++) { if (strncmp(what, choices[i], len) == 0) { if (strcmp(what, choices[i]) == 0) exactmatch = i; save = i; found += 1; /* found one */ } } if (found == 0) save = ORIGINAL; else if (found > 1) { if (exactmatch != -1) save = exactmatch; else save = AMBIGUOUS; } return save; } void Source() { char *com, *getenv(), buf[FILESIZE]; #ifndef MSDOS sprintf(buf, "%s/.joverc", getenv("HOME")); #else /* MSDOS */ if (com = getenv("JOVERC")) strcpy(buf, com); else strcpy(buf, Joverc); #endif /* MSDOS */ com = ask_file((char *) 0, buf, buf); if (joverc(buf) == 0) complain(IOerr("read", com)); } void BufPos() { register Line *lp = curbuf->b_first; register int i, dotline; long dotchar, nchars; for (i = nchars = 0; lp != 0; i++, lp = lp->l_next) { if (lp == curline) { dotchar = nchars + curchar; dotline = i + 1; } nchars += length(lp) + (lp->l_next != 0); /* include the NL */ } s_mess("[\"%s\" line %d/%d, char %D/%D (%d%%), cursor = %d/%d]", filename(curbuf), dotline, i, dotchar, nchars, (nchars == 0) ? 100 : (int) (((long) dotchar * 100) / nchars), calc_pos(linebuf, curchar), calc_pos(linebuf, strlen(linebuf))); } #define IF_UNBOUND -1 #define IF_TRUE 1 #define IF_FALSE !IF_TRUE #ifndef MAC int do_if(cmd) char *cmd; { #ifdef MSDOS int status; #else int pid, status; #endif /* MSDOS */ #ifndef MSDOS switch (pid = fork()) { case -1: complain("[Fork failed: if]"); case 0: { #endif /* MSDOS */ char *args[12], *cp = cmd, **ap = args; *ap++ = cmd; for (;;) { if ((cp = index(cp, ' ')) == 0) break; *cp++ = '\0'; *ap++ = cp; } *ap = 0; #ifndef MSDOS close(0); /* we want reads to fail */ /* close(1); but not writes or ioctl's close(2); */ #else /* MSDOS */ if ((status = spawnvp(0, args[0], args)) < 0) complain("[Spawn failed: if]"); #endif /* MSDOS */ #ifndef MSDOS (void) execvp(args[0], args); _exit(-10); /* signals exec error (see below) */ } } #ifdef IPROCS sighold(SIGCHLD); #endif dowait(pid, &status); #ifdef IPROCS sigrelse(SIGCHLD); #endif if (status == -10) complain("[Exec failed]"); if (status < 0) complain("[Exit %d]", status); #endif /* MSDOS */ return (status == 0); /* 0 means successful */ } #endif /* MAC */ int joverc(file) char *file; { char buf[LBSIZE], lbuf[LBSIZE]; int lnum = 0, eof = FALSE; jmp_buf savejmp; int IfStatus = IF_UNBOUND; File *fp; fp = open_file(file, buf, F_READ, !COMPLAIN, QUIET); if (fp == NIL) return NO; /* joverc returns an integer */ /* Catch any errors, here, and do the right thing with them, and then restore the error handle to whoever did a setjmp last. */ InJoverc += 1; push_env(savejmp); if (setjmp(mainjmp)) { Buffer *savebuf = curbuf; SetBuf(do_select((Window *) 0, "RC errors")); ins_str(sprint("%s:%d:%s\t%s\n", pr_name(file, YES), lnum, lbuf, mesgbuf), NO); unmodify(); SetBuf(savebuf); Asking = 0; } if (!eof) do { eof = (f_gets(fp, lbuf, sizeof lbuf) == EOF); lnum += 1; if (lbuf[0] == '#') /* a comment */ continue; #ifndef MAC if (casencmp(lbuf, "if", 2) == 0) { char cmd[128]; if (IfStatus != IF_UNBOUND) complain("[Cannot have nested if's]"); if (LookingAt("if[ \t]*\\(.*\\)$", lbuf, 0) == 0) complain("[If syntax error]"); putmatch(1, cmd, sizeof cmd); IfStatus = do_if(cmd) ? IF_TRUE : IF_FALSE; continue; } else if (casencmp(lbuf, "else", 4) == 0) { if (IfStatus == IF_UNBOUND) complain("[Unexpected `else']"); IfStatus = !IfStatus; continue; } else if (casencmp(lbuf, "endif", 5) == 0) { if (IfStatus == IF_UNBOUND) complain("[Unexpected `endif']"); IfStatus = IF_UNBOUND; continue; } #endif if (IfStatus == IF_FALSE) continue; (void) strcat(lbuf, "\n"); Inputp = lbuf; while (*Inputp == ' ' || *Inputp == '\t') Inputp += 1; /* skip white space */ Extend(); } while (!eof); f_close(fp); pop_env(savejmp); Inputp = 0; Asking = 0; InJoverc -= 1; if (IfStatus != IF_UNBOUND) complain("[Missing endif]"); return 1; }